---
-Checks: 'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,bugprone-*,concurrency-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers'
+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,-cppcoreguidelines-avoid-do-while,-cppcoreguidelines-avoid-const-or-ref-data-members'
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
- key: readability-function-size.LineThreshold
value: '4294967295'
- key: bugprone-easily-swappable-parameters.MinimumLength
- value: '2'
+ value: '4'
- key: portability-simd-intrinsics.Suggest
value: 'false'
- key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader
- key: modernize-pass-by-value.ValuesOnly
value: 'false'
- key: readability-function-cognitive-complexity.IgnoreMacros
- value: 'false'
+ value: 'true'
- key: modernize-loop-convert.IncludeStyle
value: llvm
- key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons
- key: modernize-use-noexcept.UseNoexceptFalse
value: 'true'
- key: readability-function-cognitive-complexity.Threshold
- value: '25'
+ value: '75'
- key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
value: 'true'
- key: bugprone-argument-comment.IgnoreSingleArgument
Makefile.docker
**/venv
**/*.1
-!.git
+!.git/**
*.o
*.la
*.a
---
name: Bug report
-about: Create a report to help us improve
+about: Create a report to help us improve.
title: ''
labels: ''
assignees: ''
<!-- Hi! Thanks for filing an issue. It will be read with care by human beings. Can we ask you to please fill out this template and not simply demand new features or send in complaints? Thanks! -->
<!-- Also please search the existing issues (both open and closed) to see if your report might be duplicate -->
-<!-- Please don't file an issue when you have a support question, send support questions to the mailinglist or ask them on IRC (https://www.powerdns.com/opensource.html) -->
-<!-- Before filing your ticket, please read our 'out in the open' support policy at https://blog.powerdns.com/2016/01/18/open-source-support-out-in-the-open/ -->
+- [ ] This is not a support question, I have read [about opensource](https://www.powerdns.com/opensource.html) and will send support questions to the IRC channel, [Github Discussions](https://github.com/PowerDNS/pdns/discussions/) or the mailing list.
+- [ ] I have read and understood the ['out in the open' support policy](https://blog.powerdns.com/2016/01/18/open-source-support-out-in-the-open/)
<!-- Tell us what is issue is about -->
- Program: Authoritative, Recursor, dnsdist <!-- delete the ones that do not apply -->
blank_issues_enabled: false
contact_links:
+- name: Ask a question
+ url: https://github.com/PowerDNS/pdns/discussions/categories/q-a
+ about: Use GitHub discussions to ask Questions and get Answers.
- name: Get help with a question or a problem
url: https://www.powerdns.com/opensource.html
- about: Find us on IRC or our mailing lists to get the answers or help you need!
\ No newline at end of file
+ about: Find us on IRC or our mailing lists to get the answers or help you need!
---
name: Feature request
-about: Suggest an idea for this project
+about: Suggest an idea for this project.
title: ''
labels: ''
assignees: ''
baz
bbnew
bbold
+bbr
bbsv
bccaf
bdr
checkzone
chgrp
childstat
-chkconfig
chmod
chown
chr
dontinclude
dontqueries
DONTWAIT
+DOQ
doquery
dosec
dotests
localname
localsock
localstatedir
+localwho
loctext
locwild
logaction
sockowner
socktype
sodbc
-sodcrypto
sodiumsigners
sokolov
somedata
tcpbench
tcpbytesanswered
tcpclient
-tcpclientimeouts
+tcpclienttimeouts
tcpclientthreads
tcpcurrentconnections
tcpdiedreaddingresponse
# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
-# marker for ignoring a comment to the end of the line
-// #no-spell-check.*$
+# marker to ignore all code on line
+^.*\bno-spell-check(?:-line|)(?:\s.*|)$
+
+# https://cspell.org/configuration/document-settings/
+# cspell inline
+^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
# patch hunk comments
^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .*
# git index header
-index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
+index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
+
+# file permissions
+['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
+
+# css url wrappings
+#\burl\([^)]+\)
# cid urls
(['"])cid:.*?\g{-1}
# data url in parens
-\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
+\(data:(?:[^) ][^)]*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
# data url in quotes
-([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
+([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
# data url
data:[-a-zA-Z=;:/0-9+]*,\S*
+# https/http/file urls
+#(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]
+
# mailto urls
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
# asciinema
\basciinema\.org/a/[0-9a-zA-Z]+
+# asciinema v2
+^\[\d+\.\d+, "[io]", ".*"\]$
+
# apple
\bdeveloper\.apple\.com/[-\w?=/]+
# Apple music
# Google Drive
\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
# Google Groups
-\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)*
+\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)*
# Google Maps
\bmaps\.google\.com/maps\?[\w&;=]*
# Google themes
(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
# GitHub SHAs
\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
+# GitHub SHA refs
+\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]*
# GitHub wiki
\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
# githubusercontent
# git.io
\bgit\.io/[0-9a-zA-Z]+
# GitHub JSON
-"node_id": "[-a-zA-Z=;:/0-9+]*"
+"node_id": "[-a-zA-Z=;:/0-9+_]*"
# Contributor
-\[[^\]]+\]\(https://github\.com/[^/\s"]+\)
+\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\)
# GHSA
GHSA(?:-[0-9a-z]{4}){3}
# GitLab commits
\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
-# binanace
-accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
+# binance
+accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
# bitbucket diff
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
# ipfs protocol
-ipfs://[0-9a-z]*
+ipfs://[0-9a-zA-Z]{3,}
# ipfs url
-/ipfs/[0-9a-z]*
+/ipfs/[0-9a-zA-Z]{3,}
# w3
\bw3\.org/[-0-9a-zA-Z/#.]+
# Wikipedia
\ben\.wikipedia\.org/wiki/[-\w%.#]+
+# Contributors with non-ascii characters in their name
+Hoffst[^[:ascii:]]+tte
+Gri[^[:ascii:]]
+Lundstr[^[:ascii:]]+m
+Joaqu[^[:ascii:]]n
+
# gitweb
[^"\s]+/gitweb/\S+;h=[0-9a-f]+
# tinyurl
\btinyurl\.com/\w+
+# codepen
+\bcodepen\.io/[\w/]+
+
+# registry.npmjs.org
+\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+
+
# getopts
\bgetopts\s+(?:"[^"]+"|'[^']+')
# ANSI color codes
-(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
+(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m
# URL escaped characters
-\%[0-9A-F][A-F]
+\%[0-9A-F][A-F](?=[A-Za-z])
+# lower URL escaped characters
+\%[0-9a-f][a-f](?=[a-z]{2,})
# IPv6
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
# c99 hex digits (not the full format, just one I've seen)
# sha
sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
# sha-... -- uses a fancy capture
-(['"]|")[0-9a-f]{40,}\g{-1}
+(\\?['"]|")[0-9a-f]{40,}\g{-1}
# hex runs
\b[0-9a-fA-F]{16,}\b
# hex in url queries
# Well known gpg keys
.well-known/openpgpkey/[\w./]+
+# pki
+-----BEGIN.*-----END
+
# uuid:
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
# hex digits including css/html color classes:
-(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
+(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
# integrity
-integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}"
+integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1}
# https://www.gnu.org/software/groff/manual/groff.html
# man troff content
\\f[BCIPR]
-# '
-\\\(aq
+# '/"
+\\\([ad]q
# .desktop mime types
^MimeTypes?=.*$
# Localized .desktop content
Name\[[^\]]+\]=.*
-# IServiceProvider
-#\bI(?=(?:[A-Z][a-z]{2,})+\b)
+# IServiceProvider / isAThing
+#\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b)
# crypt
-"\$2[ayb]\$.{56}"
+(['"])\$2[ayb]\$.{56}\g{-1}
# scrypt / argon
\$(?:scrypt|argon\d+[di]*)\$\S+
+# go.sum
+#\bh1:\S+
+
+# scala modules
+("[^"]+"\s*%%?\s*){2,3}"[^"]+"
+
# Input to GitHub JSON
-content: "[-a-zA-Z=;:/0-9+]*="
+content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
+
+# This does not cover multiline strings, if your repository has them,
+# you'll want to remove the `(?=.*?")` suffix.
+# The `(?=.*?")` suffix should limit the false positives rate
+# printf
+%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA]|p)(?=[a-zA-Z]{2,}))(?=[_a-zA-Z]+\b)(?!%)(?=.*?['"])
-# Python stringprefix / binaryprefix
+# Python string prefix / binary prefix
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
-(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
+(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
# Regular expressions for (P|p)assword
\([A-Z]\|[a-z]\)[a-z]+
^\s*/\\[b].*/[gim]*\s*(?:\)(?:;|$)|,$)
# javascript replace regex
\.replace\(/[^/\s"]*/[gim]*\s*,
+# assign regex
+= /[^*]*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/
+# perl regex test
+[!=]~ (?:/.*/|m\{.*?\}|m<.*?>|m([|!/@#,;']).*?\g{-1})
+
+# perl qr regex
+(?<!\$)\bqr(?:\{.*?\}|<.*?>|\(.*?\)|([|!/@#,;']).*?\g{-1})
+
+# perl run
+perl(?:\s+-[a-zA-Z]\w*)+
# Go regular expressions
regexp?\.MustCompile\(`[^`]*`\)
+# regex choice
+\(\?:[^)]+\|[^)]+\)
+
+# proto
+^\s*(\w+)\s\g{-1} =
+
# sed regular expressions
sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}
+# node packages
+(["'])\@[^/'" ]+/[^/'" ]+\g{-1}
+
# go install
go install(?:\s+[a-z]+\.[-@\w/.]+)+
+# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571
+urn:shemas-jetbrains-com
+
# kubernetes pod status lists
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
-[0-9a-f]{10}-\w{5}\s
# posthog secrets
-posthog\.init\((['"])phc_[^"',]+\g{-1},
+([`'"])phc_[^"',]+\g{-1}
# xcode
# xcodeproject scenes
-(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}"
+(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}"
# xcode api botches
customObjectInstantitationMethod
+# configure flags
+.* \| --\w{2,}.*?(?=\w+\s\w+)
+
# font awesome classes
\.fa-[-a-z0-9]+
+# bearer auth
+(['"])Bear[e][r] .*?\g{-1}
+
+# basic auth
+(['"])Basic [-a-zA-Z=;:/0-9+]{3,}\g{-1}
+
+# base64 encoded content
+([`'"])[-a-zA-Z=;:/0-9+]+==\g{-1}
+# base64 encoded content in xml/sgml
+>[-a-zA-Z=;:/0-9+]+=</
+# base64 encoded content, possibly wrapped in mime
+#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
+
+# encoded-word
+=\?[-a-zA-Z0-9"*%]+\?[BQ]\?[^?]{0,75}\?=
+
+# Time Zones
+\b(?:Africa|Atlantic|America|Antarctica|Asia|Australia|Europe|Indian|Pacific)(?:/\w+)+
+
+# linux kernel info
+^(?:bugs|flags|Features)\s+:.*
+
+# systemd mode
+systemd.*?running in system mode \([-+].*\)$
+
# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*
# Non-English
-[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
+[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
+
+# highlighted letters
+\[[A-Z]\][a-z]+
# French
# This corpus only had capital letters, but you probably want lowercase ones as well.
\b[LN]'+[a-z]{2,}\b
-# latex
-\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
+# latex (check-spelling >= 0.0.22)
+\\\w{2,}\{
+
+# eslint
+"varsIgnorePattern": ".+"
+
+# Windows short paths
+[/\\][^/\\]{5,6}~\d{1,2}[/\\]
+
+# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
+# printf markers
+(?<!\\)\\[nrt](?=[a-z]{2,})
+# alternate markers if you run into latex and friends
+(?<!\\)\\[nrt](?=[a-z]{2,})(?=.*['"`])
+
+# apache
+a2(?:en|dis)
+
+# weak e-tag
+W/"[^"]+"
# the negative lookahead here is to allow catching 'templatesz' as a misspelling
# but to otherwise recognize a Windows path with \templates\foo.template or similar:
\\(?:necessary|r(?:eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b
-# Note that the next example is no longer necessary if you are using
-# to match a string starting with a `#`, use a character-class:
-[#]backwards
+
# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
-# Compiler flags (Scala)
-(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
-# Compiler flags
-#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+
+# Compiler flags (Unix, Java/Scala)
+# Use if you have things like `-Pdocker` and want to treat them as `docker`
+#(?:^|[\t ,>"'`=(])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+
+# Compiler flags (Windows / PowerShell)
+# This is a subset of the more general compiler flags pattern.
+# It avoids matching `-Path` to prevent it from being treated as `ath`
+#(?:^|[\t ,"'`=(])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
# Compiler flags (linker)
,-B
+
# curl arguments
\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# set arguments
(?:^|/)go\.mod$
(?:^|/)go\.sum$
(?:^|/)m4/
-(?:^|/)m4/
(?:^|/)package(?:-lock|)\.json$
-(?:^|/)requirements\.txt$
+(?:^|/)Pipfile$
+(?:^|/)pyproject.toml
+(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$
(?:^|/)vendor/
/expected_result
/test-base64_cc\.cc$
\.a$
\.ai$
\.asc$
+\.all-contributorsrc$
\.avi$
\.bmp$
\.bz2$
\.cer$
\.class$
+\.coveragerc$
\.crl$
\.crt$
\.csr$
\.eps$
\.exe$
\.gif$
+\.git-blame-ignore-revs$
\.gitattributes$
+\.gitkeep$
\.graffle$
\.gz$
\.icns$
\.ico$
+\.ipynb$
\.jar$
\.jks$
\.jpe?g$
\.map$
\.min\..
\.mmdb$
+\.mo$
\.mod$
-\.mp3$
-\.mp4$
\.mp[34]$
\.nsec3(?:-optout|)$
\.o$
\.ocf$
\.otf$
+\.p12$
+\.parquet$
\.pdf$
\.pem$
+\.pfx$
\.png$
\.psd$
\.pyc$
+\.pylintrc$
+\.qm$
\.s$
\.sig$
\.so$
\.supp$
-\.svg$
-\.svgz$
+\.svgz?$
+\.sys$
\.tar$
\.tgz$
\.tiff?$
\.xcf$
\.xlsx?$
\.xpm$
+\.xz$
\.yml$
\.zip$
^codedocs/doxygen\.conf$
aaaarecord
aaldering
-abi
aborttransaction
Abraitis
ACLTo
algoroll
allocs
Altpeter
-amd
Anderton
anewid
anid
ANSSI
Antoin
apikey
+apizones
AQAB
ARCHFLAGS
arecord
Asenov
ASEP
Ashish
-asnum
-aspx
associateddomain
asyncresolve
+ATHENE
Atlassian
Atomia
aton
Baan
backgrounding
backported
+backticks
BADALG
BADCOOKIE
badips
BINDTODEVICE
Binero
binlog
+bitfield
bitmaps
bla
blackhole
Cegetel
Cerb
certusage
-CFLAGS
CGNAT
changeme
changetype
closesocket
clusions
cmouse
-cmsg
cmsghdr
-cname
+cmsgs
cnamechainresolution
CNAMEd
CNAMEDNS
cnamerecord
+cnames
cnf
cnn
cockroachlabs
configfile
configname
configsetting
-configurability
confs
conntrack
Conntracking
Consolas
-constexpr
controllen
controlsocket
coprocess
costypetrisor
coverity
cppcheck
-createdb
createslavedomain
Cremers
criteo
cryptoshop
csum
csync
+Cunha
custommetrics
cve
cvename
Dayneko
dbfile
dblfilename
+dblookup
dbpf
dbr
-DBX
dcobject
ddns
+ddos
deactivatedomainkey
debian
deboynepollard
defttl
Dehaine
DENIC
-deref
descclassname
descname
Dessel
destname
Detlef
devicename
+devonly
devtoolset
DHCID
dhcpd
Dimitrios
Directi
Disqus
-distro
djbdns
dlerror
dlg
dnsext
dnsgram
dnskey
-dnsmessage
dnsname
dnsnameset
dnsop
dnswasher
dnszone
Dobrawy
+docdefault
docnamecachelookup
-documentclass
documentwrapper
-dofile
Dohmen
domaininfo
domainmetadata
domainrelatedobject
Donatas
dontcare
+doq
downsides
downstreams
dport
econds
ECSDA
ecswho
-EDE
editline
edns
ednsbufsiz
ejones
Ekkelenkamp
elgoog
-Emph
+endbr
Enden
+enp
ent
envoutput
epel
Faerch
failedservers
farsightsec
-fcgi
fcontext
fedoraproject
feedents
ffi
ffipolicy
filedescriptor
+finalised
findclientpolicy
Firefox
firewalled
firewalls
fixednow
Florus
-fontname
footerbgcolor
footertextcolor
forfun
fullycapable
Furnell
Fusl
+fwzones
FYhvws
FZq
gaba
gacogne
gatech
Gavarret
-gcc
+Gbps
gdpr
Geijn
genindex
getdomaininfo
getdomainkeys
getdomainmetadata
-gethostname
getifaddrs
getlocaladdress
getn
getregisteredname
gettag
gettime
-gettimeofday
gettsigkey
Geuze
GFm
+Ghz
Gibheer
Gieben
Gillstrom
gsqlite
gss
gssapi
-gsub
gtld
guilabel
Gyselinck
-hackerone
Hakulinen
Hannu
Harker
headlinkcolor
headtextcolor
healthcheck
+Heftrig
Heimhilcher
Helbekkmo
Hendriks
Holger
Hooimeijer
Hotmail
+Houtworm
howto
hpecorp
hpiers
-hpp
htbp
htmlescape
htmlhelp
hubert
iana
icann
-ico
ict
idprotect
-idx
iers
ietf
ifportup
ifurlextup
ifurlup
ihsinme
-illumos
Imhard
incbin
includeboilerplate
ipfilter
IPSECKEY
iputils
+irc
isane
ismaster
isoc
-isp
-ispell
+ISPs
isql
ixfr
ixfrdist
Jermar
Jeroen
jessie
-Joaqu
jonathaneen
Jong
Jorn
KEYBITS
keyblock
keydir
-keyfile
keyname
keypair
keypairgen
KTNAME
Kuehrer
kvs
+kxdpgun
Ladot
Lafon
Lakkas
libatomic
libcrypto
libcryptopp
-libcurl
libdecaf
libdir
libedit
libsofthsm
libsystemd
libtdsodbc
+libxdp
libyaml
libzmq
lightningstream
Lutter
Luuk
LYg
+Machard
Maik
Maikel
MAILA
mallocs
malware
Mamane
-manpages
mapasync
Mapbox
mariadb
metricscarbon
Meulen
Michiel
-middleware
Miek
Miell
Mieslinger
Mimimization
minbody
mindex
-MINFO
minipatch
Mischan
mjt
msdcs
MSDNS
msphinx
+msrv
mtasker
mthread
+mtid
Mulholland
multimaster
munmap
myuser
mywebapp
namedroppers
-nameserver
nameserving
naptr
Nauck
Neue
Neuf
newcontent
+nftables
nic
+Niklas
Nilsen
nimber
Nixu
noad
noall
nocookie
-NODELAY
+NODCACHEDIRNOD
+NODCACHEDIRUDR
noedns
noerrors
NOLOCK
noping
noport
norve
-nosniff
nostrip
NOSUBDIR
nosync
NULs
NUMA
numreceived
+nvd
nxd
NXDATA
nxdomain
objectclass
Obser
obspm
-odbc
odbcbackend
odbcinst
Oddy
openpgpkey
openports
opensc
+opensuse
openwall
Opmeer
OPNUM
papersize
paramater
PARAMKEYWORDS
-passthrough
-passthru
PATC
patchlevels
pathconfig
pgmysqlbackend
pgp
pgpsql
-pgsql
phishing
phonedph
+pickchashed
pickclosest
pickhashed
+picknamehashed
pickrandom
pickrandomsample
pickwhashed
Plusnet
plzz
pmtmr
-pnds
Poelov
pointsize
polarssl
primetime
princ
prioritization
-privatekey
privs
-progid
protobuf
protozero
providername
pseudorecord
pthread
ptrrecord
-ptrs
Publieke
publishdomainkey
pullreq
Qlim
qname
qperq
-qps
QPSIP
qpslimits
QRate
querytime
qytpe
ragel
+Rak
randombackend
randombit
randombytes
rdqueries
rdynamic
reconnections
+recordcache
recursor
+recursord
recursordist
Recursordoc
Recuweb
redjack
reentrantly
refman
-refreh
refuseds
reid
reimplementation
replacerrset
requery
resolv
-respawn
+respawned
respawning
respout
respsizes
reuseds
reuseport
RFCs
+rhel
Rietz
rightsidebar
Rijsdijk
Roel
Rosmalen
roundrobin
-RPATH
rping
rpms
rpz
rpzstatistics
rrcontent
-rrd
rrdata
rrdtool
rrname
Rueckert
rulesets
runtimedir
+rustup
Ruthensteiner
Rvd
rwlock
Scholten
Schryver
Schueler
+Schulmann
schwer
scopebits
scopemask
+sdfn
sdfoijdfio
sdig
secpoll
showdetails
showflags
Shukla
-sid
sidebarbgcolor
sidebarbtncolor
sidebarbutton
signpipe
signttl
signzone
-sigs
singlethreaded
Sipek
siphash
slaveness
SLES
smartcard
+Smeenk
smellyspice
smimea
smn
Smurthwaite
Snarked
sndbuf
-snmp
-snmpd
snprintf
soa
soadata
srcname
SRecord
Srule
-srv
sshfp
ssi
SSQ
stou
strcasestr
stringmatch
-strpos
+structuredlogging
stubquery
stubresolver
Stussy
stutiredboy
-subdomain
subkey
submitters
subnetmask
tbhandler
tcely
TCounters
-tcp
tcpconnecttimeouts
tcpdump
TCPKEEPALIVE
teeaction
Telenet
testsdir
-textcolor
Tful
thel
thelog
tinydnsbackend
tisr
tlsa
-tlsresumptions
-tmp
tmpfs
tobool
toctree
todos
toint
tokenuser
-tolower
Tolstov
Toosarani
Toshifumi
trx
trxid
TSAN
-tsc
tsig
tsigalgo
tsigkey
Tuxis
TVJRU
tylerneylon
-typedefs
typenames
ualberta
udpqueryresponse
Ueli
UIDs
Uisms
+UMEM
unauth
unbreak
-uncached
unescaping
unfresh
unhash
upperalpha
upperroman
urandom
-usec
usecase
userbase
-userspace
uwaterloo
Valentei
Valentini
voxel
Vranken
vulns
+Waidner
WAITFORONE
wal
wallclock
webdocs
webhandler
webpassword
+webservice
Webspider
Wegener
Weimer
Wijk
Wijnand
Wijngaards
-wikipedia
wil
wildcarded
Willcott
Wisiol
wmem
Wojas
-workaround
+workarounds
Worldnic
would've
wouter
xorbooter
xpf
XRecord
+xsk
+xskmap
XXXXXX
yahttp
+yamlconversion
+yamlsettings
Yehuda
yeswehack
Yiu
Ylitalo
-yml
YMMV
Yogesh
yourcompany
-# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere
+# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere
+# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529)
+# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46)
# \bm_data\b
# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
# to use this:
#\bfit\(
+# s.b. anymore
+\bany more[,.]
+
+# s.b. cannot
+\b[Cc]an not\b
+
# s.b. GitHub
-\bGithub\b
+(?<![&*.]|// |\btype )\bGithub\b(?![{)])
# s.b. GitLab
-\bGitlab\b
+(?<![&*.]|// |\btype )\bGitlab\b(?![{)])
# s.b. JavaScript
\bJavascript\b
+# s.b. macOS or Mac OS X or ...
+\bMacOS\b
+
# s.b. Microsoft
\bMicroSoft\b
+# s.b. TypeScript
+\bTypescript\b
+
# s.b. another
\ban[- ]other\b
+# s.b. deprecation warning
+\b[Dd]epreciation [Ww]arnings?\b
+
# s.b. greater than
\bgreater then\b
+# s.b. in front of
+\bin from of\b
+
# s.b. into
-#\sin to\s
+# when not phrasal and when `in order to` would be wrong:
+# https://thewritepractice.com/into-vs-in-to/
+\sin to\s(?!if\b)
+
+# s.b. is obsolete
+\bis obsolescent\b
+
+# s.b. it's or its
+\bits['’]
# s.b. opt-in
-#\sopt in\s
+(?<!\sfor)\sopt in\s
# s.b. less than
\bless then\b
+# s.b. one of
+\bon of\b
+
# s.b. otherwise
\bother[- ]wise\b
+# s.b. or (more|less)
+\bore (?:more|less)\b
+
# s.b. nonexistent
\bnon existing\b
\b[Nn]o[nt][- ]existent\b
+# s.b. brief / details/ param / return / retval
+(?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b
+
# s.b. preexisting
[Pp]re[- ]existing
# s.b. preemptively
[Pp]re[- ]emptively
+# s.b. recently changed or recent changes
+[Rr]ecent changed
+
# s.b. reentrancy
[Rr]e[- ]entrancy
# s.b. reentrant
[Rr]e[- ]entrant
-# s.b. workaround(s)
-#\bwork[- ]arounds?\b
+# s.b. understand
+\bunder stand\b
+
+# s.b. workarounds
+\bwork[- ]arounds\b
+
+# s.b. workaround
+(?:(?:[Aa]|[Tt]he|ugly)\swork[- ]around\b|\swork[- ]around\s+for)
+
+# s.b. (coarse|fine)-grained
+\b(?:coarse|fine) grained\b
+
+# s.b. neither/nor -- or reword
+#\bnot\b[^.?!"/(]+\bnor\b
+
+# probably a double negative
+# s.b. neither/nor (plus rewording the beginning)
+\bnot\b[^.?!"/]*\bneither\b[^.?!"/(]*\bnor\b
-# Reject duplicate words
+# In English, it is generally wrong to have the same word twice in a row without punctuation.
+# Duplicated words are generally mistakes.
+# There are a few exceptions where it is acceptable (e.g. "that that").
+# If the highlighted doubled word pair is in a code snippet, you can write a pattern to mask it.
+# If the highlighted doubled word pair is in prose, have someone read the English before you dismiss this error.
\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s
/docs/
^docs/
+pdns/recursordist/settings/table.py
# PGP
\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
+# hit-count: 4 file-count: 3
+# Non-English
+[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
+
# hit-count: 2 file-count: 2
# IServiceProvider
\bI(?=(?:[A-MOQ-Z][a-z]{2,})+\b)
# Wikipedia
\ben\.wikipedia\.org/wiki/[-\w%.#]+
-# Contributors with non-ascii characters in their name
-Hoffst[^[:ascii:]]+tte
-Gri[^[:ascii:]]
-Lundstr[^[:ascii:]]+m
+# hit-count: 1 file-count: 1
+# base64 encoded content
+([`'"])[-a-zA-Z=;:/0-9+]+==\g{-1}
# Questionably acceptable forms of `in to`
# Personally, I prefer `log into`, but people object
# https://www.tprteaching.com/log-into-log-in-to-login/
-\b[Ll]og in to\b
+\b(?:[Ll]og|[Ss]ign) in to\b
+
+# to opt in
+\bto opt in\b
# acceptable duplicates
# ls directory listings
-[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+
+[-bcdlpsw](?:[-r][-w][-SsTtx]){3}[\.+*]?\s+\d+\s+\S+\s+\S+\s+\d+\s+
+# mount
+\bmount\s+-t\s+(\w+)\s+\g{-1}\b
# C types and repeated CSS values
-\s(center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s
+\s(auto|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s
+# C struct
+\bstruct\s+(\w+)\s+\g{-1}\b
# go templates
-\s(\w+)\s+\g{-1}\s+\`(?:graphql|json|yaml):
-# javadoc / .net
-(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s
+\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
+# doxygen / javadoc / .net
+(?:[\\@](?:brief|groupname|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+static|\s+override|\s+readonly)*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
# Commit message -- Signed-off-by and friends
^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$
^attache$
+^bellow$
benefitting
occurences?
^dependan.*
--- /dev/null
+#!/usr/bin/env python3
+#
+#===- clang-tidy-diff.py - ClangTidy Diff Checker -----------*- python -*--===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+#===-----------------------------------------------------------------------===#
+
+r"""
+ClangTidy Diff Checker
+======================
+
+This script reads input from a unified diff, runs clang-tidy on all changed
+files and outputs clang-tidy warnings in changed lines only. This is useful to
+detect clang-tidy regressions in the lines touched by a specific patch.
+Example usage for git/svn users:
+
+ git diff -U0 HEAD^ | clang-tidy-diff.py -p1
+ svn diff --diff-cmd=diff -x-U0 | \
+ clang-tidy-diff.py -fix -checks=-*,modernize-use-override
+
+"""
+
+import argparse
+import glob
+import json
+import multiprocessing
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import threading
+import traceback
+
+from pathlib import Path
+
+try:
+ import yaml
+except ImportError:
+ yaml = None
+
+is_py2 = sys.version[0] == '2'
+
+if is_py2:
+ import Queue as queue
+else:
+ import queue as queue
+
+
+def run_tidy(task_queue, lock, timeout):
+ watchdog = None
+ while True:
+ command = task_queue.get()
+ try:
+ proc = subprocess.Popen(command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+
+ if timeout is not None:
+ watchdog = threading.Timer(timeout, proc.kill)
+ watchdog.start()
+
+ stdout, stderr = proc.communicate()
+
+ with lock:
+ sys.stdout.write(stdout.decode('utf-8') + '\n')
+ sys.stdout.flush()
+ if stderr:
+ sys.stderr.write(stderr.decode('utf-8') + '\n')
+ sys.stderr.flush()
+ except Exception as e:
+ with lock:
+ sys.stderr.write('Failed: ' + str(e) + ': '.join(command) + '\n')
+ finally:
+ with lock:
+ if not (timeout is None or watchdog is None):
+ if not watchdog.is_alive():
+ sys.stderr.write('Terminated by timeout: ' +
+ ' '.join(command) + '\n')
+ watchdog.cancel()
+ task_queue.task_done()
+
+
+def start_workers(max_tasks, tidy_caller, task_queue, lock, timeout):
+ for _ in range(max_tasks):
+ t = threading.Thread(target=tidy_caller, args=(task_queue, lock, timeout))
+ t.daemon = True
+ t.start()
+
+
+def merge_replacement_files(tmpdir, mergefile):
+ """Merge all replacement files in a directory into a single file"""
+ # The fixes suggested by clang-tidy >= 4.0.0 are given under
+ # the top level key 'Diagnostics' in the output yaml files
+ mergekey = "Diagnostics"
+ merged = []
+ for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')):
+ content = yaml.safe_load(open(replacefile, 'r'))
+ if not content:
+ continue # Skip empty files.
+ merged.extend(content.get(mergekey, []))
+
+ if merged:
+ # MainSourceFile: The key is required by the definition inside
+ # include/clang/Tooling/ReplacementsYaml.h, but the value
+ # is actually never used inside clang-apply-replacements,
+ # so we set it to '' here.
+ output = {'MainSourceFile': '', mergekey: merged}
+ with open(mergefile, 'w') as out:
+ yaml.safe_dump(output, out)
+ else:
+ # Empty the file:
+ open(mergefile, 'w').close()
+
+
+def main():
+ parser = argparse.ArgumentParser(description=
+ 'Run clang-tidy against changed files, and '
+ 'output diagnostics only for modified '
+ 'lines.')
+ parser.add_argument('-clang-tidy-binary', metavar='PATH',
+ default='clang-tidy',
+ help='path to clang-tidy binary')
+ parser.add_argument('-p', metavar='NUM', default=0,
+ help='strip the smallest prefix containing P slashes')
+ parser.add_argument('-regex', metavar='PATTERN', default=None,
+ help='custom pattern selecting file paths to check '
+ '(case sensitive, overrides -iregex)')
+ parser.add_argument('-iregex', metavar='PATTERN', default=
+ r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)',
+ help='custom pattern selecting file paths to check '
+ '(case insensitive, overridden by -regex)')
+ parser.add_argument('-j', type=int, default=1,
+ help='number of tidy instances to be run in parallel.')
+ parser.add_argument('-timeout', type=int, default=None,
+ help='timeout per each file in seconds.')
+ parser.add_argument('-fix', action='store_true', default=False,
+ help='apply suggested fixes')
+ parser.add_argument('-checks',
+ help='checks filter, when not specified, use clang-tidy '
+ 'default',
+ default='')
+ parser.add_argument('-use-color', action='store_true',
+ help='Use colors in output')
+ parser.add_argument('-path', dest='build_path',
+ help='Path used to read a compile command database.')
+ if yaml:
+ parser.add_argument('-export-fixes', metavar='FILE', dest='export_fixes',
+ help='Create a yaml file to store suggested fixes in, '
+ 'which can be applied with clang-apply-replacements.')
+ parser.add_argument('-extra-arg', dest='extra_arg',
+ action='append', default=[],
+ help='Additional argument to append to the compiler '
+ 'command line.')
+ parser.add_argument('-extra-arg-before', dest='extra_arg_before',
+ action='append', default=[],
+ help='Additional argument to prepend to the compiler '
+ 'command line.')
+ parser.add_argument('-quiet', action='store_true', default=False,
+ help='Run clang-tidy in quiet mode')
+ parser.add_argument('-load', dest='plugins',
+ action='append', default=[],
+ help='Load the specified plugin in clang-tidy.')
+
+ clang_tidy_args = []
+ argv = sys.argv[1:]
+ if '--' in argv:
+ clang_tidy_args.extend(argv[argv.index('--'):])
+ argv = argv[:argv.index('--')]
+
+ args = parser.parse_args(argv)
+
+ # Extract changed lines for each file.
+ filename = None
+ lines_by_file = {}
+ for line in sys.stdin:
+ match = re.search('^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % args.p, line)
+ if match:
+ filename = match.group(2)
+ if filename is None:
+ continue
+
+ if args.regex is not None:
+ if not re.match('^%s$' % args.regex, filename):
+ continue
+ else:
+ if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
+ continue
+
+ match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
+ if match:
+ start_line = int(match.group(1))
+ line_count = 1
+ if match.group(3):
+ line_count = int(match.group(3))
+ if line_count == 0:
+ continue
+ end_line = start_line + line_count - 1
+ lines_by_file.setdefault(filename, []).append([start_line, end_line])
+
+ if not any(lines_by_file):
+ print("No relevant changes found.")
+ sys.exit(0)
+
+ max_task_count = args.j
+ if max_task_count == 0:
+ max_task_count = multiprocessing.cpu_count()
+ max_task_count = min(len(lines_by_file), max_task_count)
+
+ tmpdir = None
+ if yaml and args.export_fixes:
+ tmpdir = tempfile.mkdtemp()
+
+ # Tasks for clang-tidy.
+ task_queue = queue.Queue(max_task_count)
+ # A lock for console output.
+ lock = threading.Lock()
+
+ # Run a pool of clang-tidy workers.
+ start_workers(max_task_count, run_tidy, task_queue, lock, args.timeout)
+
+ # Form the common args list.
+ common_clang_tidy_args = []
+ if args.fix:
+ common_clang_tidy_args.append('-fix')
+ if args.checks != '':
+ common_clang_tidy_args.append('-checks=' + args.checks)
+ if args.quiet:
+ common_clang_tidy_args.append('-quiet')
+ if args.build_path is not None:
+ common_clang_tidy_args.append('-p=%s' % args.build_path)
+ if args.use_color:
+ common_clang_tidy_args.append('--use-color')
+ for arg in args.extra_arg:
+ common_clang_tidy_args.append('-extra-arg=%s' % arg)
+ for arg in args.extra_arg_before:
+ common_clang_tidy_args.append('-extra-arg-before=%s' % arg)
+ for plugin in args.plugins:
+ common_clang_tidy_args.append('-load=%s' % plugin)
+
+ for name in lines_by_file:
+ line_filter_json = json.dumps(
+ # clang-tidy only supports filenames in -line-filter, not paths
+ [{"name": Path(name).name, "lines": lines_by_file[name]}],
+ separators=(',', ':'))
+
+ # Run clang-tidy on files containing changes.
+ command = [args.clang_tidy_binary]
+ command.append('-line-filter=' + line_filter_json)
+ if yaml and args.export_fixes:
+ # Get a temporary file. We immediately close the handle so clang-tidy can
+ # overwrite it.
+ (handle, tmp_name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir)
+ os.close(handle)
+ command.append('-export-fixes=' + tmp_name)
+ command.extend(common_clang_tidy_args)
+ command.append(name)
+ command.extend(clang_tidy_args)
+
+ task_queue.put(command)
+
+ # Wait for all threads to be done.
+ task_queue.join()
+
+ if yaml and args.export_fixes:
+ print('Writing fixes to ' + args.export_fixes + ' ...')
+ try:
+ merge_replacement_files(tmpdir, args.export_fixes)
+ except:
+ sys.stderr.write('Error exporting fixes.\n')
+ traceback.print_exc()
+
+ if tmpdir:
+ shutil.rmtree(tmpdir)
+
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+#!/usr/bin/env python3
+
+"""Clang-tidy to Github Actions annotations converter.
+
+Convert the YAML file produced by clang-tidy-diff containing warnings and
+suggested fixes to Github Actions annotations.
+
+"""
+
+import argparse
+import os
+import sys
+from pathlib import Path
+
+import helpers
+
+
+def create_argument_parser():
+ """Create command-line argument parser."""
+ parser = argparse.ArgumentParser(
+ description="Convert clang-tidy output to Github Actions"
+ )
+ parser.add_argument(
+ "--fixes-file",
+ type=str,
+ required=True,
+ help="Path to the clang-tidy fixes YAML",
+ )
+ return parser.parse_args()
+
+
+def main():
+ """Start the script."""
+ args = create_argument_parser()
+
+ repo_root_dir = Path(helpers.get_repo_root())
+ fixes_path = Path(args.fixes_file)
+ compdb_filename = os.path.join(fixes_path.parent, "compile_commands.json")
+ compdb = helpers.load_compdb(compdb_filename)
+ compdb = helpers.index_compdb(compdb)
+
+ fixes = helpers.load_fixes_file(args.fixes_file)
+
+ if not fixes:
+ print("No diagnostics or warnings produced by clang-tidy")
+ return 0
+
+ gh_step_summary = os.getenv("GITHUB_STEP_SUMMARY")
+ if gh_step_summary:
+ # Print Markdown summary
+ summary_fp = open(gh_step_summary, "a", encoding="utf-8")
+ print("### clang-tidy summary", file=summary_fp)
+
+ fixes = fixes["Diagnostics"]
+ have_warnings = False
+ for fix in fixes:
+ name = fix["DiagnosticName"]
+ level = fix["Level"]
+ directory = fix["BuildDirectory"]
+ diagnostic = fix["DiagnosticMessage"]
+ offset = diagnostic["FileOffset"]
+ filename = diagnostic["FilePath"]
+ message = diagnostic["Message"]
+
+ if filename == "":
+ print(f"Meta error message from `{directory}`: {message}")
+ continue
+
+ full_filename = filename
+ full_filename = Path(full_filename)
+ full_filename = (
+ full_filename.as_posix()
+ if full_filename.is_absolute()
+ else os.path.join(directory, filename)
+ )
+
+ try:
+ file_contents = helpers.load_file(full_filename)
+ except OSError:
+ # Skip in case the file can't be found. This is usually one of
+ # those "too many errors emitted, stopping now" clang messages.
+ print(f"Skipping `{full_filename}` because it is not found")
+ continue
+
+ line = helpers.get_line_from_offset(file_contents, offset)
+
+ rel_filename = Path(full_filename).resolve().relative_to(repo_root_dir)
+ annotation = "".join(
+ [
+ f"::warning file={rel_filename},line={line}",
+ f"::{message} ({name} - Level={level})",
+ ]
+ )
+ print(annotation)
+
+ # User-friendly printout
+ print(f"{level}: {rel_filename}:{line}: {message} ({name})")
+
+ if gh_step_summary:
+ print(
+ f"- **{rel_filename}:{line}** {message} (`{name}`)",
+ file=summary_fp,
+ )
+
+ have_warnings = True
+
+ return 1 if have_warnings else 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
--- /dev/null
+#!/usr/bin/env python3
+
+"""Filter git diff files that are not in the product.
+
+Filter files out of a git diff output that are not part of the product found in
+the current directory.
+
+"""
+
+import argparse
+import os
+import sys
+from pathlib import Path
+
+import helpers
+import unidiff
+
+
+def create_argument_parser():
+ """Create command-line argument parser."""
+ parser = argparse.ArgumentParser(
+ description="Filter git diff files that are not in the product"
+ )
+ parser.add_argument(
+ "--product",
+ type=str,
+ required=True,
+ help="Product (auth, dnsdist or rec)",
+ )
+ return parser.parse_args()
+
+
+def main():
+ """Start the script."""
+ args = create_argument_parser()
+ product = args.product
+
+ compdb = helpers.load_compdb("compile_commands.json")
+ compdb = helpers.index_compdb(compdb)
+
+ cwd = Path(os.getcwd())
+
+ diff = sys.stdin.read()
+ patch_set = unidiff.PatchSet(diff)
+ for patch in patch_set:
+ # We have to deal with several possible cases for input files, as shown
+ # by git:
+ #
+ # - in ext/: ext/lmdb-safe/lmdb-safe.cc
+ # - in modules/: modules/lmdbbackend/lmdbbackend.cc
+ # - files that live in the dnsdist or rec dir only:
+ # pdns/dnsdistdist/dnsdist-dnsparser.cc or
+ # pdns/recursordist/rec-tcp.cc
+ # - files that live in pdns/ and are used by several products (but
+ # possibly not with the same compilation flags, so it is actually
+ # important that they are processed for all products: pdns/misc.cc
+ path = Path(patch.path)
+ if product == "auth":
+ path = Path(cwd).joinpath(path)
+ else:
+ if str(path).startswith("modules"):
+ print(
+ f"Skipping {path}: modules do not apply to {product}",
+ file=sys.stderr,
+ )
+ continue
+
+ if str(path).startswith("ext"):
+ subpath = Path(cwd).joinpath(path)
+ else:
+ subpath = Path(cwd).joinpath(path.name)
+
+ if not subpath.exists():
+ print(
+ f"Skip {path}: doesn't exist for {product} ({subpath})",
+ file=sys.stderr,
+ )
+ continue
+
+ path = subpath
+ if patch.source_file is not None:
+ patch.source_file = str(path)
+ patch.target_file = str(path)
+
+ if not str(path) in compdb:
+ print(
+ f"Skipping {path}: it is not in the compilation db",
+ file=sys.stderr,
+ )
+ continue
+
+ print(patch, file=sys.stderr)
+ print(patch)
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
--- /dev/null
+"""Helpers for dealing with git, compilation databases, etc."""
+
+import json
+import os
+
+import git
+import yaml
+
+
+def load_file(filename):
+ """Load the entire contents of a file."""
+ with open(filename, encoding="utf-8") as file:
+ contents = file.read()
+ return contents
+
+
+def get_line_from_offset(file_contents, offset):
+ """Calculate line number from byte offset in source file."""
+ return file_contents[:offset].count("\n") + 1
+
+
+def get_repo_root():
+ """Get the git repo's root directory."""
+ cwd = os.getcwd()
+ repo = git.Repo(cwd, search_parent_directories=True)
+ root = repo.git.rev_parse("--show-toplevel")
+ return root
+
+
+def load_fixes_file(filename):
+ """Load the clang-tidy YAML fixes file."""
+ with open(filename, encoding="utf_8") as file:
+ return yaml.safe_load(file)
+
+
+def load_compdb(filename):
+ """Load the compilation database."""
+ with open(filename, encoding="utf_8") as file:
+ return json.load(file)
+
+
+def index_compdb(file_contents):
+ """Index the compilation database."""
+ result = set()
+ for item in file_contents:
+ filename = os.path.join(item["directory"], item["file"])
+ result.add(filename)
+ return result
--- /dev/null
+#!/usr/bin/env python
+
+import os
+import sys
+
+if __name__ == '__main__':
+ repositoryRoot = os.path.realpath(sys.argv[1])
+ version = sys.argv[2]
+ inputFile = sys.argv[3]
+ outputFile = sys.argv[4]
+ with open(inputFile, mode='r') as inputFilePtr:
+ with open(outputFile, mode='w') as outputFilePtr:
+ for line in inputFilePtr:
+ if not line.startswith('SF:'):
+ outputFilePtr.write(line)
+ continue
+
+ parts = line.split(':')
+ if len(parts) != 2:
+ outputFilePtr.write(line)
+ continue
+
+ source_file = parts[1].rstrip()
+ # get rid of symbolic links
+ target = os.path.realpath(source_file)
+
+ # get rid of the distdir path, to get file paths as they are in the repository
+ if f'pdns-{version}' in target:
+ # authoritative or tool
+ authPath = os.path.join(repositoryRoot, f'pdns-{version}')
+ relativeToAuth = os.path.relpath(target, authPath)
+ target = relativeToAuth
+ elif f'pdns-recursor-{version}' in target:
+ recPath = os.path.join(repositoryRoot, 'pdns', 'recursordist', f'pdns-recursor-{version}')
+ relativeToRec = os.path.relpath(target, recPath)
+ target = os.path.join('pdns', 'recursordist', relativeToRec)
+ elif f'dnsdist-{version}' in target:
+ distPath = os.path.join(repositoryRoot, 'pdns', 'dnsdistdist', f'dnsdist-{version}')
+ relativeToDist = os.path.relpath(target, distPath)
+ target = os.path.join('pdns', 'dnsdistdist', relativeToDist)
+ else:
+ print(f'Ignoring {target} that we could not map to a distdir', file=sys.stderr)
+ continue
+
+ # we need to properly map symbolic links
+ fullPath = os.path.join(repositoryRoot, target)
+ if os.path.islink(fullPath):
+ # get the link target
+ realPath = os.path.realpath(fullPath)
+ # and make it relative again
+ target = os.path.relpath(realPath, repositoryRoot)
+
+ outputFilePtr.write(f"SF:{target}\n")
--- /dev/null
+---
+name: Trigger workflow build-and-test-all for different releases
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 22 * * 4'
+
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+ actions: read
+ contents: read
+
+jobs:
+ call-build-and-test-all-auth-49:
+ name: Call build-and-test-all rel/auth-4.9.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/auth-4.9.x
+ with:
+ branch-name: rel/auth-4.9.x
+
+ call-build-and-test-all-auth-48:
+ name: Call build-and-test-all rel/auth-4.8.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/auth-4.8.x
+ with:
+ branch-name: rel/auth-4.8.x
+
+ call-build-and-test-all-auth-47:
+ name: Call build-and-test-all rel/auth-4.7.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/auth-4.7.x
+ with:
+ branch-name: rel/auth-4.7.x
+
+ call-build-and-test-all-auth-46:
+ name: Call build-and-test-all rel/auth-4.6.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/auth-4.6.x
+ with:
+ branch-name: rel/auth-4.6.x
+
+ call-build-and-test-all-rec-50:
+ name: Call build-and-test-all rel/rec-5.0.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/rec-5.0.x
+ with:
+ branch-name: rel/rec-5.0.x
+
+ call-build-and-test-all-rec-49:
+ name: Call build-and-test-all rel/rec-4.9.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/rec-4.9.x
+ with:
+ branch-name: rel/rec-4.9.x
+
+ call-build-and-test-all-rec-48:
+ name: Call build-and-test-all rel/rec-4.8.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/rec-4.8.x
+ with:
+ branch-name: rel/rec-4.8.x
+
+ call-build-and-test-all-dnsdist-19:
+ name: Call build-and-test-all rel/dnsdist-1.9.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/dnsdist-1.9.x
+ with:
+ branch-name: rel/dnsdist-1.9.x
+
+ call-build-and-test-all-dnsdist-18:
+ name: Call build-and-test-all rel/dnsdist-1.8.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/dnsdist-1.8.x
+ with:
+ branch-name: rel/dnsdist-1.8.x
+
+ call-build-and-test-all-dnsdist-17:
+ name: Call build-and-test-all rel/dnsdist-1.7.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+ uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/dnsdist-1.7.x
+ with:
+ branch-name: rel/dnsdist-1.7.x
on:
push:
pull_request:
+ workflow_call:
+ inputs:
+ branch-name:
+ description: 'Checkout to a specific branch'
+ required: true
+ default: ''
+ type: string
schedule:
- cron: '0 22 * * 3'
permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
contents: read
+env:
+ COMPILER: clang
+ CLANG_VERSION: '13'
+ # github.workspace variable points to the Runner home folder. Container home folder defined below.
+ REPO_HOME: '/__w/${{ github.event.repository.name }}/${{ github.event.repository.name }}'
+ BUILDER_VERSION: '0.0.0-git1'
+ COVERAGE: ${{ github.repository == 'PowerDNS/pdns' && 'yes' || 'no' }}
+ LLVM_PROFILE_FILE: "/tmp/code-%p.profraw"
+ OPTIMIZATIONS: yes
+ DECAF_SUPPORT: yes
+
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
+ runs-on: ubuntu-22.04
+ container:
+ image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-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
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
+ defaults:
+ run:
+ working-directory: ./pdns-${{ env.BUILDER_VERSION }}
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
- name: get timestamp for cache
id: get-stamp
run: |
echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
shell: bash
+ working-directory: .
+ - run: mkdir -p ~/.ccache
+ working-directory: .
- name: let GitHub cache our ccache data
- uses: actions/cache@v3
+ uses: actions/cache@v4
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-auth-build-deps
- run: inv ci-autoconf
+ working-directory: .
+ - run: inv ci-auth-configure
+ working-directory: .
+ - run: inv ci-make-distdir
+ working-directory: .
- run: inv ci-auth-configure
- - run: inv ci-auth-make
+ - run: inv ci-auth-make-bear # This runs under pdns-$BUILDER_VERSION/pdns/
- run: inv ci-auth-install-remotebackend-test-deps
- run: inv ci-auth-run-unit-tests
+ - run: inv generate-coverage-info ./testrunner $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' }}
+ working-directory: ./pdns-${{ env.BUILDER_VERSION }}/pdns
+ - name: Coveralls Parallel auth unit
+ if: ${{ env.COVERAGE == 'yes' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: auth-unit-${{ matrix.sanitizers }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
- run: inv ci-make-install
- run: ccache -s
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Store the binaries
- uses: actions/upload-artifact@v3 # this takes 30 seconds, maybe we want to tar
+ uses: actions/upload-artifact@v4 # this takes 30 seconds, maybe we want to tar
with:
- name: pdns-auth
+ name: pdns-auth-${{ env.normalized-branch-name }}
path: /opt/pdns-auth
retention-days: 1
build-recursor:
name: build recursor
if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
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
+ features: [least, full]
+ exclude:
+ - sanitizers: tsan
+ features: least
+ container:
+ image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-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
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
defaults:
run:
- working-directory: ./pdns/recursordist/
+ working-directory: ./pdns/recursordist/pdns-recursor-${{ env.BUILDER_VERSION }}
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
- name: get timestamp for cache
id: get-stamp
run: |
echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
shell: bash
+ working-directory: .
+ - run: mkdir -p ~/.ccache
+ working-directory: .
- name: let GitHub cache our ccache data
- uses: actions/cache@v3
+ uses: actions/cache@v4
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-rec-build-deps
+ key: recursor-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-${{ steps.get-stamp.outputs.stamp }}
+ restore-keys: recursor-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-
+ - run: inv ci-install-rust ${{ env.REPO_HOME }}
+ working-directory: ./pdns/recursordist/
- run: inv ci-autoconf
- - run: inv ci-rec-configure
- - run: inv ci-rec-make
+ working-directory: ./pdns/recursordist/
+ - run: inv ci-rec-configure ${{ matrix.features }}
+ working-directory: ./pdns/recursordist/
+ - run: inv ci-make-distdir
+ working-directory: ./pdns/recursordist/
+ - run: inv ci-rec-configure ${{ matrix.features }}
+ - run: inv ci-rec-make-bear
- run: inv ci-rec-run-unit-tests
+ - run: inv generate-coverage-info ./testrunner $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ - name: Coveralls Parallel rec unit
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: rec-unit-${{ matrix.features }}-${{ matrix.sanitizers }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
- run: inv ci-make-install
- run: ccache -s
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Store the binaries
- uses: actions/upload-artifact@v3 # this takes 30 seconds, maybe we want to tar
+ uses: actions/upload-artifact@v4 # this takes 30 seconds, maybe we want to tar
with:
- name: pdns-recursor-${{ matrix.sanitizers }}
+ name: pdns-recursor-${{ matrix.features }}-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
path: /opt/pdns-recursor
retention-days: 1
build-dnsdist:
name: build dnsdist
if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
strategy:
matrix:
sanitizers: [ubsan+asan, tsan]
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-12-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
+ FUZZING_TARGETS: yes
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
defaults:
run:
- working-directory: ./pdns/dnsdistdist/
+ working-directory: ./pdns/dnsdistdist/dnsdist-${{ env.BUILDER_VERSION }}
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
- name: get timestamp for cache
id: get-stamp
run: |
echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
shell: bash
+ working-directory: .
+ - run: mkdir -p ~/.ccache
+ working-directory: .
- name: let GitHub cache our ccache data
- uses: actions/cache@v3
+ uses: actions/cache@v4
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-dnsdist-build-deps
+ - run: inv ci-install-rust ${{ env.REPO_HOME }}
+ working-directory: ./pdns/dnsdistdist/
+ - run: inv ci-build-and-install-quiche
+ working-directory: ./pdns/dnsdistdist/
- run: inv ci-autoconf
+ working-directory: ./pdns/dnsdistdist/
- run: inv ci-dnsdist-configure ${{ matrix.features }}
- - run: inv ci-dnsdist-make
+ working-directory: ./pdns/dnsdistdist/
+ - run: inv ci-make-distdir
+ working-directory: ./pdns/dnsdistdist/
+ - run: inv ci-dnsdist-configure ${{ matrix.features }}
+ - run: inv ci-dnsdist-make-bear
- run: inv ci-dnsdist-run-unit-tests
+ - run: inv generate-coverage-info ./testrunner $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ - name: Coveralls Parallel dnsdist unit
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: dnsdist-unit-${{ matrix.features }}-${{ matrix.sanitizers }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
- run: inv ci-make-install
- run: ccache -s
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Store the binaries
- uses: actions/upload-artifact@v3 # this takes 30 seconds, maybe we want to tar
+ uses: actions/upload-artifact@v4 # this takes 30 seconds, maybe we want to tar
with:
- name: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}
+ name: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
path: /opt/dnsdist
retention-days: 1
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"
+ runs-on: ubuntu-22.04
+ container:
+ image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-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"
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
strategy:
matrix:
include:
options: >-
--restart always
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Fetch the binaries
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: pdns-auth
+ name: pdns-auth-${{ env.normalized-branch-name }}
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 }}
+ - run: inv generate-coverage-info /opt/pdns-auth/sbin/pdns_server $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' }}
+ - name: Coveralls Parallel auth API ${{ matrix.backend }}
+ if: ${{ env.COVERAGE == 'yes' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: auth-api-${{ matrix.backend }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
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/
+ runs-on: ubuntu-22.04
+ container:
+ image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-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"
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
strategy:
matrix:
include:
image: mcr.microsoft.com/mssql/server:2017-GA-ubuntu
env:
ACCEPT_EULA: Y
- SA_PASSWORD: 'SAsa12%%'
+ SA_PASSWORD: 'SAsa12%%-not-a-secret-password'
ports:
- 1433:1433
- backend: ldap
options: >-
--restart always
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Fetch the binaries
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: pdns-auth
+ name: pdns-auth-${{ env.normalized-branch-name }}
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 }}
+ - run: inv generate-coverage-info /opt/pdns-auth/sbin/pdns_server $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' }}
+ - name: Coveralls Parallel auth backend ${{ matrix.backend }}
+ if: ${{ env.COVERAGE == 'yes' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: auth-backend-${{ matrix.backend }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
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
+ runs-on: ubuntu-22.04
+ container:
+ image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-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
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Fetch the binaries
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: pdns-auth
+ name: pdns-auth-${{ env.normalized-branch-name }}
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
+ - run: inv generate-coverage-info /opt/pdns-auth/bin/ixfrdist $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' }}
+ - name: Coveralls Parallel ixfrdist
+ if: ${{ env.COVERAGE == 'yes' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: ixfrdist
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
test-recursor-api:
needs: build-recursor
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
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: [bookworm]
+ pdns_repo_version: ['48']
+ container:
+ image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-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"
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Fetch the binaries
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: pdns-recursor-${{ matrix.sanitizers }}
+ name: pdns-recursor-full-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
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
+ - run: inv generate-coverage-info /opt/pdns-recursor/sbin/pdns_recursor $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ - name: Coveralls Parallel recursor API
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: rec-api-full-${{ matrix.sanitizers }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
test-recursor-regression:
needs: build-recursor
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
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: [bookworm]
+ pdns_repo_version: ['48']
+ container:
+ image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-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"
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ # - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Fetch the binaries
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: pdns-recursor-${{ matrix.sanitizers }}
+ name: pdns-recursor-full-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
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
+ - run: inv generate-coverage-info /opt/pdns-recursor/sbin/pdns_recursor $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ - name: Coveralls Parallel recursor regression
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: rec-regression-full-${{ matrix.sanitizers }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
test-recursor-bulk:
name: 'test rec *mini* bulk'
needs: build-recursor
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
strategy:
matrix:
sanitizers: [ubsan+asan, tsan]
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-12-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"
+ options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Fetch the binaries
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: pdns-recursor-${{ matrix.sanitizers }}
+ name: pdns-recursor-full-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
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 }}
+ - run: inv generate-coverage-info /opt/pdns-recursor/sbin/pdns_recursor $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ - name: Coveralls Parallel recursor bulk
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: rec-regression-bulk-full-${{ matrix.sanitizers }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
test-dnsdist-regression:
needs: build-dnsdist
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
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-12-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
+ SANITIZERS: ${{ matrix.sanitizers }}
+ COVERAGE: yes
+ options: --sysctl net.ipv6.conf.all.disable_ipv6=0 --privileged
steps:
- - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ # workaround issue 9491 repo actions/runner-images
+ - name: get runner image version
+ id: runner-image-version
+ run: |
+ echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+ working-directory: .
+ - name: modify number of bits to use for aslr entropy
+ if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+ run: |
+ sudo sysctl -a | grep vm.mmap.rnd
+ sudo sysctl -w vm.mmap_rnd_bits=28
+ working-directory: .
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
+ - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
- name: Fetch the binaries
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: dnsdist-full-${{ matrix.sanitizers }}
+ name: dnsdist-full-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
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
+ - run: inv generate-coverage-info /opt/dnsdist/bin/dnsdist $GITHUB_WORKSPACE
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ - name: Coveralls Parallel dnsdist regression
+ if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ flag-name: dnsdist-regression-full-${{ matrix.sanitizers }}
+ path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+ parallel: true
+ allow-empty: true
swagger-syntax-check:
if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
+ # FIXME: https://github.com/PowerDNS/pdns/pull/12880
+ # container:
+ # image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+ # options: --sysctl net.ipv6.conf.all.disable_ipv6=0
steps:
- uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
- run: build-scripts/gh-actions-setup-inv # this runs apt update+upgrade
- run: inv install-swagger-tools
- run: inv swagger-syntax-check
- test-recursor-regression
- test-recursor-bulk
if: success() || failure()
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
steps:
+ - name: Coveralls Parallel Finished
+ if: ${{ env.COVERAGE == 'yes' }}
+ uses: coverallsapp/github-action@v2
+ with:
+ parallel-finished: true
- name: Install jq and yq
run: "sudo snap install jq yq"
- name: Fail job if any of the previous jobs failed
run: "for i in `echo '${{ toJSON(needs) }}' | jq '.[].result' | tr -d '\"'`; do if [[ $i == 'failure' ]]; then echo '${{ toJSON(needs) }}'; exit 1; fi; done;"
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
+ ref: ${{ inputs.branch-name }}
- name: Get list of jobs in the workflow
run: "yq e '.jobs | keys' .github/workflows/build-and-test-all.yml | awk '{print $2}' | grep -v collect | sort | tee /tmp/workflow-jobs-list.yml"
- name: Get list of prerequisite jobs
--- /dev/null
+---
+name: Build packages
+
+on:
+ workflow_call:
+ inputs:
+ product:
+ required: true
+ description: Product to build
+ type: string
+ os:
+ required: false
+ description: OSes to build for, space separated
+ type: string
+ # please remember to update the pkghashes below when you
+ # update this list, as well as the one in builder-dispatch.yml
+ default: >-
+ el-7
+ el-8
+ el-9
+ debian-buster
+ debian-bullseye
+ debian-bookworm
+ ubuntu-focal
+ ubuntu-jammy
+ ubuntu-noble
+ ref:
+ description: git ref to checkout
+ type: string
+ default: master
+ required: false
+ is_release:
+ description: is this a release build?
+ type: string
+ required: false
+ default: 'NO'
+ secrets:
+ DOWNLOADS_AUTOBUILT_SECRET:
+ required: true
+ DOWNLOADS_AUTOBUILT_RSYNCTARGET:
+ required: true
+ DOWNLOADS_AUTOBUILT_HOSTKEY:
+ required: true
+
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+ contents: read
+
+jobs:
+ prepare:
+ name: generate OS list
+ runs-on: ubuntu-20.04
+ outputs:
+ oslist: ${{ steps.get-oslist.outputs.oslist }}
+ steps:
+ # instead of jo, we could use jq here, which avoids running apt, and thus would be faster.
+ # but, as this whole workflow needs at least 30 minutes to run, I prefer spending a few seconds here
+ # so that the command remains readable, because jo is simpler to use.
+ - run: sudo apt-get update && sudo apt-get -y install jo
+ - id: get-oslist
+ run: echo "oslist=$(jo -a ${{ inputs.os }})" >> "$GITHUB_OUTPUT"
+ build:
+ needs: prepare
+ name: build ${{ inputs.product }} (${{ inputs.ref }}) for ${{ matrix.os }}
+ # on a ubuntu-20.04 VM
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ os: ${{fromJson(needs.prepare.outputs.oslist)}}
+ fail-fast: false
+ outputs:
+ version: ${{ steps.getversion.outputs.version }}
+ pkghashes-el-7: ${{ steps.pkghashes.outputs.pkghashes-el-7 }}
+ pkghashes-el-8: ${{ steps.pkghashes.outputs.pkghashes-el-8 }}
+ pkghashes-el-9: ${{ steps.pkghashes.outputs.pkghashes-el-9 }}
+ pkghashes-debian-buster: ${{ steps.pkghashes.outputs.pkghashes-debian-buster }}
+ pkghashes-debian-bullseye: ${{ steps.pkghashes.outputs.pkghashes-debian-bullseye }}
+ pkghashes-debian-bookworm: ${{ steps.pkghashes.outputs.pkghashes-debian-bookworm }}
+ pkghashes-ubuntu-focal: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-focal }}
+ pkghashes-ubuntu-jammy: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-jammy }}
+ pkghashes-ubuntu-noble: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-noble }}
+ srchashes: ${{ steps.srchashes.outputs.srchashes }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # for correct version numbers
+ submodules: recursive
+ ref: ${{ inputs.ref }}
+ # this builds packages and runs our unit tests (make check)
+ - run: IS_RELEASE=${{ inputs.is_release}} builder/build.sh -v -m ${{ inputs.product }} ${{ matrix.os }}
+ - name: Get version number
+ run: |
+ echo "version=$(readlink builder/tmp/latest)" >> $GITHUB_OUTPUT
+ id: getversion
+ - name: Upload packages as GH artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ inputs.product }}-${{ matrix.os }}-${{ steps.getversion.outputs.version }}
+ path: built_pkgs/
+ retention-days: 7
+ - name: Normalize package name
+ id: normalize-name
+ run: |
+ if [ "x${{ inputs.product }}" = "xauthoritative" ]; then
+ echo "normalized-package-name=pdns" >> $GITHUB_OUTPUT
+ elif [ "x${{ inputs.product }}" = "xrecursor" ]; then
+ echo "normalized-package-name=pdns-recursor" >> $GITHUB_OUTPUT
+ else
+ echo "normalized-package-name=${{ inputs.product }}" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Extract packages from the tarball
+ # so we get provenance for individual packages (and the JSON package manifests from the builder)
+ id: extract
+ run: |
+ mkdir -m 700 -p ./packages/
+ tar xvf ./built_pkgs/*/*/${{ steps.normalize-name.outputs.normalized-package-name }}-${{ steps.getversion.outputs.version }}-${{ matrix.os }}.tar.bz2 -C ./packages/ --transform='s/.*\///'
+ - name: Generate package hashes for provenance
+ shell: bash
+ id: pkghashes
+ run: |
+ echo "pkghashes-${{ matrix.os }}=$(sha256sum ./packages/*.rpm ./packages/*.deb ./packages/*.json | base64 -w0)" >> $GITHUB_OUTPUT
+ - name: Generate source hash for provenance
+ shell: bash
+ id: srchashes
+ run: |
+ echo "srchashes=$(sha256sum ./built_pkgs/*/*/${{ steps.normalize-name.outputs.normalized-package-name }}-${{ steps.getversion.outputs.version }}.tar.bz2 ./packages/*.json | base64 -w0)" >> $GITHUB_OUTPUT
+ - name: Upload packages to downloads.powerdns.com
+ env:
+ SSHKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+ RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+ HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
+ if:
+ "${{ env.SSHKEY != '' }}"
+ run: |
+ mkdir -m 700 -p ~/.ssh
+ echo "$SSHKEY" > ~/.ssh/id_ed25519
+ chmod 600 ~/.ssh/id_ed25519
+ echo "$HOSTKEY" > ~/.ssh/known_hosts
+ rsync -4rlptD built_pkgs/* "$RSYNCTARGET"
+
+ check-hashes:
+ needs: build
+ name: Check if hashes were created for all requested targets
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Get list of outputs from build jobs
+ run: echo '${{ toJSON(needs.build.outputs) }}' | jq 'keys[]' | grep -v version | tee /tmp/build-outputs.txt
+ - name: Get list of OS inputs
+ run: for i in ${{ inputs.os }}; do echo "\"pkghashes-$i\""; done | sort | tee /tmp/os-inputs.txt; echo "\"srchashes\"" | tee -a /tmp/os-inputs.txt
+ - name: Fail if there is a hash missing
+ run: if ! diff -q /tmp/build-outputs.txt /tmp/os-inputs.txt; then exit 1; fi
+
+ provenance-pkgs:
+ needs: [prepare, build]
+ name: Generate provenance for ${{ inputs.product }} (${{ inputs.ref }}) for ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: ${{fromJson(needs.prepare.outputs.oslist)}}
+ permissions:
+ actions: read # To read the workflow path.
+ id-token: write # To sign the provenance.
+ contents: write # To be able to upload assets as release artifacts
+ uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
+ with:
+ base64-subjects: "${{ needs.build.outputs[format('pkghashes-{0}', matrix.os)] }}"
+ upload-assets: false
+ provenance-name: "${{ inputs.product }}-${{ needs.build.outputs.version }}-${{ matrix.os}}.intoto.jsonl"
+
+ provenance-src:
+ needs: build
+ name: Generate provenance for ${{ inputs.product }} (${{ inputs.ref }}) source tarball
+ permissions:
+ actions: read # To read the workflow path.
+ id-token: write # To sign the provenance.
+ contents: write # To be able to upload assets as release artifacts
+ uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
+ with:
+ base64-subjects: "${{ needs.build.outputs.srchashes }}"
+ upload-assets: false
+ provenance-name: "${{ inputs.product }}-${{ needs.build.outputs.version }}-src.intoto.jsonl"
+
+ upload-provenance:
+ needs: [prepare, build, provenance-src, provenance-pkgs]
+ name: Upload the provenance artifacts to downloads.powerdns.com
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ os: ${{fromJson(needs.prepare.outputs.oslist)}}
+ steps:
+ - name: Download source tarball provenance for ${{ inputs.product }} (${{ inputs.ref }})
+ id: download-src-provenance
+ uses: actions/download-artifact@v3 # we need v3, see https://github.com/slsa-framework/slsa-github-generator/pull/3067/files
+ with:
+ name: "${{ inputs.product }}-${{ needs.build.outputs.version }}-src.intoto.jsonl"
+ - name: Download provenance for ${{ inputs.product }} (${{ inputs.ref }}) for ${{ matrix.os }}
+ id: download-provenance
+ uses: actions/download-artifact@v3 # we need v3, see https://github.com/slsa-framework/slsa-github-generator/pull/3067/files
+ with:
+ name: "${{ inputs.product }}-${{ needs.build.outputs.version }}-${{ matrix.os}}.intoto.jsonl"
+ - name: Upload provenance artifacts to downloads.powerdns.com
+ id: upload-provenance
+ env:
+ SSHKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+ RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+ HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
+ PRODUCT: ${{ inputs.product }}
+ VERSION: ${{ needs.build.outputs.version }}
+ if:
+ "${{ env.SSHKEY != '' }}"
+ shell: bash
+ run: |
+ mkdir -m 700 -p ~/.ssh
+ echo "$SSHKEY" > ~/.ssh/id_ed25519
+ chmod 600 ~/.ssh/id_ed25519
+ echo "$HOSTKEY" > ~/.ssh/known_hosts
+ rsync -4rlptD ${{steps.download-src-provenance.outputs.download-path}}/*.jsonl ${{steps.download-provenance.outputs.download-path}}/*.jsonl "${RSYNCTARGET}/${PRODUCT}/${VERSION}/"
--- /dev/null
+---
+name: Build packages for tags
+
+on:
+ push:
+ tags:
+ - 'auth-*'
+ - 'dnsdist-*'
+ - 'rec-*'
+
+permissions:
+ actions: read
+ id-token: write
+ contents: write
+
+jobs:
+ call-build-packages-auth:
+ uses: PowerDNS/pdns/.github/workflows/build-packages.yml@master
+ if: startsWith(github.ref_name, 'auth')
+ with:
+ is_release: 'YES'
+ product: 'authoritative'
+ ref: ${{ github.ref_name }}
+ secrets:
+ DOWNLOADS_AUTOBUILT_SECRET: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+ DOWNLOADS_AUTOBUILT_RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+ DOWNLOADS_AUTOBUILT_HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
+
+ call-build-packages-dnsdist:
+ uses: PowerDNS/pdns/.github/workflows/build-packages.yml@master
+ if: startsWith(github.ref_name, 'dnsdist')
+ with:
+ is_release: 'YES'
+ product: 'dnsdist'
+ ref: ${{ github.ref_name }}
+ secrets:
+ DOWNLOADS_AUTOBUILT_SECRET: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+ DOWNLOADS_AUTOBUILT_RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+ DOWNLOADS_AUTOBUILT_HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
+
+ call-build-packages-rec:
+ uses: PowerDNS/pdns/.github/workflows/build-packages.yml@master
+ if: startsWith(github.ref_name, 'rec')
+ with:
+ is_release: 'YES'
+ product: 'recursor'
+ ref: ${{ github.ref_name }}
+ secrets:
+ DOWNLOADS_AUTOBUILT_SECRET: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+ DOWNLOADS_AUTOBUILT_RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+ DOWNLOADS_AUTOBUILT_HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
os:
description: OSes to build for, space separated
type: string
- default: >
+ # please remember to update build-packages.yml as well
+ default: >-
el-7
el-8
el-9
debian-buster
debian-bullseye
- ubuntu-bionic
+ debian-bookworm
ubuntu-focal
ubuntu-jammy
+ ubuntu-noble
ref:
description: git ref to checkout
type: string
- 'YES'
permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
- contents: read
+ actions: read
+ contents: write # To be able to upload assets as release artifacts
+ id-token: write # To sign the provenance in the build packages reusable workflow.
jobs:
- prepare:
- name: generate OS list
- runs-on: ubuntu-20.04
- outputs:
- oslist: ${{ steps.get-oslist.outputs.oslist }}
- steps:
- # instead of jo, we could use jq here, which avoids running apt, and thus would be faster.
- # but, as this whole workflow needs at least 30 minutes to run, I prefer spending a few seconds here
- # so that the command remains readable, because jo is simpler to use.
- - run: sudo apt-get update && sudo apt-get -y install jo
- - id: get-oslist
- run: echo "oslist=$(jo -a ${{ github.event.inputs.os }})" >> "$GITHUB_OUTPUT"
-
- build:
- needs: prepare
- name: build ${{ github.event.inputs.product }} (${{ github.event.inputs.ref }}) for ${{ matrix.os }}
- # on a ubuntu-20.04 VM
- runs-on: ubuntu-20.04
- strategy:
- matrix:
- os: ${{fromJson(needs.prepare.outputs.oslist)}}
- fail-fast: false
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0 # for correct version numbers
- submodules: recursive
- ref: ${{ github.event.inputs.ref }}
- # this builds packages and runs our unit tests (make check)
- - run: IS_RELEASE=${{ github.event.inputs.is_release}} builder/build.sh -v -m ${{ github.event.inputs.product }} ${{ matrix.os }}
- - name: Get version number
- run: 'echo ::set-output name=version::$(readlink builder/tmp/latest)'
- id: getversion
- - name: Upload packages as GH artifacts
- uses: actions/upload-artifact@v3
- with:
- name: ${{ github.event.inputs.product }}-${{ matrix.os }}-${{ steps.getversion.outputs.version }}
- path: built_pkgs/
- retention-days: 7
- - name: Upload packages to downloads.powerdns.com
- env:
- SSHKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
- RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
- HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
- if:
- "${{ env.SSHKEY != '' }}"
- run: |
- mkdir -m 700 -p ~/.ssh
- echo "$SSHKEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- echo "$HOSTKEY" > ~/.ssh/known_hosts
- rsync -4rlptD built_pkgs/* "$RSYNCTARGET"
+ call-build-packages:
+ uses: PowerDNS/pdns/.github/workflows/build-packages.yml@master
+ with:
+ product: ${{ github.event.inputs.product }}
+ os: ${{ github.event.inputs.os }}
+ ref: ${{ github.event.inputs.ref }}
+ is_release: ${{ github.event.inputs.is_release }}
+ secrets:
+ DOWNLOADS_AUTOBUILT_SECRET: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+ DOWNLOADS_AUTOBUILT_RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+ DOWNLOADS_AUTOBUILT_HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
--- /dev/null
+---
+name: Trigger workflow builder for different releases
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 2 * * *'
+
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+ actions: read
+ contents: read
+
+jobs:
+ call-builder-auth-49:
+ name: Call builder rel/auth-4.9.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/auth-4.9.x
+ with:
+ branch-name: rel/auth-4.9.x
+
+ call-builder-auth-48:
+ name: Call builder rel/auth-4.8.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/auth-4.8.x
+ with:
+ branch-name: rel/auth-4.8.x
+
+ call-builder-auth-47:
+ name: Call builder rel/auth-4.7.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/auth-4.7.x
+ with:
+ branch-name: rel/auth-4.7.x
+
+ call-builder-auth-46:
+ name: Call builder rel/auth-4.6.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/auth-4.6.x
+ with:
+ branch-name: rel/auth-4.6.x
+
+ call-builder-rec-50:
+ name: Call builder rel/rec-5.0.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/rec-5.0.x
+ with:
+ branch-name: rel/rec-5.0.x
+
+ call-builder-rec-49:
+ name: Call builder rel/rec-4.9.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/rec-4.9.x
+ with:
+ branch-name: rel/rec-4.9.x
+
+ call-builder-rec-48:
+ name: Call builder rel/rec-4.8.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/rec-4.8.x
+ with:
+ branch-name: rel/rec-4.8.x
+
+ call-builder-dnsdist-19:
+ name: Call builder rel/dnsdist-1.9.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/dnsdist-1.9.x
+ with:
+ branch-name: rel/dnsdist-1.9.x
+
+ call-builder-dnsdist-18:
+ name: Call builder rel/dnsdist-1.8.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/dnsdist-1.8.x
+ with:
+ branch-name: rel/dnsdist-1.8.x
+
+ call-builder-dnsdist-17:
+ name: Call builder rel/dnsdist-1.7.x
+ if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+ uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/dnsdist-1.7.x
+ with:
+ branch-name: rel/dnsdist-1.7.x
name: 'Test package building for specific distributions'
on:
+ workflow_call:
+ inputs:
+ branch-name:
+ description: 'Checkout to a specific branch'
+ required: true
+ default: ''
+ type: string
schedule:
- cron: '0 1 * * *'
product: ['authoritative', 'recursor', 'dnsdist']
os:
- centos-7
- - ubuntu-bionic
- el-8
- centos-8-stream
- centos-9-stream
- - ubuntu-kinetic
- ubuntu-lunar
+ - ubuntu-mantic
+ - ubuntu-noble
- debian-bookworm
+ - debian-trixie
- amazon-2023
fail-fast: false
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0 # for correct version numbers
submodules: recursive
+ ref: ${{ inputs.branch-name }}
# this builds packages and runs our unit test (make check)
- run: builder/build.sh -v -m ${{ matrix.product }} ${{ matrix.os }}
- name: Get version number
- run: 'echo ::set-output name=version::$(readlink builder/tmp/latest)'
+ run: |
+ echo "version=$(readlink builder/tmp/latest)" >> $GITHUB_OUTPUT
id: getversion
- name: Upload packages
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: ${{ matrix.product }}-${{ matrix.os }}-${{ steps.getversion.outputs.version }}
path: built_pkgs/
-name: "CodeQL"
+name: "CodeQL and clang-tidy"
on:
push:
permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
contents: read
+# clang-tidy fun:
+# We need to invoke clang-tidy from the correct directory, the one the product was compiled in, so that we get the correct include paths.
+# This means the root for the auth, pdns/recursordist for the rec and pdns/dnsdistdist for dnsdist
+# It is important that files that are used by more than one product are processed by all the products using them
+# because they might have difference compilation flags.
+# We have to use our own clang-tidy-diff.py because the line-filter flag only supports file names, not paths.
+# Finally the GH annotations that we generate from clang-tidy.py, have to be relative to the path in the git repository, so we need to
+# follow symlinks.
+# How does that work? We use git diff to get the list of diffs, and git-filter.py to get the right folder depending on the product.
+# Then we call clang-tidy-diff.py, which invokes clang-tidy on the correct file, deducing the line numbers from the diff, and
+# merging the results for all processed files to a YAML file. Finally clang-tidy.py converts the YAML output to GitHub annotations
+# (GitHub only supports 10 of these per job, the rest are not displayed) and to GitHub markdown step summary (which has no such limits).
+
jobs:
analyze:
name: Analyze
if: ${{ !github.event.schedule || vars.SCHEDULED_CODEQL_ANALYSIS }}
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
permissions:
actions: read # for github/codeql-action/init to get workflow details
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
+ env:
+ COMPILER: gcc
+ UNIT_TESTS: yes
+ FUZZING_TARGETS: yes
+ COVERAGE: no
+ OPTIMIZATIONS: no
+ # for clang-tidy only, not compilation
+ CLANG_VERSION: '14'
+ REPO_HOME: ${{ github.workspace }}
+ DECAF_SUPPORT: no
+
+ outputs:
+ clang-tidy-annotations-auth: ${{ steps.clang-tidy-annotations-auth.outputs.failed }}
+ clang-tidy-annotations-dnsdist: ${{ steps.clang-tidy-annotations-dnsdist.outputs.failed }}
+ clang-tidy-annotations-rec: ${{ steps.clang-tidy-annotations-rec.outputs.failed }}
+
steps:
- uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
- # Python is required for building the Authoritative server
- - uses: actions/setup-python@v4
- with:
- python-version: '3.8'
-
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
# TODO: go through +security-and-quality (400 alerts) once, then see if we can upgrade to it
# If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
+ # By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Update repository metadata
run: |
sudo apt-get update
- - name: Install dependencies
- run: |
- sudo apt-get -qq -y --no-install-recommends --allow-downgrades install \
- bison \
- default-libmysqlclient-dev \
- flex \
- libboost-all-dev \
- libcap-dev \
- libcdb-dev \
- libcurl4-openssl-dev \
- libedit-dev \
- libfstrm-dev \
- libgeoip-dev \
- libgnutls28-dev \
- libh2o-evloop-dev \
- libkrb5-dev \
- libldap2-dev \
- liblmdb-dev \
- liblua5.3-dev \
- libmaxminddb-dev \
- libnghttp2-dev \
- libp11-kit-dev \
- libpq-dev \
- libre2-dev \
- libsnmp-dev \
- libsodium-dev \
- libsqlite3-dev \
- libssl-dev \
- libsystemd-dev \
- libwslay-dev \
- libyaml-cpp-dev \
- ragel \
- unixodbc-dev
+ - name: Install python invoke and needed libs
+ run: |
+ sudo apt-get -qq -y --no-install-recommends install python3 python3-pip python3-invoke python3-git python3-unidiff ccache
+
+ - name: Install clang-tidy tools
+ run: |
+ inv install-clang-tidy-tools
+
+ - name: Install dependencies for auth
+ if: matrix.product == 'auth'
+ run: |
+ inv install-auth-build-deps
+ - name: Autoreconf auth
+ if: matrix.product == 'auth'
+ run: |
+ inv ci-autoconf
+ - name: Configure auth
+ if: matrix.product == 'auth'
+ run: |
+ inv ci-auth-configure
- name: Build auth
if: matrix.product == 'auth'
run: |
- autoreconf -vfi
- ./configure --with-modules='bind geoip gmysql godbc gpgsql gsqlite3 ldap lmdb lua2 pipe remote tinydns' --enable-tools --enable-ixfrdist --enable-dns-over-tls --enable-experimental-pkcs11 --with-libsodium --enable-lua-records CFLAGS='-O0' CXXFLAGS='-O0'
- make -j8 -C ext
- make -j8 -C modules
- make -j8 -C pdns
+ inv ci-auth-make-bear
+ - run: ln -s .clang-tidy.full .clang-tidy
+ if: matrix.product == 'auth'
+ - name: Run clang-tidy for auth
+ if: matrix.product == 'auth'
+ run: git diff --no-prefix -U0 HEAD^..HEAD | python3 .github/scripts/git-filter.py --product auth | python3 .github/scripts/clang-tidy-diff.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p0 -export-fixes clang-tidy-auth.yml
+ - name: Print clang-tidy fixes YAML for auth
+ if: matrix.product == 'auth'
+ shell: bash
+ run: |
+ if [ -f clang-tidy-auth.yml ]; then
+ cat clang-tidy-auth.yml
+ fi
+ - name: Result annotations for auth
+ if: matrix.product == 'auth'
+ id: clang-tidy-annotations-auth
+ shell: bash
+ run: |
+ if [ -f clang-tidy-auth.yml ]; then
+ set +e
+ python3 .github/scripts/clang-tidy.py --fixes-file clang-tidy-auth.yml
+ echo "failed=$?" >> $GITHUB_OUTPUT
+ fi
+ - name: Install dependencies for dnsdist
+ if: matrix.product == 'dnsdist'
+ run: |
+ inv install-dnsdist-build-deps --skipXDP
+ - name: Autoreconf dnsdist
+ if: matrix.product == 'dnsdist'
+ working-directory: ./pdns/dnsdistdist/
+ run: |
+ inv ci-autoconf
+ - run: inv ci-install-rust ${{ env.REPO_HOME }}
+ if: matrix.product == 'dnsdist'
+ working-directory: ./pdns/dnsdistdist/
+ - run: inv ci-build-and-install-quiche
+ if: matrix.product == 'dnsdist'
+ working-directory: ./pdns/dnsdistdist/
+ - name: Configure dnsdist
+ if: matrix.product == 'dnsdist'
+ working-directory: ./pdns/dnsdistdist/
+ run: |
+ inv ci-dnsdist-configure full
- name: Build dnsdist
if: matrix.product == 'dnsdist'
+ working-directory: ./pdns/dnsdistdist/
+ run: |
+ inv ci-dnsdist-make-bear
+ - run: ln -s ../../.clang-tidy.full .clang-tidy
+ if: matrix.product == 'dnsdist'
+ working-directory: ./pdns/dnsdistdist/
+ - name: Run clang-tidy for dnsdist
+ if: matrix.product == 'dnsdist'
+ working-directory: ./pdns/dnsdistdist/
+ run: git diff --no-prefix -U0 HEAD^..HEAD | python3 ../../.github/scripts/git-filter.py --product dnsdist | python3 ../../.github/scripts/clang-tidy-diff.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p0 -export-fixes clang-tidy-dnsdist.yml
+ - name: Print clang-tidy fixes YAML for dnsdist
+ if: matrix.product == 'dnsdist'
+ working-directory: ./pdns/dnsdistdist/
+ shell: bash
+ run: |
+ if [ -f clang-tidy-dnsdist.yml ]; then
+ cat clang-tidy-dnsdist.yml
+ fi
+ - name: Result annotations for dnsdist
+ if: matrix.product == 'dnsdist'
+ id: clang-tidy-annotations-dnsdist
+ working-directory: ./pdns/dnsdistdist/
+ shell: bash
run: |
- cd pdns/dnsdistdist
- autoreconf -vfi
- ./configure --enable-unit-tests --enable-dnstap --enable-dnscrypt --enable-dns-over-tls --enable-dns-over-https LIBS=-lwslay CFLAGS='-O0' CXXFLAGS='-O0'
- make -j8 -C ext/ipcrypt
- make -j8 -C ext/yahttp
- make -j4 dnsdist
+ if [ -f clang-tidy-dnsdist.yml ]; then
+ set +e
+ python3 ../../.github/scripts/clang-tidy.py --fixes-file clang-tidy-dnsdist.yml
+ echo "failed=$?" >> $GITHUB_OUTPUT
+ fi
- - name: Build recursor
+ - name: Install dependencies for rec
+ if: matrix.product == 'rec'
+ run: |
+ inv install-rec-build-deps
+ - run: inv ci-install-rust ${{ env.REPO_HOME }}
+ if: matrix.product == 'rec'
+ working-directory: ./pdns/recursordist/
+ - name: Autoreconf rec
+ if: matrix.product == 'rec'
+ working-directory: ./pdns/recursordist/
+ run: |
+ inv ci-autoconf
+ - name: Configure rec
+ if: matrix.product == 'rec'
+ working-directory: ./pdns/recursordist/
+ run: |
+ inv ci-rec-configure full
+ - name: Build rec
if: matrix.product == 'rec'
+ working-directory: ./pdns/recursordist/
run: |
- cd pdns/recursordist
- autoreconf -vfi
- ./configure --enable-unit-tests --enable-nod --enable-dnstap CFLAGS='-O0' CXXFLAGS='-O0'
- make -j8 -C ext
- make htmlfiles.h
- make -j4 pdns_recursor rec_control
+ CONCURRENCY=4 inv ci-rec-make-bear
+ - run: ln -s ../../.clang-tidy.full .clang-tidy
+ if: matrix.product == 'rec'
+ working-directory: ./pdns/recursordist/
+ - name: Run clang-tidy for rec
+ if: matrix.product == 'rec'
+ working-directory: ./pdns/recursordist/
+ run: git diff --no-prefix -U0 HEAD^..HEAD | python3 ../../.github/scripts/git-filter.py --product rec | python3 ../../.github/scripts/clang-tidy-diff.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p0 -export-fixes clang-tidy-rec.yml
+ - name: Print clang-tidy fixes YAML for rec
+ if: matrix.product == 'rec'
+ working-directory: ./pdns/recursordist/
+ shell: bash
+ run: |
+ if [ -f clang-tidy-rec.yml ]; then
+ cat clang-tidy-rec.yml
+ fi
+ - name: Result annotations for rec
+ if: matrix.product == 'rec'
+ id: clang-tidy-annotations-rec
+ working-directory: ./pdns/recursordist/
+ shell: bash
+ run: |
+ if [ -f clang-tidy-rec.yml ]; then
+ set +e
+ python3 ../../.github/scripts/clang-tidy.py --fixes-file clang-tidy-rec.yml
+ echo "failed=$?" >> $GITHUB_OUTPUT
+ fi
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
+
+ check-clang-tidy:
+ needs: analyze
+ runs-on: ubuntu-22.04
+ name: Check whether clang-tidy succeeded
+ steps:
+ - run: |
+ if [ "x${{ needs.analyze.outputs.clang-tidy-annotations-auth }}" != "x" -a "${{ needs.analyze.outputs.clang-tidy-annotations-auth }}" != "0" ]; then
+ echo "::error::Auth clang-tidy failed"
+ exit 1
+ fi
+ if [ "x${{ needs.analyze.outputs.clang-tidy-annotations-dnsdist }}" != "x" -a "${{ needs.analyze.outputs.clang-tidy-annotations-dnsdist }}" != "0" ]; then
+ echo "::error::DNSdist clang-tidy failed"
+ exit 1
+ fi
+ if [ "x${{needs.analyze.outputs.clang-tidy-annotations-rec }}" != "x" -a "${{needs.analyze.outputs.clang-tidy-annotations-rec }}" != "0" ]; then
+ echo "::error::Rec clang-tidy failed"
+ exit 1
+ fi
+
+ check-for-binaries:
+ runs-on: ubuntu-22.04
+ name: Force failure in case there are binaries present in a pull request
+ if: ${{ github.event_name == 'pull_request' }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+ - run: if [[ "$(file -i --dereference $(git diff --name-only HEAD^..HEAD -- . :^fuzzing/corpus) | grep binary | grep -v 'image/' | grep -v 'inode/x-empty' | grep -v 'inode/directory')" != "" ]]; then exit 1; fi
matrix:
product: ['auth', 'recursor', 'dnsdist']
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
jobs:
build-upload-docs:
name: Build and upload docs
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
steps:
- uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- run: build-scripts/gh-actions-setup-inv-no-dist-upgrade # this runs apt update
- run: inv install-doc-deps
- run: inv install-doc-deps-pdf
- id: get-version
- run: echo "pdns_version=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+ run: |
+ echo "pdns_version=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- id: setup-ssh
run: |-
working-directory: ./docs/_build
- run: tar cf auth-html-docs.tar auth-html-docs
working-directory: ./docs/_build
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: authoritative-html-docs-${{steps.get-version.outputs.pdns_version}}
path: ./docs/_build/auth-html-docs.tar
if: ${{github.ref_name == 'master'}}
working-directory: ./docs/_build
- run: inv ci-docs-build-pdf
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: PowerDNS-Authoritative-${{steps.get-version.outputs.pdns_version}}.pdf
path: ./docs/_build/latex/PowerDNS-Authoritative.pdf
if: ${{github.ref_name == 'master' && steps.setup-ssh.outputs.have_ssh_key != ''}}
# Rec
+ - run: inv ci-docs-rec-generate
+ working-directory: ./pdns/recursordist/settings
- run: inv ci-docs-build
working-directory: ./pdns/recursordist
- run: mv html rec-html-docs
working-directory: ./pdns/recursordist/docs/_build
- run: tar cf rec-html-docs.tar rec-html-docs
working-directory: ./pdns/recursordist/docs/_build
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: recursor-html-docs-${{steps.get-version.outputs.pdns_version}}
path: ./pdns/recursordist/docs/_build/rec-html-docs.tar
working-directory: ./pdns/recursordist/docs/_build
- run: inv ci-docs-build-pdf
working-directory: ./pdns/recursordist
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: PowerDNS-Recursor-${{steps.get-version.outputs.pdns_version}}.pdf
path: ./pdns/recursordist/docs/_build/latex/PowerDNS-Recursor.pdf
working-directory: ./pdns/dnsdistdist/docs/_build
- run: tar cf dnsdist-html-docs.tar dnsdist-html-docs
working-directory: ./pdns/dnsdistdist/docs/_build
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: dnsdist-html-docs-${{steps.get-version.outputs.pdns_version}}
path: ./pdns/dnsdistdist/docs/_build/dnsdist-html-docs.tar
working-directory: ./pdns/dnsdistdist/docs/_build
- run: inv ci-docs-build-pdf
working-directory: ./pdns/dnsdistdist
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: dnsdist-${{steps.get-version.outputs.pdns_version}}.pdf
path: ./pdns/dnsdistdist/docs/_build/latex/dnsdist.pdf
---
-name: 'Verify formatting and Makefile.am sort order'
+name: 'Verify source code formatting; check Makefile.am sort order'
on:
push:
jobs:
build:
- name: verify formatting and Makefile.am sort order
+ name: Verify source code formatting; check Makefile.am sort order
# on a ubuntu-20.04 VM
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
Fuzzing:
runs-on: ubuntu-20.04
steps:
+ - uses: actions/checkout@v4
+ 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:
fuzz-seconds: 600
dry-run: false
- name: Upload Crash
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: failure()
with:
name: artifacts
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 }}
if: ${{ vars.SCHEDULED_MISC_DAILIES }}
runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
coverity-auth:
name: coverity scan of the auth
if: ${{ vars.SCHEDULED_MISC_DAILIES }}
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
env:
COVERITY_TOKEN: ${{ secrets.coverity_auth_token }}
FUZZING_TARGETS: no
UNIT_TESTS: no
steps:
- uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
- - run: build-scripts/gh-actions-setup-inv # this runs apt update+upgrade
+ - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade
- run: inv install-clang
- run: inv install-auth-build-deps
- run: inv install-coverity-tools PowerDNS
coverity-dnsdist:
name: coverity scan of dnsdist
if: ${{ vars.SCHEDULED_MISC_DAILIES }}
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
env:
COVERITY_TOKEN: ${{ secrets.coverity_dnsdist_token }}
SANITIZERS:
UNIT_TESTS: no
steps:
- uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
- - run: build-scripts/gh-actions-setup-inv # this runs apt update+upgrade
+ - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade
- run: inv install-clang
- - run: inv install-dnsdist-build-deps
+ - run: inv install-dnsdist-build-deps --skipXDP
- run: inv install-coverity-tools dnsdist
- run: inv coverity-clang-configure
- run: inv ci-autoconf
working-directory: ./pdns/dnsdistdist/
+ - run: inv ci-build-and-install-quiche
+ working-directory: ./pdns/dnsdistdist/
- run: inv ci-dnsdist-configure full
working-directory: ./pdns/dnsdistdist/
- run: inv coverity-make
coverity-rec:
name: coverity scan of the rec
if: ${{ vars.SCHEDULED_MISC_DAILIES }}
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
env:
COVERITY_TOKEN: ${{ secrets.coverity_rec_token }}
SANITIZERS:
UNIT_TESTS: no
steps:
- uses: PowerDNS/pdns/set-ubuntu-mirror@meta
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
- - run: build-scripts/gh-actions-setup-inv # this runs apt update+upgrade
+ - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade
- run: inv install-clang
- run: inv install-rec-build-deps
- run: inv install-coverity-tools 'PowerDNS+Recursor'
- run: inv coverity-clang-configure
- run: inv ci-autoconf
working-directory: ./pdns/recursordist/
- - run: inv ci-rec-configure
+ - run: inv ci-rec-configure full
working-directory: ./pdns/recursordist/
- run: inv coverity-make
working-directory: ./pdns/recursordist/
# on a ubuntu-20.04 VM
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v3
+ - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+ - uses: actions/checkout@v4
with:
fetch-depth: 5
submodules: recursive
pull_request:
branches:
- "**"
- tags-ignore:
- - "**"
types:
- 'opened'
- 'reopened'
outputs:
followup: ${{ steps.spelling.outputs.followup }}
runs-on: ubuntu-latest
- if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'"
+ if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
concurrency:
group: spelling-${{ github.event.pull_request.number || github.ref }}
# note: If you use only_check_changed_files, you do not want cancel-in-progress
steps:
- name: check-spelling
id: spelling
- uses: check-spelling/check-spelling@v0.0.21
+ uses: check-spelling/check-spelling@v0.0.22
with:
config: .github/actions/spell-check
+ suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
checkout: true
- spell_check_this: check-spelling/spell-check-this@prerelease
+ spell_check_this: powerdns/pdns@master
post_comment: 0
+ warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check
use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }}
extra_dictionaries:
- cspell:software-terms/src/software-terms.txt
- cspell:python/src/python/python-lib.txt
- cspell:node/node.txt
+ cspell:software-terms/dict/softwareTerms.txt
+ cspell:node/dict/node.txt
cspell:python/src/common/extra.txt
- cspell:fullstack/fullstack.txt
- cspell:html/html.txt
+ cspell:php/dict/php.txt
+ cspell:python/src/python/python-lib.txt
+ cspell:golang/dict/go.txt
+ cspell:fullstack/dict/fullstack.txt
+ cspell:k8s/dict/k8s.txt
cspell:aws/aws.txt
- cspell:npm/npm.txt
cspell:cpp/src/stdlib-cpp.txt
+ cspell:filetypes/filetypes.txt
cspell:python/src/python/python.txt
- cspell:django/django.txt
+ cspell:django/dict/django.txt
+ cspell:typescript/dict/typescript.txt
+ cspell:dotnet/dict/dotnet.txt
+ cspell:html/dict/html.txt
+ cspell:cpp/src/lang-keywords.txt
+ cspell:lua/dict/lua.txt
+ cspell:latex/dict/latex.txt
check_extra_dictionaries: ''
./ext/lmdb-safe/lmdb-typed.hh
./ext/probds/murmur3.cc
./pdns/anadns.hh
-./pdns/arguments.cc
-./pdns/arguments.hh
./pdns/auth-caches.cc
./pdns/auth-carbon.cc
./pdns/auth-packetcache.cc
./pdns/axfr-retriever.hh
./pdns/backends/gsql/gsqlbackend.cc
./pdns/backends/gsql/gsqlbackend.hh
-./pdns/backends/gsql/ssql.hh
./pdns/base32.cc
./pdns/base64.cc
./pdns/base64.hh
./pdns/cdb.hh
./pdns/comfun.cc
./pdns/comment.hh
-./pdns/communicator.cc
-./pdns/communicator.hh
./pdns/dbdnsseckeeper.cc
./pdns/delaypipe.cc
./pdns/delaypipe.hh
-./pdns/digests.hh
./pdns/distributor.hh
./pdns/dns.cc
./pdns/dns.hh
-./pdns/dns_random.cc
-./pdns/dns_random.hh
-./pdns/dnsbackend.cc
-./pdns/dnsbackend.hh
./pdns/dnsbulktest.cc
./pdns/dnscrypt.cc
./pdns/dnscrypt.hh
./pdns/dnsdemog.cc
-./pdns/dnsdist-cache.cc
-./pdns/dnsdist-cache.hh
-./pdns/dnsdist-console.cc
-./pdns/dnsdist-console.hh
-./pdns/dnsdist-dynblocks.hh
-./pdns/dnsdist-dynbpf.cc
-./pdns/dnsdist-dynbpf.hh
-./pdns/dnsdist-ecs.cc
-./pdns/dnsdist-ecs.hh
-./pdns/dnsdist-lbpolicies.hh
-./pdns/dnsdist-lua-actions.cc
-./pdns/dnsdist-lua-bindings-dnsquestion.cc
-./pdns/dnsdist-lua-bindings.cc
-./pdns/dnsdist-lua-inspection.cc
-./pdns/dnsdist-lua-rules.cc
-./pdns/dnsdist-lua-vars.cc
-./pdns/dnsdist-lua.hh
-./pdns/dnsdist-protobuf.cc
-./pdns/dnsdist-protobuf.hh
-./pdns/dnsdist-rings.cc
-./pdns/dnsdist-rings.hh
-./pdns/dnsdist-snmp.cc
-./pdns/dnsdist-snmp.hh
-./pdns/dnsdist-tcp.cc
-./pdns/dnsdist-web.cc
-./pdns/dnsdist-xpf.cc
-./pdns/dnsdist-xpf.hh
-./pdns/dnsdist.cc
-./pdns/dnsdist.hh
./pdns/dnsdistdist/connection-management.hh
./pdns/dnsdistdist/dnsdist-backend.cc
-./pdns/dnsdistdist/dnsdist-dynblocks.cc
-./pdns/dnsdistdist/dnsdist-healthchecks.cc
-./pdns/dnsdistdist/dnsdist-healthchecks.hh
./pdns/dnsdistdist/dnsdist-kvs.cc
./pdns/dnsdistdist/dnsdist-kvs.hh
./pdns/dnsdistdist/dnsdist-lbpolicies.cc
./pdns/dnsdistdist/dnsdist-lua-bindings-protobuf.cc
./pdns/dnsdistdist/dnsdist-lua-ffi.cc
./pdns/dnsdistdist/dnsdist-lua-ffi.hh
-./pdns/dnsdistdist/dnsdist-lua-inspection-ffi.hh
./pdns/dnsdistdist/dnsdist-lua-web.cc
./pdns/dnsdistdist/dnsdist-prometheus.hh
./pdns/dnsdistdist/dnsdist-rules.hh
./pdns/dnspcap.hh
./pdns/dnspcap2calidns.cc
./pdns/dnspcap2protobuf.cc
-./pdns/dnsproxy.cc
-./pdns/dnsproxy.hh
./pdns/dnsrecords.cc
./pdns/dnsrecords.hh
./pdns/dnsreplay.cc
./pdns/dnssecinfra.hh
./pdns/dnsseckeeper.hh
./pdns/dnssecsigner.cc
-./pdns/dnstap.cc
-./pdns/dnstap.hh
./pdns/dnstcpbench.cc
./pdns/dnswasher.cc
./pdns/dnswriter.cc
./pdns/dnswriter.hh
./pdns/doh.hh
-./pdns/dolog.hh
./pdns/dumresp.cc
./pdns/dynhandler.cc
./pdns/dynhandler.hh
./pdns/ednsoptions.cc
./pdns/ednsoptions.hh
./pdns/ednspadding.cc
-./pdns/ednssubnet.cc
-./pdns/ednssubnet.hh
./pdns/fstrm_logger.cc
./pdns/fstrm_logger.hh
-./pdns/fuzz_dnsdistcache.cc
-./pdns/fuzz_moadnsparser.cc
-./pdns/fuzz_packetcache.cc
-./pdns/fuzz_proxyprotocol.cc
-./pdns/fuzz_zoneparsertng.cc
./pdns/gettime.cc
./pdns/gettime.hh
./pdns/histog.hh
./pdns/lua-record.cc
./pdns/malloctrace.cc
./pdns/malloctrace.hh
-./pdns/mastercommunicator.cc
+./pdns/auth-primarycommunicator.cc
./pdns/minicurl.cc
./pdns/minicurl.hh
./pdns/misc.cc
./pdns/misc.hh
-./pdns/mtasker.cc
-./pdns/mtasker.hh
./pdns/nameserver.cc
./pdns/nameserver.hh
./pdns/namespaces.hh
./pdns/signingpipe.cc
./pdns/signingpipe.hh
./pdns/sillyrecords.cc
-./pdns/slavecommunicator.cc
./pdns/snmp-agent.cc
./pdns/snmp-agent.hh
-./pdns/sodcrypto.cc
-./pdns/sodcrypto.hh
./pdns/sortlist.cc
./pdns/sortlist.hh
./pdns/speedtest.cc
-./pdns/ssqlite3.cc
-./pdns/ssqlite3.hh
./pdns/sstuff.hh
./pdns/standalone_fuzz_target_runner.cc
./pdns/stat_t.hh
./pdns/statnode.cc
./pdns/statnode.hh
./pdns/stubquery.cc
-./pdns/stubresolver.cc
./pdns/svc-records.cc
./pdns/svc-records.hh
./pdns/tcpiohandler.cc
./pdns/test-base32_cc.cc
./pdns/test-base64_cc.cc
./pdns/test-common.hh
-./pdns/test-digests_hh.cc
./pdns/test-distributor_hh.cc
-./pdns/test-dns_random_hh.cc
./pdns/test-dnscrypt_cc.cc
-./pdns/test-dnsdist_cc.cc
-./pdns/test-dnsdistpacketcache_cc.cc
./pdns/test-dnsname_cc.cc
./pdns/test-dnsparser_cc.cc
./pdns/test-dnsparser_hh.cc
./pdns/test-packetcache_hh.cc
./pdns/test-proxy_protocol_cc.cc
./pdns/test-rcpgenerator_cc.cc
-./pdns/test-sha_hh.cc
./pdns/test-sholder_hh.cc
./pdns/test-statbag_cc.cc
./pdns/test-svc_records_cc.cc
./pdns/tsigutils.hh
./pdns/tsigverifier.cc
./pdns/tsigverifier.hh
-./pdns/ueberbackend.cc
-./pdns/ueberbackend.hh
./pdns/unix_semaphore.cc
./pdns/unix_utility.cc
./pdns/utility.hh
./pdns/version.hh
./pdns/webserver.cc
./pdns/webserver.hh
-./pdns/ws-api.cc
-./pdns/ws-api.hh
-./pdns/ws-auth.cc
-./pdns/ws-auth.hh
./pdns/xpf.cc
./pdns/xpf.hh
./pdns/zone2json.cc
--- /dev/null
+Building packages
+=================
+
+PowerDNS uses the pdns-builder tool to generate packages for its products. The actual workflow can be found in the [builder-support](https://github.com/PowerDNS/pdns/tree/master/builder-support) directory of the git repository.
+The [build-tags.yml](https://github.com/PowerDNS/pdns/blob/master/.github/workflows/build-tags.yml) workflow automatically builds packages when a tag is pushed, so there is no need to trigger a manual build for releases, and actually doing so would be worse from a provenance point of view where full automation is always better.
+
+Building packages on your own computer
+--------------------------------------
+
+This requires a working Docker installation.
+
+1. Clone our git repo (`git clone https://github.com/PowerDNS/pdns.git`)
+2. Check out the version you want, it can be a git tag like dnsdist-1.8.1, a git commit ID or branch
+3. Update submodules (`git submodule update --init --recursive`)
+4. Execute `builder/build.sh` to see what arguments it supports
+5. Then run `builder/build.sh` with the arguments you want (for example, `builder/build.sh -m recursor debian-bookworm`)
+
+Building packages from GitHub actions
+-------------------------------------
+
+You can build packages from your own fork of the PowerDNS repository. Go to the [PowerDNS/pdns](https://github.com/PowerDNS/pdns) repository and click on `Fork` at the top right of the screen. When asked if you would like to only copy the master branch, say no, as otherwise you will not be able to build packages from tagged releases. If you have already done so and have not done any modification to your fork, the easiest way is to delete and recreate it.
+
+On your fork, go to the `Actions` tab. You will be greeted by a message stating `Workflows aren’t being run on this forked repository`. You can click `I understand my workflows, go ahead and enable them`.
+
+Please be aware that by default some of the workflows are executed once every day, and enabling them will consume billing time our of your GitHub actions quota, although at the moment GitHub disables these by default: `This scheduled workflow is disabled because scheduled workflows are disabled by default in forks`.
+
+On the left side, click on `Trigger specific package build`.
+
+Locate the `Run workflow` dropdown item on the top right side of the screen, inside the blue region stating `This workflow has a workflow_dispatch event trigger.` It will open a menu with several options:
+- `Branch`: you can keep `master` here, unless you need to build for an operating system which is not in the list, in which case you will have to create a new branch and add the required file(s) for this OS. See `Adding a new OS` below.
+- `Product to build`: select the product you want to build packages for, for example `dnsdist`
+- `OSes to build for, space separated`: keep one or more OSes you want to build packages for, for example `ubuntu-focal`
+- `git ref to checkout`: the exact version you want to build. It can be the name of branch, a git tag or a git commit ID. Most likely you will be willing to build from a tagged release, like `dnsdist-1.8.1`.
+- `is this a release build?`: Keep `NO`
+
+Click `Run workflow` to start the build.
+
+If you reload the page, you should now see your build in progress as a `Trigger specific package build` workflow run. It will take some time to finish, but you can look at the progress by clicking on it.
+
+Once it's done, you can retrieve the generated package in the list of artifacts on the `Summary` page of the workflow run, by clicking on the `Summary` link on the top right of the screen.
+
+Adding a new OS to the list
+---------------------------
+
+Adding a new OS is usually easy, provided that it does not differ too much from an existing one. For example, to add support for Debian Bookworm (already present in the current repository), one had to:
+
+Copy the existing instructions for Debian Buster:
+```
+cp builder-support/dockerfiles/Dockerfile.target.debian-buster builder-support/dockerfiles/Dockerfile.target.debian-bookworm
+```
+
+In the new `builder-support/dockerfiles/Dockerfile.target.debian-bookworm` file, replace every occurence of `debian-buster` by `debian-bookworm`, and of `debian:buster` by `debian:bookworm`
+
+Create symbolic links for the amd64 and arm64 versions:
+```
+ln -s builder-support/dockerfiles/Dockerfile.target.debian-bookworm builder-support/dockerfiles/Dockerfile.target.debian-bookworm-amd64
+ln -s builder-support/dockerfiles/Dockerfile.target.debian-bookworm builder-support/dockerfiles/Dockerfile.target.debian-bookworm-arm64
+```
+
+Then add the new target to the list of OSes in the `.github/workflows/builder-dispatch.yml` workflow file:
+```
+default: >-
+ el-7
+ el-8
+ el-9
+ debian-buster
+ debian-bullseye
+ debian-bookworm
+ ubuntu-focal
+ ubuntu-jammy
+```
+
+If release packages should be automatically built for this new target, then `.github/workflows/build-packages.yml` has to be updated as well:
+``
+```
+default: >-
+ el-7
+ el-8
+ el-9
+ debian-buster
+ debian-bullseye
+ debian-bookworm
+ ubuntu-focal
+ ubuntu-jammy
+```
+
+Not forgetting to update the list of hashes later in the same file:
+```
+pkghashes-el-7: ${{ steps.pkghashes.outputs.pkghashes-el-7 }}
+pkghashes-el-8: ${{ steps.pkghashes.outputs.pkghashes-el-8 }}
+pkghashes-el-9: ${{ steps.pkghashes.outputs.pkghashes-el-9 }}
+pkghashes-debian-buster: ${{ steps.pkghashes.outputs.pkghashes-debian-buster }}
+pkghashes-debian-bullseye: ${{ steps.pkghashes.outputs.pkghashes-debian-bullseye }}
+pkghashes-debian-bookworm: ${{ steps.pkghashes.outputs.pkghashes-debian-bookworm }}
+pkghashes-ubuntu-focal: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-focal }}
+pkghashes-ubuntu-jammy: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-jammy }}
+```
--- /dev/null
+Code Coverage
+-------------
+
+PowerDNS uses [coveralls](https://coveralls.io/) to generate code coverage reports from our Continuous Integration tests. The resulting analysis can then be consulted [online](https://coveralls.io/github/PowerDNS/pdns), and gives insight into which parts of the code are automatically tested.
+
+Code coverage is generated during our Continuous Integration tests, for every pull request. In addition to the dashboard on Coveralls' website, a summary is posted on pull requests.
+
+# Technical Details
+
+## DebugInfo vs Source-based Code Coverage
+
+There are two main ways of generating code coverage: `GCOV` and `source-based`.
+
+### GCOV
+
+The `GCOV` approach, supported by both `g++` and `clang++`, is enabled by passing the `--coverage` flag (equivalent to `-ftest-coverage -fprofile-arcs`) to the compiler and linker. It operates on debugging information (`DebugInfo`), usually [DWARF](https://dwarfstd.org/), generated by the compiler, and also used by debuggers.
+This approach generates `.gcno` files during the compilation, which are stored along the object files, and `.gcda` files at runtime when the final program is executed.
+
+* There are as many `.gcno` and `.gcda` files as object files, which may be a lot.
+* Every invocation of a program updates the `.gcda` files corresponding to the code that has been executed. It will append to existing `.gcda` files, but only process can update a given file so parallel execution will result in corrupted data.
+* Writing to each `.gcda` might take a while for large programs, and has been known to slow down execution quite a lot.
+* Accurate reporting of lines and branches may be problematic when optimizations are enabled, so it is advised to disable optimizations to get useful analysis.
+* Note that the `.gcda` files produced by `clang++` are not fully compatible with the `g++` ones, and with the existing tools, but [`llvm-cov gcov`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-gcov) can produce `.gcov` files that should be compatible. A symptom of this incompatiblity looks like this:
+
+```
+Processing pdns/ednssubnet.gcda
+__w/pdns/pdns/pdns/ednssubnet.gcno:version '408', prefer 'B02'
+```
+
+### Source Based
+
+`clang++` supports [source-based coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html), which operates on `AST` and preprocessor information directly. This is enabled by passing `-fprofile-instr-generate -fcoverage-mapping` to the compiler and leads to `.profraw` files being produced when the binary is executed.
+The `.profraw` file(s) can be merged by [`llvm-profdata merge`](https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge) into a `.profdata` file which can then be used by [`llvm-cov show`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-show) to generate HTML and text reports, or by [`llvm-cov export`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-export) to export `LCOV` data that is compatible with other tools.
+
+* Source-based coverage can generate accurate data with optimizations enabled, and has a much lower overhead that `GCOV`.
+* The path and exact name of the `.profraw` files generated when a program is executed can be controlled via the `LLVM_PROFILE_FILE` environment variable, which supports [patterns](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#running-the-instrumented-program) like `%p`, which expands to the process ID. That allows running several programs in parallel, each program generating its own file at the end.
+
+## Implementation
+
+We use `clang++`'s source-based coverage method in our CI, as it allows running our regression tests in parallel with several workers. It is enabled by passing the `--enable-coverage=clang` flag during `configure` for all products.
+The code coverage generation is done as part of the [build-and-test-all.yml](https://github.com/PowerDNS/pdns/blob/master/.github/workflows/build-and-test-all.yml) workflow.
+
+Since we have a `monorepo` for three products which share the same code-base, the process is a bit tricky:
+
+* We use coveralls's `parallel` feature, which allows us to generate partial reports from several steps of our CI process, then merge them during the `collect` phase and upload the resulting `LCOV` file to coveralls.
+* After executing our tests, the `generate_coverage_info` method in [`tasks.py`](https://github.com/PowerDNS/pdns/blob/master/tasks.py) merges the `.profraw` files that have been generated every time a binary has been executed into a single `.profdata` file via [`llvm-profdata merge`](https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge). We enable the `sparse` mode to get a smaller `.profdata` file, since we do not do Profile-Guided Optimization (PGO).
+* It then generates a `.lcov` file from the `.profdata` via [`llvm-cov export`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-export), telling it to ignore reports for files under `/usr` in the process (via the `-ignore-filename-regex` parameter).
+* We then normalize the paths of the source files to prevent duplicates for files that are used by more than one product, and to account for the fact that our CI actually compiles from a `distdir`. This is handled by a Python script, [.github/scripts/normalize_paths_in_coverage.py](https://github.com/PowerDNS/pdns/blob/master/.github/scripts/normalize_paths_in_coverage.py) that parses the `LCOV` data and updates the paths.
+* We call [Coveralls's github action](https://github.com/coverallsapp/github-action) to upload the resulting `LCOV` data for this step.
+* After all steps have completed, we call that action again to let it know that our workflow is finished and the data can be consolidated.
+
+One important thing to remember is that the content is only written into a `.profraw` file is the program terminates correctly, calling `exit` handlers, and if the `__llvm_profile_write_file()` function is called. Our code base has a wrapper around that, `pdns::coverage::dumpCoverageData()`.
+This is especially important for us because our products often terminates by calling `_exit()`, bypassing the `exit` handlers, to avoid issues with the destruction order of global objects.
+
+## Generating Coverage Outside Of the CI
+
+It is possible to generate a code coverage report without going through the CI, for example to test the coverage of a new feature in a given product.
+
+### Source-based Coverage With clang++
+
+* Run the `configure` script with the `--enable-coverage=clang` option, setting the `CC` and `CXX` environment variables to use the `clang` compiler: `CC=clang CXX=clang++ ./configure --enable-coverage=clang`
+* Compile the product as usual with: `make`
+* Run the test(s) that are expected to cover the new feature, via `./testrunner` or `make check` for the unit tests, and the instructions of the corresponding `regression-tests*` directory for the regression tests. It is advised to set the `LLVM_PROFILE_FILE` environment variable in such a way that an invocation of the product do not override the results from the previous invocation. For example setting `LLVM_PROFILE_FILE="/tmp/code-%p.profraw"` will result in each invocation writing a new file into the `/tmp` directory, replacing `%p` with the process ID.
+* Merge the resulting `*.profraw` file into a single `code.profdata` file by running `llvm-profdata merge -sparse -o /tmp/code.profdata /tmp/code-*.profraw`
+* Generate a HTML report into the `/tmp/html-report` directory by running `llvm-cov show --instr-profile /tmp/code.profdata -format html -output-dir /tmp/html-report -object </path/to/product/binary>`
+
+### GCOV
+
+* Run the `configure` script with the `--enable-coverage` option, using either `g++` or `clang++`: `./configure --enable-coverage`
+* Compile as usual with: `make`. This will generate `.gcno` files along with the usual `.o` object files and the final binaries.
+* Run the test(s) that are expected to cover the new feature, via `./testrunner` or `make check` for the unit tests, and the instructions of the corresponding `regression-tests*` directory for the regression tests. Note that the regression should not be run in parallel, as it would corrupt the `.gcna` files that will be generated in the process. For dnsdist, that means running `pytest` without the `--dist=loadfile -n auto` options.
+* Generate a HTML report using `gcovr`, or `gcov` then `lcov`
+
+# Remaining Tasks
+
+The way our code coverage report is generated does not currently handle the different authoritative server tools (that end up in the `pdns-tools` package) very well. Consequently the coverage report for these tools, and the related code parts, is not accurate.
+It is likely possible to pass several `--object </path/to/binary>` options to `llvm-cov` when processing the `.profdata` file.
--- /dev/null
+Coding Guidelines for Contributing to PowerDNS
+----------------------------------------------
+
+Thank you for you interest in contributing to the PowerDNS project.
+This document describes the general coding guidelines to keep in mind when contributing code to our code base.
+It does assume that you have already read the contributing document at [CONTRIBUTING.md](https://github.com/PowerDNS/pdns/blob/master/CONTRIBUTING.md).
+
+# High-level Guidelines
+
+* Although the codebase does not consistently have them, [docblocks](https://www.doxygen.nl/manual/docblocks.html) on functions and classes are appreciated.
+* Never hesitate to write comments on anything that might not be immediately clear just from reading the code.
+* When adding whole new things, consider putting them in a `pdns::X` namespace.
+ Look for `namespace pdns` in the codebase for examples.
+
+# Memory Handling
+
+The memory model in C++, inherited from the C era, is very powerful but also very error-prone.
+Several features are available in modern C++ (11 and up) to make it possible to avoid most of the pitfalls, while conserving the same level of performance.
+
+Most of the issues related to memory allocation (memory leaks, use-after-free) can be solved by using standard containers, or taking advantage of RAII and smart pointers, which take care of destroying objects when it is not used anymore.
+
+## Stack-based Memory Allocation
+
+Default allocations, when declaring a variable local to a function for example, are done on the stack instead of doing a dynamic allocation on the heap.
+Allocating objects on the stack is faster, especially in threaded programs, and provides the benefit that objects are automatically destroyed when the function exits.
+
+One caveat that the programmer needs to be aware of is the size of the object in order to not exceed the space available on the stack, which would corrupt other objects in memory and could lead to a crash, or even execution of arbitrary code.
+This is especially true in the Recursor which uses a custom mechanism for stack-switching in user-space and thus has a reduced stack size.
+
+### Variable-Length Arrays (VLAs)
+
+In order to avoid smashing the stack, special care should be taken to limit the depth of function calls that, for example, can grow quickly with recursion.
+A second common source of stack smashing is the use of Variable-Length Arrays (VLAs), whose size is determined at runtime and is therefore very hard to predict.
+The C++ language does not support VLAs but a lot of compilers inherit such support from C99, so it is possible to use them by accident.
+PowerDNS strictly forbids the use of VLAs, as does the Linux kernel, and enforces that with the `-Werror=vla` compiler flag.
+
+### C-style Arrays
+
+While you might still find some uses of C-style arrays in the existing code base, we are actively trying to get rid of them. One example is as follows:
+
+```C++
+somestruct buffer[12];
+auto bufferSize = sizeof(buffer) / sizeof(*buffer);
+auto& firstElement = buffer[0];
+```
+
+It is immediately obvious that computing the actual number of elements is error-prone, because `sizeof()` does not return the number of elements but the total memory space used by the array.
+Another obvious issue is that accesses to the array are not bound-checked.
+These are not the only drawbacks of C-style arrays, but are bad enough already to justify getting rid of them.
+
+The modern C++ way is to use `std::array`s:
+
+```C++
+std::array<somestruct, 12> buffer;
+auto bufferSize = buffer.size();
+auto& firstElement = buffer.at(0);
+```
+
+### `alloca`
+
+The use of `alloca()` is forbidden in the code base because it is too easy to smash the stack.
+
+## Resource Acquisition Is Initialization (RAII)
+
+Resource acquisition is initialization ([RAII](https://en.cppreference.com/w/cpp/language/raii)) is one of the fundamental concepts in C++.
+Resources are allocated during the construction of an object and destroyed when the object is itself destructed.
+It means that if an object is correctly designed, the resources associated with it cannot survive its lifetime. In other words, the resources associated with a correctly designed object are owned by the object and cannot outlive it.
+Since stack-allocated objects, like local variables in a function, are automatically destroyed when a function exits, be it by reaching the last line, calling return or throwing an exception, it makes it possible to ensure that resources are always properly destroyed by wrapping them in an object.
+
+We describe the use of smart pointers, containers and other wrappers for that purpose below, but first a few words of caution.
+Resources stored in a object are only tied to this object if the constructor executes fully and completes properly.
+If an exception is raised in the constructor's body, the object is not created and therefore the destructor will not be called.
+This means that if the object has non-object members holding resources, like raw file descriptors or raw C-style pointers, they need to be explicitly released before raising the exception, otherwise they are lost or leaked.
+
+```C++
+class BadFileDescriptorWrapper
+{
+ BadFileDescriptorWrapper()
+ {
+ d_fd = open(...);
+ if (something) {
+ throw std::runtime_error(...); // WRONG, DO NOT DO THIS!
+ }
+ ...
+ }
+
+ ~BadFileDescriptorWrapper()
+ {
+ if (d_fd > 0) {
+ close(d_fd);
+ d_fd = -1;
+ }
+ }
+
+ int getHandle() const
+ {
+ return d_fd;
+ }
+
+private:
+ int d_fd{-1};
+};
+```
+
+The use of smart pointers can be a solution to most resource leakage problems, but otherwise the only way is to be careful about exceptions in constructors:
+
+```C++
+GoodFileDescriptorWrapper()
+{
+ d_fd = open(...);
+ if (something) {
+ close(d_fd);
+ d_fd = -1;
+ throw std::runtime_error(...);
+ }
+ ...
+}
+```
+
+## Smart Pointers
+
+There is almost no good reason to not use a smart pointer when doing dynamic memory allocation.
+Smart pointers will keep track of whether the dynamically allocated object is still used, and destroy it when the last user goes away.
+
+Using raw pointers quickly results in security issues, ranging from memory leaks to arbitrary code execution.
+Examples of such issues can be found in the following PowerDNS security advisories:
+
+* [2017-07: Memory leak in DNSSEC parsing](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2017-07.html)
+* [2018-04: Crafted answer can cause a denial of service](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-04.html)
+
+Most allocations should be wrapped in a `std::unique_ptr`, using `make_unique`.
+There can only be one owner at any given time, as opposed to shared pointers, but the ownership can be passed along using `std::move()` if needed.
+
+If the dynamically allocated object needs to be referenced in several places, the use of a `std::shared_ptr` is advised instead, via `std::make_shared`.
+
+The use of `make_*` methods has three advantages:
+
+* They result in a single allocation for `shared_ptr`s, instead of two otherwise ;
+* They avoid duplicating the type name ;
+* They prevent a possible issue if an exception is raised with temporaries.
+
+They also make is easier to spot raw pointers by searching or `grep`ping for "new" and "delete" throughout the code :)
+
+Please note, however, that while unique pointers are as cheap as raw pointers, shared pointers are much more expensive.
+That is because they need to use atomic operations to update their internal counters, so making a copy of a shared pointer is expensive.
+Passing one by reference is cheap, however.
+
+### Shared Pointers
+
+An important thing to be aware of with shared pointers is that making a new copy or releasing a shared pointer, thus updating its internal reference counter, is atomic and therefore thread-safe.
+Altering the content of the object pointed to is not, though, and is subject to the usual locking methods.
+The often misunderstood part is that updating the target of the shared pointer is not thread-safe.
+Basically, you can copy the shared pointer from multiple threads at once, and then each thread can assign a new target to its own copy safely, like this:
+
+```C++
+auto ptr = std::make_shared<int>(4);
+for (auto idx = 0; idx < 10 ; idx++){
+ std::thread([ptr]{ auto copy = ptr; }).detach(); // ok, only mutates the control block
+}
+```
+
+But there is a race if one thread updates the exact same smart pointer that another thread is trying to read:
+
+```c++
+auto ptr = std::make_shared<int>(4);
+
+std::thread threadA([&ptr]{
+ ptr = std::make_shared<int>(10);
+});
+
+std::thread threadB([&ptr]{
+ ptr = std::make_shared<int>(20);
+});
+```
+
+That unfortunately means that we still need some locking with shared pointers.
+C++11 defines atomic compare/exchange operations for `std::shared_ptr`, but they are implemented in `libstdc++` by global mutexes and are therefore not lock-free.
+
+### Wrapping C Pointers
+
+Smart pointers can also be used to wrap C-pointers, such as `FILE*` pointers:
+
+```c++
+auto fp = std::unique_ptr<FILE, decltype(&std::fclose)>(fopen(certificateFile.c_str(), "r"), std::fclose);
+```
+
+It also works with types from external C libraries, like OpenSSL:
+
+```c++
+auto cert = std::unique_ptr<X509, decltype(&X509_free)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
+```
+
+Unfortunately there are a few cases where smart pointers cannot be used.
+In the PowerDNS products, these cases have been mostly reduced to a few select classes, like the `pdns::channel` ones, that are used to pass pointers to a different thread by writing them to a pipe, as is done for example by the query distributors of the auth and the rec.
+
+When smart pointers cannot be used, special care should be taken to:
+
+* Make sure that every exit point frees the allocated memory (early return, goto, exceptions..) ;
+* Set the pointer to `nullptr` right after the deallocation, so we can avoid use-after-free vulnerabilities and crash the program instead ;
+* Do not mix `malloc` with `delete`, or `new` with `free` (destructors are, at the very least, not run in such cases) ;
+* Do not mix array allocations (`new[]`) with a non-array `delete` (vs `delete[]`).
+
+## Pointer Arithmetic
+
+It is very common to use pointer arithmetic to calculate a position in a buffer, or to test whether a given offset is outside of a given buffer.
+Unfortunately it is quite easy to trigger undefined behaviour when doing so because the C++ standard does not allow pointer arithmetic pointing inside an object, except for arrays where it is also permitted to point one element past the end.
+Still, that undefined behaviour is mostly harmless, but it might lead to real issue on some platforms.
+
+One such example occurred in dnsdist: [2017-01: Crafted backend responses can cause a denial of service](https://dnsdist.org/security-advisories/powerdns-advisory-for-dnsdist-2017-01.html)
+
+In that case, a pointer was set to the start of a buffer plus a given length, to see whether the result would go past another pointer that was set to the end of the buffer.
+Unfortunately, if the start of the buffer is at a very high virtual address, the result of the addition might overflow and wrap around, causing the check to become true and leading to either a crash or the reading of unrelated memory.
+While very unlikely on a 64 bits platform, it could happen on 32 bits platform.
+
+This kind of issue is best avoided by the use of containers to avoid the need for pointer arithmetic, or by being very careful to only add checked offsets to a pointer.
+
+### Containers
+
+The use of containers like `vector`, `map` or `set` has several advantages in terms of security:
+
+* Memory allocations are handled by the container itself ;
+* It prevents a disconnect between the actual size and the variable tracking that size ;
+* It provides safe (and fast) operations like comparisons, iterators, etc..
+
+One issue that could have been prevented by the use of a container can be found in the following advisory: [2018-09: Crafted query can cause a denial of service](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-09.html)
+
+The use of a container and its corresponding `at()` operator would have prevented an out-of-bounds read since calling `at()` on an invalid offset results in an exception being raised.
+The cost of using `at()` is negligible for most use cases, and can be avoided by using the `[]` operator in the rare case when the cost cannot be afforded.
+Note that several Linux distributions now build with `-Wp,-D_GLIBCXX_ASSERTIONS` enabled by default, which turns on cheap range checks for C++ arrays, vectors, and strings.
+
+Regarding performance, it is advised to [`reserve()`](https://en.cppreference.com/w/cpp/container/vector/reserve) the needed size in advance when a rough estimate is known to avoid reallocations and copies. It usually triggers the allocation of enough memory to hold the requested number of items but does not increase the size of the container as reported by `size()`.
+Calling [`resize()`](https://en.cppreference.com/w/cpp/container/vector/resize) in advance is not advised, though, as it makes it harder to exactly know what is in the container in case of early returns or exceptions.
+
+In C++11, move operators make it possible to cheaply get the contents of a container into a different variable if needed.
+
+The need to pass a subset of a container without copying it often leads to passing a pointer to an array of chars along with a size.
+Introduced in C++14, `views` provide a nice way to borrow the content of a container to pass it to a function, without any copying or dynamic memory allocation. The basic `string_view` class provides that feature for a container of chars.
+
+# Threads and Concurrency
+
+All of our products use threading to be able to take advantage of the increasing number of cores on modern CPUs.
+This inevitably leads to the question of how to synchronise data accesses between threads.
+Most objects, like containers, cannot be accessed from more than one thread at once.
+Even `const` methods on containers might not be thread-safe.
+For example getting the `size()` of a container might not be thread-safe if a different thread might be writing to the container.
+Some functions might also not be thread-safe, for example if they have a static non-const variable.
+
+We currently use three solutions, depending on the use-case.
+The first one is used when we only need to share some kind of counter or gauge, and involves the use of `std::atomic` which allows atomic operations to be performed from different threads without locking. More on that later.
+The second one is the "share nothing" approach, where each thread has its own data (using `thread_local`, for example), avoiding the need for data synchronization.
+When a thread needs to communicate with another one, it might use a `pdns::channel` to pass a pointer to that second thread.
+That works quite well but sometimes sharing data is much more efficient than the alternative.
+
+For cases where sharing the data between threads is needed, we use the classic locking approach, using either a simple mutex or read-write lock, depending on the use case.
+
+## Locks
+
+Locks allow a thread of execution to ensure that no other thread will try to access the code path or data they protect at the same time.
+
+There are a few pitfalls to avoid when using locks:
+
+* Failing to release a lock, which can be avoided by using wrappers like `std::lock_guard`, `std::unique_lock` and our own wrappers: `LockGuarded` and `SharedLockGuarded` in `lock.hh` ;
+* High contention, where threads are blocked for a long time while waiting to acquire a lock.
+ This can be solved by carefully examining the portion of code that really needs to hold the lock, making the critical path shorter or faster, or by using sharding which basically divides the data protected by the lock into several pieces, each of them protected by its own lock ;
+* Dead-locks, which occur for example when thread 1 acquires lock 1 and wants to acquire lock 2, which is already acquired by thread 2, itself currently waiting to acquire lock 1.
+ This can be avoided by a better design of the locking mechanism, and assuring that locks are always acquired in the same order if more than one lock is required. Abstracting multiple locks away into a class with a small state machine that locks and unlocks both in the correct sequence and checks that they are always in a valid in-tandem state may prove to be a less error-prone approach while also improving readability and ergonomics.
+
+There are several types of locks:
+
+* Spinlocks are very fast but are busy-waiting, meaning that they do not pause, but instead repetitively try to get hold of the lock, using 100% of one core, doing so unless preempted by the OS ;
+ So they are only suited for locks that are almost never contented ;
+* A mutex is a very simple lock.
+ In most implementations it is a very fast lock, implemented in user-space on recent Linux kernels and glibc ;
+* A read-write lock (RW-lock) allows several threads to acquire it in read mode, but only one thread can acquire it in write mode.
+ This is suited when most accesses are read-only and writes are rare and do not take too long.
+ Otherwise, a mutex might actually be faster.
+
+One quick word about condition variables, that allow a thread to notify one or more threads waiting for a condition to happen.
+A thread should acquire a mutex using a `std::unique_lock` and call the `wait()` method of the condition variable.
+This is a very useful mechanism but one must be careful about two things:
+
+* The producer thread can either wake only one thread or all threads waiting on the condition variable.
+ Waking up several threads if only one has something to do (known as a "thundering herd") is bad practice, but there are some cases where it makes sense ;
+* A consumer thread might be waken up spuriously, which can be avoided by passing a predicate (which can be as simple as a small lambda function) to `wait()`.
+
+Our wrappers, `LockGuarded`, `SharedLockGuarded` in `lock.hh`, should always be preferred over other solutions.
+They provide a way to wrap any data structure as protected by a lock (mutex or shared mutex), while making it immediately clear which data is protected by that lock, and preventing any access to the data without holding the lock.
+
+For example, to protect a set of integers with a simple mutex:
+
+```c++
+LockGuarded<std::set<int>> d_data;
+```
+
+or with a shared mutex instead:
+
+```c+++
+SharedLockGuarded<std::set<int>> d_data;
+```
+
+Then the only way to access the data is to call the `lock()`, `read_only_lock()` or `try_lock()` methods for the simple case, or the `read_lock()`, `write_lock()`, `try_read_lock()` or `try_write_lock()` for the shared one.
+Doing so will return a "holder" object, which provides access to the protected data, checking that the lock has really been acquired if needed (`try_` cases).
+The data might be read-only if `read_lock()`, `try_read_lock()` or `read_only_lock()` was called.
+Access is provided by dereferencing the holder object via `*` or `->`, allowing a quick-access syntax:
+
+```c+++
+return d_data.lock()->size();
+```
+
+Or when the lock needs to be kept for a bit longer:
+
+```c++
+{
+ auto data = d_data.lock();
+ data->clear();
+ data->insert(42);
+}
+```
+
+## Atomics
+
+`std::atomic` provides a nice way to share a counter or gauge between threads without the need for locking.
+This is done by implementing operations like reading, increasing, decreasing or writing a value in an atomic way, using memory barriers, making sure that the value cannot be updated from a different core during the operation.
+The default mode uses a sequentially consistent ordering memory model, which is quite expensive since it requires a full memory fence on all multi-core systems.
+A relaxed model can be used for specific operations, but the default model has the advantage of being safe in all situations.
+
+## Per-Thread Counters
+
+For generic per-thread counters, we have a class in `tcounters.hh` that should provide better performance by allowing each thread to independently update its own counter, the costly operation only happens when the counter needs to be read by one thread gathering metrics from all threads.
+
+# Dealing with Untrusted Data
+
+As a rule of thumb, any data received from outside the process should be considered untrusted.
+This includes data received on a socket, loaded from a file, retrieved from a database, etc.
+Data received from an internal pipe might be excluded from that rule.
+
+Untrusted data should never be trusted to adhere to the expected format or specifications, and a strict checking of boundaries should be performed.
+It means for example that, after reading the length for a field inside the data, whether that length does not exceed the total length of the data should be checked.
+In the same way, if we expect a numerical type we should check whether it matches what we expect and understand.
+
+Anything unexpected should stop the processing and lead to the discarding of the complete data set.
+If a smaller data set can be safely discarded, and it is more important to load an incomplete set than to assure the integrity of the complete data set, only the faulty data can be discarded instead.
+
+## Alignment Issues
+
+When structured, binary data is received from the network or read from a file, it might be tempting to map it to an existing structure directly to make the parsing easier.
+But one must be careful about alignment issues on some architectures:
+
+```c++
+struct my_struct {
+ uint32_t foo;
+ uint32_t bar;
+};
+```
+
+It might be tempting to directly cast the received data:
+
+```c++
+void func(char* data, size_t offset, size_t length) {
+ // bounds check left out!
+ const struct my_struct* tmp = reinterpret_cast<const struct my_struct*>(data + offset);
+ ...
+}
+```
+
+Unfortunately this leads to undefined behaviour because the offset might not be aligned with the alignment requirement of the struct.
+One solution is to do a copy:
+
+```c++
+void func(char* data, size_t offset, size_t length) {
+ // bounds check left out!
+ struct my_struct tmp;
+ memcpy(&tmp, data + offset, sizeof(tmp));
+ /* ... */
+}
+```
+
+## Signed vs. Unsigned
+
+Signed integers might overflow, and the resulting value is unpredictable, as this overflow is undefined behaviour.
+That means that this code results in an unpredictable value:
+
+```c++
+int8_t a = std::numeric_limits<int8_t>::max();
+a++;
+```
+
+One such example led to [2006-01: Malformed TCP queries can lead to a buffer overflow which might be exploitable](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2006-01.html).
+
+It would be necessary to check that the value cannot overflow first.
+Another possibility would be to instruct the compiler to treat signed overflow as it does for unsigned values, by wrapping.
+This can be done with `-fwrapv` with g++.
+
+An operation on an unsigned integer will never result in an overflow, because the value will simply wrap around.
+This might still result in an unexpected value, possibly bypassing a critical check:
+
+```c++
+void parse_untrusted_data(uint8_t* data, uint16_t length)
+{
+ /* parse a record, first two bytes are the size of the record data, second two bytes are the type of the record */
+ if (length < 4) {
+ return;
+ }
+
+ /* read the first two bytes which hold the length of the next record */
+ uint16_t recordLen = data[0] * 256 + data[1];
+
+ /* let's assume that recordLen is equal to 65535 */
+ uint16_t totalRecordLen = /* size of the type */ sizeof(uint16_t) + recordLen; // <-- this results in a wrapped value of 65535 + 2 = 65537 = 1
+ if (totalRecordLen > length) {
+ return;
+ }
+
+ /* ... */
+}
+```
+
+A valid version to prevent the overflow:
+
+```c++
+void parse_untrusted_data(uint8_t* data, uint16_t length)
+{
+ /* parse a record, first two bytes are the size of the record data, second two bytes are the type of the record */
+ if (length < 4) {
+ return;
+ }
+
+ /* read the first two bytes which hold the length of the next record */
+ uint16_t recordLen = data[0] * 256 + data[1];
+ if (recordLen > length || (length - recordLen) < sizeof(uint16_t)) {
+ return;
+ }
+
+ /* ... */
+}
+```
+
+Converting from unsigned to signed will lose the high order bytes, and should be avoided, or the value should be checked beforehand:
+
+```c++
+uint64_t u = std::numeric_limits<uint64_t>::max();
+int64_t s = static_cast<int64_t>(u); /* Wrong, and the cast eliminates any warning */
+if (u <= std::numeric_limit<int64_t>::max()) {
+ int64_t s = static_cast<int64_t>(u); /* OK */
+}
+```
+
+The `pdns::checked_conv()` function can be used, ensuring that the conversion can safely be done and raising an exception otherwise.
+
+`-Wsign-conversion` can be used to warn about dangerous conversions (disabled by default in g++, and note that a cast disables the warning).
+
+## Fuzzing
+
+Fuzzing is a very useful way to test a piece of code that parses untrusted data.
+Efficient fuzzers are often doing coverage-based fuzzing, where the code that they test has been compiled in a special way to allow the fuzzer to detect which branches are executed and which are not, so that the fuzzer can see the effect of mutating specific bytes of the input on the code path.
+
+PowerDNS has a few fuzzing targets that can be used with libFuzzer or AFL in the `pdns/` directory, and are built when `--enable-fuzzing-target` is passed to `configure`.
+More information can be found in the [fuzzing/README.md](https://github.com/PowerDNS/pdns/blob/master/fuzzing/README.md) file.
+The existing fuzzing targets are run on the OSS-Fuzz infrastructure for a short time every time a pull request is opened, and for a longer time on the HEAD of the repository.
+
+# Other Potential Issues
+
+## Time-Of-Check to Time-Of-Use (TOCTOU)
+
+The time-of-check to time-of-use vulnerability is a very easy mistake to make when dealing with files or directories.
+The gist of it is that there is a small race condition between the time where a program might check the ownership, permissions or even existence of a file and the time it will actually do something with it.
+This time might be enough to allow an attacker to create a symbolic link to a critical file at the place of that exact file, for example.
+Since the program has enough rights to edit this file, this might allow an attacker to trick the program into writing into a completely different file.
+
+This is hard to avoid in all cases, but some mitigations do help:
+
+* Opening a file first (handling errors if that fails) then getting the needed metadata via the file descriptor instead of the path (`fstat`, `fchmod`, `fchown`) ;
+* Opening with the `O_NOFOLLOW` flag set, so that the operation will fail if the target exists and is a symbolic link ;
+* Always creating temporary files via the `mkstemp()` function, which guarantees that the file did not exist before and has been created with the right permissions ;
+* Using operations that are guaranteed to be atomic, like renaming a file on the same filesystem (for example in the same directory).
+
+## `errno`
+
+`errno` is only guaranteed to be set on failing system calls and not set on succeeding system calls.
+A library call may clobber `errno`, even when it succeeds.
+Safe practice is:
+
+* Only look at `errno` on failing system calls or when a library function is documented to set `errno` ;
+* Immediately save the value of `errno` in a local variable after a system call for later decision making.
+
+## Secrets
+
+Try very hard not to load sensitive information into memory.
+And of course do not write this information to logs or to disk!
+
+If you have to:
+
+* Use an object that can't be copied, by deleting the copy constructors and assignments operators,
+* Try to lock the memory so it cannot be swapped out to disk, or included in a core dump, via `sodium_malloc()` or `sodium_mlock()`, for example ;
+* Wipe the content before releasing the memory, so it will not linger around.
+ Do note that `memset()` is very often optimized out by the compiler, so function like `sodium_munlock()`, `explicit_bzero()` or `explicit_memset()` should be used instead.
+
+### Constant-Time Comparison
+
+Don't compare secret against data using a naive string comparison, as the timing of the operation will leak information about the content of the secret.
+Ideally, a constant-time comparison should be used instead (see `constantTimeStringEquals()` in the PowerDNS code base) but it is not always easy to achieve.
+One option might be to compute an HMAC of the secret using a key that was randomly generated at startup, and compare it against a HMAC of the supplied data computed with the same key.
+
+## Virtual Destructors
+
+Any class that is expected to be sub-classed should provide a virtual destructor.
+Not doing so will prevent the destructor of any derived class from being called if the object is held as the base type:
+
+```c++
+class Parent
+{
+ virtual void doVirtualCall();
+};
+
+class Child: public Parent
+{
+ Child()
+ {
+ d_fd = fopen(..);
+ }
+
+ ~Child()
+ {
+ if (d_fd) {
+ fclose(d_fd);
+ f_fd = nullptr;
+ }
+ }
+
+ void doVirtualCall() override;
+};
+
+std::vector<Parent> myObjects;
+myObjects.push_back(Child());
+```
+
+Note that defining a destructor will prevent the automatic creation of move operators for that class, since they are generated only if these conditions are met:
+
+* No copy operators are declared ;
+* No move operators are declared ;
+* No destructor is declared.
+
+If the parent class holds data that is costly to copy, it might make sense to declare the move operators explicitly:
+
+```c++
+class Parent
+{
+ Parent(Parent&&) = default;
+ Parent& operator=(Parent&&) = default;
+
+ virtual ~Parent()
+ {
+ }
+
+ virtual void doVirtualCall();
+
+private:
+ FILE* d_fd{nullptr};
+};
+```
+
+Note that declaring the move operators disables the copy operators, so if they are still needed:
+
+```c++
+class Parent
+{
+ Parent(Parent&&) = default;
+ Parent& operator=(Parent&&) = default;
+
+ Parent(const Parent&) = default;
+ Parent& operator=(const Parent&) = default;
+
+ virtual ~Parent()
+ {
+ }
+
+ virtual void doVirtualCall();
+};
+```
+
+On a related topic, virtual methods should not be called from constructors or destructors.
+While this is allowed under certain restrictions, it is very hard to know exactly which method (base or derived) will be called, and whether all sub-objects contained in the class would have been correctly constructed at that point.
+
+## Hash Collisions
+
+Hashes are a very useful tool, used in `unordered_map` and `unordered_set` among others.
+They are also used in our caches.
+An important caveat that developers need to be aware of regarding hashes are that the probability of a collision is often a lot higher than expected.
+This is well-known as the birthday paradox, the fact that the probability of having two entries colliding is a lot higher than the probability of finding a collision for a specific entry.
+This means that it is important to verify that the entries are actually identical, and just not that they hash to the same value.
+
+This is especially important when hashing attacker-controlled values, as they can be specially crafted to trigger collisions to cause:
+
+* Cache pollution (see [2018-06: Packet cache pollution via crafted query](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-06.html)) ;
+* Denial of service via hash table flooding (in a map, all entries that hash to the same value are often placed into a linked-list, making it possible to cause a linear scan of entries by making all of them hash to that same value).
+
+The first issue can be prevented by comparing the entries and not just the value they hash to.
+The second one can be avoided by using some sort of secret when computing the hash so that the result cannot be guessed by the attacker.
+That can be achieved by using an unpredictable seed for certain hash algorithms, or a secret for some other like [`SipHash`](https://en.wikipedia.org/wiki/SipHash).
+
+# Readability Tips
+
+Some of these tips are actually enforced by `clang-tidy` nowadays, but it is still useful to keep them in mind.
+
+## `auto`
+
+C++11 introduced automatic type deduction, using the `auto` keyword.
+Using automatic type deduction prevents nasty surprises if the variable is initialized from another one, or from a function, and the other type is changed to a different one.
+Without `auto`, code might still compile but trigger a copy or worse.
+
+## Explicit Comparisons
+
+* Compare numerical values with `== 0` or `!= 0` explicitly ;
+* Compare to `false` explicitly, which is easier to read ;
+* Compare to `nullptr` for the same reason.
+
+## Initialization
+
+Use braced initialization for members as often as possible:
+
+* It does forbid narrowing conversions :
+* It avoids C++'s "[most vexing parse](https://en.wikipedia.org/wiki/Most_vexing_parse)" which is to declare a function instead of calling the default constructor:
+
+```c++
+Object a(); // declares a function named a that returns an object
+```
+
+## `nullptr`
+
+When representing a pointer, using `nullptr` makes it immediately obvious that we are dealing with a pointer, as opposed to the use of `0`.
+It also cannot be silently taken as an integer, which can happens with `0` but also with `NULL`.
+
+## `const`-ness
+
+* Mark parameters and variables that should not be modified as `const`.
+ This is especially important for references and pointers that come from outside the function, but it also makes sense to do it for local variables or parameters passed by value because it might help detect a logic error later ;
+* Mark `const` methods as such (and make them thread-safe) ;
+* Prefer using `at()` on containers so that no insertion can take place by mistake, and to get bounds checking.
+
+## Unnamed Namespace
+
+Functions that are only used inside a single file should be put into an unnamed namespace, so that:
+
+* The compiler knows that these functions will not be called from a different compilation unit and thus that no symbol needs to be generated, making it more likely for the function to be inlined ;
+* The reader knows that this function is only used there and can be altered without causing an issue somewhere else.
+
+```c++
+namespace {
+
+bool thisFunctionIsOnlyUsableFromThisTranslationUnit()
+{
+}
+
+}
+```
+
+These functions used to be marked `static` in the past, so you might still encounter this form in the code base instead:
+
+```c++
+static bool thisOneAsWell()
+{
+}
+```
+
+but the unnamed namespace form is now preferred.
+
+For the same reason, global variables that are only accessed from a single file should be put into an unnamed namespace, or marked static as well.
+
+## Variables
+
+Try to declare variables in the innermost scope possible and avoid uninitialized variables as much as possible.
+Declare and initialize variables when the values needed to initialize them are available.
+
+## Exceptions
+
+Exceptions should be reserved for events that interrupt the normal processing flow (corrupted data, timeouts, ...), and should not be triggered in the general case.
+
+For example, it would be better for a function checking a password or an API key to return a boolean or a `enum` indicating whether the check was successful than to throw an exception if the credentials are not valid, because the return value makes it clear that the check can and will fail, while otherwise the caller might not be aware that an exception can be raised.
+
+This does not mean that we should be afraid of using exceptions, though, but we need to keep in mind that they involve hidden complexity for the programmer that needs to keep a mental map of all the possible exceptions that can be raised.
+
+As far as performance goes the cost of an exception that is not thrown is usually very small, thanks to the zero-cost exception model. It might still force the compiler to refrain from some optimizations, so it might make sense to avoid them in some very performance-sensitive, narrow code paths, and to mark these paths as `noexcept` whenever possible.
+
+### Custom Exceptions
+
+When exceptions are used, the ones defined by the standards should be used whenever possible, as they already cover a lot of use cases.
+
+If custom exceptions are necessary, to be able to catch them explicitly, they should derive from `std::exception`, directly or indirectly, so that they can be caught in a more generic way to prevent the program from terminating.
+
+For example, the main connection handling function of a server can catch `std::exception` and terminate the current connection if an uncaught exception bubbles up, without having to worry about all the possible cases.
+
+### Catching Exceptions
+
+Catching exceptions should always be done by `const`-reference:
+
+```c+++
+try {
+}
+catch (const std::exception& e) {
+ std::cerr << e.what() <<endl;
+}
+```
+
+Not using a reference would result in the exception object being sliced, meaning that a custom exception derived from `std::exception` would not see its overriding `what()` method called but the one from the base class instead.
+
+## Casts
+
+C-style casts should be avoided, as the compiler does almost no checking on the validity of the operation.
+They are also very hard to spot in a code.
+C++-style casts can easily be spotted in a code, which makes it easy to review them.
+
+* `const_cast` can be used to remove the `const` qualifier on a variable.
+ It's usually a bad sign, but is sometimes needed to call a function that will not modify the variable but lacks the `const` qualifier ;
+* `dynamic_cast` can be used to cast a pointer to a derived class or to a base class, while checking that the operation is valid.
+ If the cast object is not valid for the intended type, a `nullptr` value will be returned (or a `bad_cast` exception for references) so the result of the operation should be checked!
+ Note that the Run-Time Type Information (RTTI) check needed to verify that the cast object is valid has a non-negligible CPU cost.
+ Not checking the return value might lead to remote denial of service by `nullptr`-dereference, as happened with the issue described in this advisory: [2017-08: Crafted CNAME answer can cause a denial of service](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2017-08.html) ;
+* `static_cast` can perform downcast in place of `dynamic_cast`, with none of the cost associated to the check, but can only be done if the cast is known to be valid.
+ It can also do implicit conversion between types (from `ssize_t` to `size_t`, **after** checking that the value is greater or equal to zero) ;
+* `reinterpret_cast` is quite dangerous, since it can be used to turn a type into a different one.
+ It cannot be be used to remove a `const` qualifier.
+ When used to reinterpret the content of a buffer it can quickly lead to alignment issues, as described in the [Alignment Issues] section.
* If this commit fixes an issue, put "Closes #XXXX" in the message
* Do not put whitespace fixes/cleanup and functionality changes in the same commit
-# Coding Guidelines
+# Formatting and Coding Guidelines
## `clang-format`
-We have `clang-format` in place, but not for all files yet.
-This is an incremental process.
-If you're adding new code, adhering to the formatting config is appreciated.
-Formatting breakage in already formatted files will be caught by the CI.
-To format all files that are supposed to be formatted, run `make format-code` in the root of the tree.
+We have `clang-format` in place, but not for all files yet. We are working towards a fully formatted codebase in an incremental fashion.
-## Additional guidelines
+If you're adding new code, adhering to the formatting configuration available in `.clang-format` is appreciated. If you are touching code that is not yet formatted, it would also be very appreciated to format it in a separate commit first.
-* Don't have end-of-line whitespace
-* Use spaces instead of tabs
-* Although the codebase does not consistently have them, [docblock](https://www.doxygen.nl/manual/docblocks.html)s on functions and classes are appreciated
-* Never hesitate to write comments on anything that might not be immediately clear just from reading the code
-* When adding whole new things, consider putting them in a `pdns::X` namespace. Look for `namespace pdns` in the codebase for examples.
+Any formatting breakage in already formatted files will be caught by the CI. To format all files that are supposed to be formatted, run `make format-code` in the root of the tree.
-## Code Checkers
+## Formatting guidelines
-Even though we don't automatically run any of the code checkers listed below as part of our CI, it might make sense to run them manually, not only on newly added code, but to also improve existing code.
+* Don't have end-of-line whitespace.
+* Use spaces instead of tabs.
+
+## Coding guidelines
+
+The coding guidelines can be found in the repository at
+[CODING_GUIDELINES.md](https://github.com/PowerDNS/pdns/blob/master/CODING_GUIDELINES.md)
+
+## Code Checkers
### `clang-tidy`
2. A more complete [.clang-tidy.full](.clang-tidy.full) which enables almost all available checks.
This configuration can be enabled using `ln -sf .clang-tidy.full .clang-tidy` and is recommended for all new code.
+### `clang-tidy` and CI
+
+We run `clang-tidy` using the `.clang-tidy.full` configuration as part of our CI. `clang-tidy` warnings will show up on a pull request if any are introduced.
+
+However, it may happen that existing code could produce warnings and can show up too due to being part of the pull request. In such a case there are two options:
+
+1. Fix the warnings in a separate commit.
+2. If fixing the warning would be too much trouble at this point in time, disabling the specific warning using the `// NOLINTNEXTLINE` or `// NOLINT` directives can be acceptable given the following is adhered to:
+
+Any added `// NOLINTNEXTLINE` or `// NOLINT` directive or others need to have a Github issue title, issue number and link next to them in the description along with the name or Github nickname of the person that wrote it. The Github issue must have an assignee and an accurate description of what needs to be done. As an example:
+
+`// NOLINTNEXTLINE(<warning-name>) <issue-number> <issue-link> <person-name>: <issue-title> + a short comment if needed.`
+
+If the warning cannot be avoided in any way, a good explanation is needed. As an example:
+
+`// NOLINTNEXTLINE(*-cast): Using the OpenSSL C APIs.`
+
+### Additional checkers
+
+Even though we don't automatically run any of the code checkers listed below as part of our CI, it might make sense to run them manually, not only on newly added code, but to also improve existing code.
+
+* `clang`'s static analyzer, sometimes also referred as `scan-build`
+* `cppcheck`
+
# Development Environment
Information about setting up a development environment using a language server like [`clangd`](https://clangd.llvm.org/) or [`ccls`](https://github.com/MaskRay/ccls) can be found in [DEVELOPMENT.md](DEVELOPMENT.md).
https://hub.docker.com/u/powerdns offers automatic builds of dnsdist, Auth and Recursor, from the pdns.git master branch.
-The images are based on Debian Buster (slim).
+The images are based on Debian Bullseye (slim).
The Dockerfiles:
The images are ready to run with limited functionality.
At container startup, the startup.py wrapper (from the dockerdata directory linked above) checks for `PDNS_RECURSOR_API_KEY` / `PDNS_AUTH_API_KEY` / `DNSDIST_API_KEY` environment variables for the product you are running.
-If such a variable is found, `/etc/powerdns-api.conf` or `/etc/dnsdist-api.conf` is written, enabling the webserver in all products, and the dnsdist console.
+If such a variable is found, `/etc/powerdns/recursor.d/_api.conf` / `/etc/powerdns/pdns.d/_api.conf` / `/etc/dnsdist/conf.d/_api.conf` is written, enabling the webserver in all products, and the dnsdist console.
For the dnsdist console, make sure that your API key is in a format suitable for the console (use `makeKey()`).
The default configs shipped in the image (see dockerdata above) parse all files in `/etc/powerdns/pdns.d` / `/etc/powerdns/recursor.d` / `/etc/dnsdist/conf.d`.
-The image also ships a symlink to the API config file inside those `.d` dirs.
For Auth and Recursor, extra configuration can be passed on the command line, or via a volume mount into `/etc/powerdns` or the `.d` dir.
For dnsdist, only the volume mount is applicable.
-If you want to volume mount a config, but also take the keys from the environment, please take care to include the same `X-api.conf` symlink in your `.d` directory.
+If you want to volume mount a config, but also take the keys from the environment, please take care to include the same `_api.conf` file in your `.d` directory.
+
+If you want to read the configuration for debugging purposes, you can run the containers with the `DEBUG_CONFIG` environment variable set to `'yes'`.
+This will print the full config on startup. Please keep in mind that this also includes credentials, therefore this setting should never be used in production environments.
# Auth and databases
* dnsdist: `setLocal()`
* Auth & Recursor: `local-address` and/or `local-port`
-Note: Docker Engine 20.10.0 (released december 2020) removed the need to set the `NET_BIND_SERVICE` capability when attempting to bind to a privileged port.
+Note: Docker Engine 20.10.0 (released december 2020) removed the need to set the `NET_BIND_SERVICE` capability when attempting to bind to a privileged port.
+
+## Auth and Supervisord
+
+The auth image uses `tini` as init process to run auth via the startup.py wrapper. However, it also has `supervisord` available for special use cases. Example scenarios for using `supervisord` include:
+
+* Running multiple processes (ie: auth + ixfrdist) within the same container - Generally not advisable, but has benefits in some cases
+* Allowing restarts of processes within a container without having the entire container restart - Primarily has benefits in Kubernetes where you could have a process (ixfrdist for example) restart when a script/agent detects changes in a mounted configmap containing the process' configuration.
+
+To use `supervisord` within Kubernetes, you can configure the container with the following:
+
+```yaml
+command: ["supervisord"]
+args:
+ - "--configuration"
+ - "/path/to/supervisord.conf"
+```
+
+In the above example `/path/to/supervisord.conf` is the path where a configmap containing your supervisord configuration is mounted.
+Further details about `supervisord` and how to configure it can be found here: http://supervisord.org/configuration.html
RUN apt-get update && apt-get -y dist-upgrade && apt-get clean
# Ensure python3 and jinja2 is present (for startup script), and sqlite3 (for db schema), and tini (for signal management),
-# and vim (for pdnsutil edit-zone)
-RUN apt-get install -y python3 python3-jinja2 sqlite3 tini libcap2-bin vim-tiny && apt-get clean
+# and vim (for pdnsutil edit-zone) , and supervisor (for special use cases requiring advanced process management)
+RUN apt-get install -y python3 python3-jinja2 sqlite3 tini libcap2-bin vim-tiny supervisor && apt-get clean
# Output from builder
COPY --from=builder /build /
--- /dev/null
+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
+
# our chosen base image
-FROM debian:11-slim AS builder
+FROM debian:12-slim AS builder
ENV NO_LUA_JIT="s390x arm64"
COPY builder-support /source/builder-support
# TODO: control file is not in tarballs at all right now
-RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/dnsdist/debian-buster/control && \
+RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/dnsdist/debian-bookworm/control && \
apt-get clean
COPY pdns /source/pdns
fi && \
BUILDER_MODULES=dnsdist autoreconf -vfi
+
+RUN mkdir /libh2o && cd /libh2o && \
+ apt-get update && apt-get install -y cmake curl libssl-dev zlib1g-dev && \
+ curl -f -L https://github.com/PowerDNS/h2o/archive/refs/tags/v2.2.6+pdns2.tar.gz | tar xz && \
+ CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6-pdns2 && \
+ make install
+
+RUN mkdir /quiche && cd /quiche && \
+ apt-get install -y libclang-dev && \
+ apt-get clean && \
+ /source/builder-support/helpers/install_rust.sh && \
+ /source/builder-support/helpers/install_quiche.sh
+
RUN mkdir /build && \
LUAVER=$([ -z "${NO_LUA_JIT##*$(dpkg --print-architecture)*}" ] && echo 'lua5.3' || echo 'luajit') && \
./configure \
--enable-dnscrypt \
--enable-dns-over-tls \
--enable-dns-over-https \
- --with-re2 && \
+ --with-re2 \
+ --with-h2o \
+ --enable-dns-over-quic \
+ --enable-dns-over-http3 \
+ --with-quiche \
+ PKG_CONFIG_PATH=/opt/lib/pkgconfig && \
make clean && \
make $MAKEFLAGS install DESTDIR=/build && make clean && \
- strip /build/usr/local/bin/*
+ strip /build/usr/local/bin/* &&\
+ mkdir -p /build/usr/lib/ && \
+ cp -rf /usr/lib/libdnsdist-quiche.so /build/usr/lib/
+
RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \
echo 'Source: docker-deps-for-pdns' > debian/control && \
dpkg-shlibdeps /build/usr/local/bin/dnsdist && \
# Runtime
-FROM debian:11-slim
+FROM debian:12-slim
# Reusable layer for base update - Should be cached from builder
RUN apt-get update && apt-get -y dist-upgrade && apt-get clean
COPY .git /source/.git
COPY builder/helpers/set-configure-ac-version.sh /usr/local/bin
+COPY builder-support/helpers/install_rust.sh /source/install_rust.sh
+RUN /source/install_rust.sh
+
# build and install (TODO: before we hit this line, rearrange /source structure if we are coming from a tarball)
WORKDIR /source/pdns/recursordist
-PowerDNS is copyright © 2001-2023 by PowerDNS.COM BV and lots of
+PowerDNS is copyright © by PowerDNS.COM BV and lots of
contributors, using the GNU GPLv2 license (see NOTICE for the
exact license and exception used).
The PowerDNS Authoritative Server depends on Boost, OpenSSL and Lua, and requires a
compiler with C++-2017 support.
-On Debian 9, the following is useful:
+On Debian, the following is useful:
```sh
apt install g++ libboost-all-dev libtool make pkg-config default-libmysqlclient-dev libssl-dev libluajit-5.1-dev python3-venv
apt install autoconf automake ragel bison flex
```
-For Ubuntu 18.04 (Bionic Beaver), the following packages should be installed:
+For Ubuntu, the following packages should be installed:
```sh
apt install libcurl4-openssl-dev luajit lua-yaml-dev libyaml-cpp-dev libtolua-dev lua5.3 autoconf automake ragel bison flex g++ libboost-all-dev libtool make pkg-config libssl-dev lua-yaml-dev libyaml-cpp-dev libluajit-5.1-dev libcurl4 gawk libsqlite3-dev python3-venv
====================================
If you have a security problem to report, please email us at both peter.van.dijk@powerdns.com and remi.gacogne@powerdns.com.
-In case you want to encrypt your report using PGP, please use: https://www.powerdns.com/powerdns-keyblock.asc
+In case you want to encrypt your report using PGP, please use: https://doc.powerdns.com/powerdns-keyblock.asc
Please do not mail security issues to public lists, nor file a ticket, unless we do not get back to you in a timely manner.
We fully credit reporters of security issues, and respond quickly, but please allow us a reasonable timeframe to coordinate a response.
# Globals
-g_version = '1.0.2'
+g_version = '1.0.3'
g_verbose = False
'dnsdist-17', 'dnsdist-18', 'dnsdist-master']:
write_dockerfile('el', '9', release)
+ if release in ['auth-48', 'auth-master']:
+ write_dockerfile('debian', 'bookworm', release)
+ write_list_file('debian', 'bookworm', release)
+
# Test Release Functions
def build (dockerfile):
if cp.returncode != 0 and cp.returncode != 99:
# FIXME write failed output to log
print('Error running {}: {}'.format(tag, repr(cp.returncode)))
- return cp.returncode
+ return cp.returncode, None
if version and version.group(2):
return cp.returncode, version.group(2)
else:
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
-sudo pip3 install git+https://github.com/pyinvoke/invoke@faa5728a6f76199a3da1750ed952e7efee17c1da
+sudo apt-get -qq -y --no-install-recommends install python3-pip python3-invoke
"
sudo chmod 755 /usr/sbin/policy-rc.d
sudo apt-get update
-sudo apt-get -qq -y --no-install-recommends install python3-pip
-sudo pip3 install git+https://github.com/pyinvoke/invoke@faa5728a6f76199a3da1750ed952e7efee17c1da
+sudo apt-get -qq -y --no-install-recommends install python3-pip python3-invoke
+++ /dev/null
-#!/bin/sh
-
-set -x
-context=''
-# poor mans option parsing
-if [ -n "$1" ]; then
- if [ "$1" != "odbc" ]; then
- echo "invalid argument"
- exit 1
- fi
- context=odbc
- if [ -n "$2" ]; then
- echo "too many arguments"
- exit 1
- fi
-fi
-
-export PDNS=/usr/sbin/pdns_server
-export PDNS2=$PDNS
-export SDIG=/usr/bin/sdig
-export NSEC3DIG=/usr/bin/nsec3dig
-export NOTIFY=/usr/bin/pdns_notify
-export SAXFR=/usr/bin/saxfr
-export ZONE2SQL=/usr/bin/zone2sql
-export ZONE2JSON=/usr/bin/zone2json
-export PDNSUTIL=/usr/bin/pdnsutil
-export PDNSCONTROL=/usr/bin/pdns_control
-
-export GEM_HOME=${PWD}/gems
-mkdir -p $GEM_HOME
-export PATH="${GEM_HOME}/bin:$PATH"
-
-if [ -z "$context" ]; then
- cd modules/remotebackend
- ruby -S bundle install
- cd ../../
-fi
-
-MODULES=""
-
-for dir in /usr/lib/x86_64-linux-gnu/pdns /usr/lib64/pdns; do
- if [ -d $dir ]; then
- MODULES=$dir
- break
- fi
-done
-[ -z $MODULES ] && echo "No module directory found" >&2 && exit 1
-
-# Symlink the modules on the system
-cd regression-tests/modules
-for backend in *.so; do
- ln -sf $MODULES/$backend $backend
-done
-
-cd ..
-
-EXITCODE=0
-
-if [ -z "$context" ]; then
- export geoipregion=oc geoipregionip=1.2.3.4
- ./timestamp ./start-test-stop 5300 bind-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 bind-dnssec-both || EXITCODE=1
-
- # No PKCS#11 in packages
- #SETUP_SOFTHSM=y ./timestamp ./start-test-stop 5300 bind-dnssec-pkcs11 || EXITCODE=1
- ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-optout-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-narrow || EXITCODE=1
- ./timestamp ./start-test-stop 5300 bind-hybrid-nsec3 || EXITCODE=1
-
- # Adding extra IPs to docker containers in not supported :(
- #./timestamp ./start-test-stop 5300 geoipbackend || EXITCODE=1
- #./timestamp ./start-test-stop 5300 geoipbackend-nsec3-narrow || EXITCODE=1
-
- ./timestamp ./start-test-stop 5300 gmysql-nodnssec-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gmysql-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gmysql-nsec3-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gmysql-nsec3-optout-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gmysql-nsec3-narrow || EXITCODE=1
-
- ./timestamp ./start-test-stop 5300 gpgsql-nodnssec-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gpgsql-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gpgsql-nsec3-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gpgsql-nsec3-optout-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gpgsql-nsec3-narrow || EXITCODE=1
-
- ./timestamp ./start-test-stop 5300 gsqlite3-nodnssec-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gsqlite3-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-optout-both || EXITCODE=1
- ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-narrow || EXITCODE=1
-
- ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe || EXITCODE=1
- ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe-dnssec || EXITCODE=1
- ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix || EXITCODE=1
- ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix-dnssec || EXITCODE=1
- ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-http || EXITCODE=1
- ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-http-dnssec || EXITCODE=1
-
- ./timestamp timeout 120s ./start-test-stop 5300 lua2
- ./timestamp timeout 120s ./start-test-stop 5300 lua2-dnssec
-
- # No 0MQ in the PowerDNS packages
- #./timestamp timeout 120s ./start-test-stop 5300 remotebackend-zeromq || EXITCODE=1
- #./timestamp timeout 120s ./start-test-stop 5300 remotebackend-zeromq-dnssec || EXITCODE=1
-
- ./timestamp ./start-test-stop 5300 tinydns || EXITCODE=1
-
- cd ../regression-tests.nobackend/
-
- ./runtests || EXITCODE=1
-elif [ "$context" = "odbc" ]; then
- cat > ~/.odbc.ini << __EOF__
-[pdns-sqlite3-1]
-Driver = SQLite3
-Database = $(pwd)/pdns.sqlite3
-
-[pdns-sqlite3-2]
-Driver = SQLite3
-Database = $(pwd)/pdns.sqlite32
-
-[pdns-mssql]
-Driver=FreeTDS
-Trace=No
-Server=pdns-odbc-regress-sql-1.database.windows.net
-Port=1433
-Database=pdns
-TDS_Version=7.1
-ClientCharset=UTF-8
-__EOF__
-
- set +x
- . ~/.mssql-credentials
- set -x
- export GODBC_SQLITE3_DSN=pdns-sqlite3-1
- ./timestamp timeout 120s ./start-test-stop 5300 godbc_sqlite3-nodnssec || EXITCODE=1
- export GODBC_MSSQL_DSN=pdns-mssql
- export GODBC_MSSQL_USERNAME
- export GODBC_MSSQL_PASSWORD
- ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nodnssec || EXITCODE=1
- ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql || EXITCODE=1
- ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3 || EXITCODE=1
- ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3-optout || EXITCODE=1
- ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3-narrow || EXITCODE=1
-fi
-
-exit $EXITCODE
set -x
+EXTRA_ARG=""
+if [ $PWD = /srv/buildbot-worker/test-rec-debian-buster/build ]; then
+ EXTRA_ARG=--ignore=test_SNMP.py
+fi
+
cd regression-tests/modules
MODULES=""
./clean.sh
cd ../regression-tests.recursor-dnssec
-./runtests $@ || EXIT=1
+
+./runtests $EXTRA_ARG $@ || EXIT=1
./printlogs.py || true
exit $EXIT
-Subproject commit 2fb9f4b5c2ef8b00cb2de33ccd1264b9ec39992a
+Subproject commit 16bc1604c48821c12b708d7afa88d9612ebdd0da
# bind-ignore-broken-records=no
#################################
-# bind-supermaster-config Location of (part of) named.conf where pdns can write zone-statements to
+# bind-autoprimary-config Location of (part of) named.conf where pdns can write zone-statements to
#
-# bind-supermaster-config=
-bind-supermaster-config=/var/lib/powerdns/supermaster.conf
+# bind-autoprimary-config=
+bind-autoprimary-config=/var/lib/powerdns/supermaster.conf
#################################
-# bind-supermaster-destdir Destination directory for newly added slave zones
+# bind-autoprimary-destdir Destination directory for newly added secondary zones
#
-# bind-supermaster-destdir=/etc/powerdns
-bind-supermaster-destdir=/var/lib/powerdns/zones.slave.d
+# bind-autoprimary-destdir=/etc/powerdns
+bind-autoprimary-destdir=/var/lib/powerdns/zones.slave.d
#################################
-# bind-supermasters List of IP-addresses of supermasters
+# bind-autoprimaries List of IP-addresses of autoprimaries
#
-# bind-supermasters=
+# bind-autoprimaries=
# tinydns-locations=yes
#################################
-# tinydns-notify-on-startup Tell the TinyDNSBackend to notify all the slave nameservers on startup. Default is no.
+# tinydns-notify-on-startup Tell the TinyDNSBackend to notify all the secondary nameservers on startup. Default is no.
#
# tinydns-notify-on-startup=no
This package contains several tools to debug DNS issues. These tools do not
require any part of the PowerDNS server components to work.
.
+ * calidns: Resolver benchmark tool
* dnsbulktest: A resolver stress-tester
* dnsgram: Show per 5-second statistics to study intermittent resolver issues
+ * dnspcap2calidns: PCAP conversion tool (calidns format)
+ * dnspcap2protobuf: PCAP conversion tool (protobuf format)
* dnsreplay: Replay a pcap with DNS queries
* dnsscan: Prints the query-type amounts in a pcap
* dnsscope: Calculates statistics without replaying traffic
* dnstcpbench: Perform TCP benchmarking of DNS servers
* dnswasher: Clean a pcap of identifying IP information
+ * dumresp: Dummy DNS responder
* ixplore: Explore diffs from IXFRs
+ * nproxy: DNS notification proxy
* nsec3dig: Calculate the correctness of NSEC3 proofs
+ * pdns_notify: Simple tool for sending DNS notifies
* saxfr: AXFR zones and show extra information
+ * sdig: dig-like tool supporting DoH, DoT, PROXY-protocol and XPF
+ * stubquery: Stub resolver query tool
Package: pdns-ixfrdist
Architecture: any
# Create suggested supermaster.conf, which is included from /etc/powerdns/named.conf by default.
BINDCONF=/etc/powerdns/pdns.d/bind.conf
SUPERMASTERCONF=/var/lib/powerdns/supermaster.conf
- if test -e $BINDCONF && grep "^bind-supermaster-config=$SUPERMASTERCONF" $BINDCONF >/dev/null 2>&1; then
+ if test -e $BINDCONF && grep "^bind-autoprimary-config=$SUPERMASTERCONF" $BINDCONF >/dev/null 2>&1; then
touch $SUPERMASTERCONF
chown pdns:pdns $SUPERMASTERCONF
fi
./launch-pdns
-dig -p 5301 @127.0.0.1 smoke.bind.example.org 2>&1 | tee "$TMPFILE"
+../../pdns/sdig 127.0.0.1 5301 smoke.bind.example.org A 2>&1 | tee "$TMPFILE"
if grep -c '127\.0\.0\.123' "$TMPFILE"; then
echo success
./launch-pdns
-dig -p 5301 @127.0.0.1 smoke.lmdb.example.org SOA 2>&1 | tee "$TMPFILE"
+../../pdns/sdig 127.0.0.1 5301 smoke.lmdb.example.org SOA 2>&1 | tee "$TMPFILE"
if grep -c 'a.misconfigured' "$TMPFILE"; then
echo success
Tests: smoke-bind
-Depends: dnsutils,
- pdns-backend-bind,
- pdns-server
+Depends: pdns-backend-bind,
+ pdns-server,
+ pdns-tools
Restrictions: needs-root
Tests: smoke-mysql
-Depends: dnsutils,
- mariadb-server,
+Depends: mariadb-server,
pdns-backend-mysql,
- pdns-server
+ pdns-server,
+ pdns-tools
Restrictions: needs-root, isolation-container
Tests: smoke-mysql-sp
-Depends: dnsutils,
- mariadb-server,
+Depends: mariadb-server,
pdns-backend-mysql,
- pdns-server
+ pdns-server,
+ pdns-tools
Restrictions: needs-root, isolation-container
Tests: smoke-pgsql
-Depends: dnsutils,
- pdns-backend-pgsql,
+Depends: pdns-backend-pgsql,
pdns-server,
+ pdns-tools,
postgresql
Restrictions: needs-root, isolation-container
}
trap cleanup EXIT
-dig @127.0.0.1 smoke.$ZONE 2>&1 | tee "$TMPFILE"
+sdig 127.0.0.1 53 smoke.$ZONE A 2>&1 | tee "$TMPFILE"
if grep -c '127\.0\.0\.222' "$TMPFILE"; then
echo success
}
trap cleanup EXIT
-dig @127.0.0.1 smoke.$ZONE 2>&1 | tee "$TMPFILE"
+sdig 127.0.0.1 53 smoke.$ZONE A 2>&1 | tee "$TMPFILE"
if grep -c '127\.0\.0\.222' "$TMPFILE"; then
echo success
}
trap cleanup EXIT
-dig @127.0.0.1 smoke.$ZONE 2>&1 | tee "$TMPFILE"
+sdig 127.0.0.1 53 smoke.$ZONE A 2>&1 | tee "$TMPFILE"
if grep -c '127\.0\.0\.222' "$TMPFILE"; then
echo success
}
trap cleanup EXIT
-dig @127.0.0.1 smoke.$ZONE 2>&1 | tee "$TMPFILE"
+sdig 127.0.0.1 53 smoke.$ZONE A 2>&1 | tee "$TMPFILE"
if grep -c '127\.0\.0\.222' "$TMPFILE"; then
echo success
--- /dev/null
+Source: dnsdist
+Section: net
+Priority: optional
+Maintainer: PowerDNS.COM BV <powerdns.support.sales@powerdns.com>
+Uploaders: PowerDNS.COM BV <powerdns.support.sales@powerdns.com>
+Build-Depends: debhelper (>= 10),
+ libboost-all-dev,
+ libbpf-dev [linux-any],
+ libcap-dev,
+ libcdb-dev,
+ libedit-dev,
+ libfstrm-dev,
+ libgnutls28-dev,
+ liblmdb-dev,
+ libluajit-5.1-dev [!arm64 !s390x],
+ liblua5.3-dev [arm64 s390x],
+ libnghttp2-dev,
+ libre2-dev,
+ libsnmp-dev,
+ libsodium-dev,
+ libssl-dev,
+ libsystemd-dev [linux-any],
+ libwslay-dev,
+ libxdp-dev [linux-any],
+ pkg-config,
+ ragel,
+ systemd [linux-any]
+Standards-Version: 4.1.5
+Homepage: https://dnsdist.org
+
+Package: dnsdist
+Architecture: any
+Depends: ${misc:Depends},
+ ${shlibs:Depends}
+Description: DNS loadbalancer
+ Highly DoS- and abuse-aware load balancing tool for DNS traffic,
+ with Lua scripting and configuration capability.
+ Can be configured to use various sets of rules to classify, route
+ and reject traffic.
--- /dev/null
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: dnsdist
+Source: https://dnsdist.org
+
+Files: *
+Copyright: 2002-2022 PowerDNS.COM BV and contributors
+License: GPL-2 with OpenSSL Exception
+
+Files: debian/*
+Copyright: 2002-2016 PowerDNS.COM BV and contributors
+ 2016 Chris Hofstaedtler <zeha@debian.org>
+License: GPL-2 with OpenSSL Exception
+Comment: Debian packaging is under same license as upstream code
+
+Files: ext/json11/*
+Copyright: 2013 Dropbox, Inc.
+License: Expat
+
+Files: ext/libbpf/*
+Copyright: 2015, 2016 Alexei Starovoitov <ast@plumgrid.com>
+License: GPL-2
+Comment: taken from Linux kernel source
+
+Files: ext/luawrapper/*
+Copyright: 2013, Pierre KRIEGER
+License: BSD-3
+
+Files: ext/yahttp/*
+Copyright: 2014 Aki Tuomi
+License: Expat
+
+Files: compile ltmain.sh
+Copyright: 1996-2011 Free Software Foundation, Inc.
+License: GPL-2+
+
+Files: m4/ax_cxx_compile_stdcxx_11.m4
+Copyright: 2008 Benjamin Kosnik <bkoz@redhat.com>
+ 2012 Zack Weinberg <zackw@panix.com>
+ 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+ 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com>
+License: free-generic
+
+Files: m4/boost.m4
+Copyright: 2007-2011, 2014 Benoit Sigoure <tsuna@lrde.epita.fr>
+License: GPL-3 or Autoconf
+
+Files: m4/libtool.m4 m4/lt*.m4
+Copyright: 1996-2011 Free Software Foundation, Inc.
+License: free-fsf
+
+Files: m4/systemd.m4
+Copyright: 2014 Luis R. Rodriguez <mcgrof@suse.com>
+ 2016 Pieter Lexis <pieter.lexis@powerdns.com>
+License: GPL-2+
+
+Files: m4/warnings.m4
+Copyright: 2008-2015 Free Software Foundation, Inc.
+License: free-fsf
+
+Files: m4/pdns_d_fortify_source.m4 m4/pdns_param_ssp_buffer_size.m4 m4/pdns_pie.m4 m4/pdns_relro.m4 m4/pdns_stack_protector.m4
+Copyright: 2013 Red Hat, Inc.
+License: LGPL-2.1+
+
+Files: src_js/d3.js
+Copyright: 2010-2016 Mike Bostock
+License: Expat
+
+Files: src_js/jquery.js
+Copyright: JS Foundation and other contributors
+License: Expat
+
+Files: src_js/moment.js
+Copyright: JS Foundation and other contributors
+License: Expat
+
+Files: src_js/rickshaw.js
+Copyright: 2011-2014 by Shutterstock Images, LLC
+License: Expat
+
+Files: */libdnsdist-quiche.so
+Copyright: 2018-2019, Cloudflare, Inc.
+License: BSD-2-clause
+
+License: Unlicense
+ This is free and unencumbered software released into the public domain.
+ .
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+ .
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+ .
+ For more information, please refer to <http://unlicense.org/>
+
+License: GPL-2 with OpenSSL Exception
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of version 2 of the GNU General Public License as
+ published by the Free Software Foundation.
+ .
+ In addition, for the avoidance of any doubt, permission is granted to
+ link this program with OpenSSL and to (re)distribute the binaries
+ produced as the result of such linking.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+License: BSD-2-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: BSD-3
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: LGPL-2.1+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see
+ <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU Lesser General Public
+ License version 2.1 can be found in the file
+ `/usr/share/common-licenses/LGPL-2.1'.
+
+License: GPL-2
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
+
+License: GPL-2+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
+
+License: GPL-3 or Autoconf
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ .
+ Additional permission under section 7 of the GNU General Public
+ License, version 3 ("GPLv3"):
+ .
+ If you convey this file as part of a work that contains a
+ configuration script generated by Autoconf, you may do so under
+ terms of your choice.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 3 can be found in the file
+ `/usr/share/common-licenses/GPL-3'.
+
+License: free-fsf
+ This file is free software; the Free Software Foundation gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+License: free-generic
+ Copying and distribution of this file, with or without modification, are
+ permitted in any medium without royalty provided the copyright notice
+ and this notice are preserved. This file is offered as-is, without any
+ warranty.
--- /dev/null
+/etc/dnsdist
--- /dev/null
+dnsdist.conf
--- /dev/null
+#! /bin/sh
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+ configure)
+
+ adduser --force-badname --system --home /nonexistent --group \
+ --no-create-home --quiet _dnsdist || true
+
+ if [ "`stat -c '%U:%G' /etc/dnsdist/dnsdist.conf`" = "root:root" ]; then
+ chown root:_dnsdist /etc/dnsdist/dnsdist.conf
+ # Make sure that dnsdist can read it; the default used to be 0600
+ chmod g+r /etc/dnsdist/dnsdist.conf
+ fi
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
--- /dev/null
+[DEFAULT]
+pristine-tar = True
+multimaint-merge = True
+patch-numbers = False
--- /dev/null
+../../src_js/d3.js
\ No newline at end of file
--- /dev/null
+../../src_js/jquery.js
\ No newline at end of file
--- /dev/null
+../../src_js/moment.js
\ No newline at end of file
--- /dev/null
+../../src_js/rickshaw.js
\ No newline at end of file
--- /dev/null
+#!/usr/bin/make -f
+include /usr/share/dpkg/architecture.mk
+include /usr/share/dpkg/pkg-info.mk
+
+# Enable hardening features for daemons
+export DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow,+pie
+# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/*
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/default.mk
+
+# for atomic support on powerpc (automatic on mipsel)
+LDFLAGS += -latomic
+
+# Only enable systemd integration on Linux operating systems
+ifeq ($(DEB_HOST_ARCH_OS),linux)
+CONFIGURE_ARGS += --enable-systemd --with-systemd=/lib/systemd/system
+DH_ARGS += --with systemd
+else
+CONFIGURE_ARGS += --disable-systemd
+endif
+
+# Only enable BPF/XDP on Linux operating systems
+ifeq ($(DEB_HOST_ARCH_OS),linux)
+CONFIGURE_ARGS += --with-xsk
+else
+CONFIGURE_ARGS += --without-xsk
+endif
+
+# Only disable luajit on arm64
+ifneq ($(DEB_HOST_ARCH),arm64)
+CONFIGURE_ARGS += --with-lua=luajit
+else
+CONFIGURE_ARGS += --with-lua=lua5.3
+endif
+
+%:
+ dh $@ \
+ --with autoreconf \
+ $(DH_ARGS)
+
+override_dh_auto_clean:
+ rm -f dnslabeltext.cc
+ dh_auto_clean
+
+override_dh_auto_configure:
+ ./configure \
+ --host=$(DEB_HOST_GNU_TYPE) \
+ --build=$(DEB_BUILD_GNU_TYPE) \
+ --prefix=/usr \
+ --sysconfdir=/etc/dnsdist \
+ --mandir=\$${prefix}/share/man \
+ --infodir=\$${prefix}/share/info \
+ --libdir='$${prefix}/lib/$(DEB_HOST_MULTIARCH)' \
+ --libexecdir='$${prefix}/lib' \
+ --enable-lto=thin \
+ --enable-dns-over-https \
+ --enable-dns-over-quic \
+ --enable-dns-over-http3 \
+ --enable-dns-over-tls \
+ --enable-dnscrypt \
+ --enable-dnstap \
+ --with-ebpf \
+ --with-gnutls \
+ --with-h2o \
+ --with-net-snmp \
+ --with-libcap \
+ --with-libsodium \
+ --with-quiche \
+ --with-re2 \
+ --with-service-user='_dnsdist' \
+ --with-service-group='_dnsdist' \
+ $(CONFIGURE_ARGS) \
+ PKG_CONFIG_PATH=/opt/lib/pkgconfig
+
+override_dh_auto_build-arch:
+ dh_auto_build -- V=1
+
+override_dh_install:
+ dh_auto_install
+ install -Dm644 /usr/lib/libdnsdist-quiche.so debian/dnsdist/usr/lib/libdnsdist-quiche.so
+ifeq ($(DEB_HOST_ARCH_BITS),32)
+ echo RestrictAddressFamilies is broken on 32bit, removing it from service file
+ perl -ni -e 'print unless /RestrictAddressFamilies/' debian/dnsdist/lib/systemd/system/*.service
+else
+ echo Keeping RestrictAddressFamilies in debian/dnsdist/lib/systemd/system/*.service
+endif
+
+override_dh_installexamples:
+ cp dnsdist.conf-dist dnsdist.conf
+ dh_installexamples
+ rm -f dnsdist.conf
+
+override_dh_installinit:
+ # do nothing here. avoids referencing a non-existant init script.
+
+override_dh_fixperms:
+ dh_fixperms
+ # these files often contain passwords. 640 as it is chowned to root:_dnsdist
+ touch debian/dnsdist/etc/dnsdist/dnsdist.conf
+ chmod 0640 debian/dnsdist/etc/dnsdist/dnsdist.conf
+
+override_dh_builddeb:
+ dh_builddeb -- -Zgzip
--- /dev/null
+3.0 (quilt)
--- /dev/null
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBE5fJpEBEADl7Epp8pxg5ENBY4KM7U/lrxRg33BPDJcZTxqnCLbNCdEOSO1T
+Ej3jWl1HEh236NlWLvHsXgrsKiB1jX037q62QKrp10trQMsM6QiEUjwmrGJxgxv2
+D/+U2PJPh6/ElFhx1PqGEC1Ih3pTpP1YINzfX6cQ9/e3nc64BcBTQqYA2/YIv4pH
+MYXZrPm398JZbPpT0ot9ggdLulUYSRJQ9dfNJbGpstMMfOkA2IFvfmKc5BT5Y/ZA
+ayF7xPBEGbBMLaZuT8q+x5S39ZyzxzCMSIJD7nYAh7qI0xiosfu8YyjXPN3x1OYX
+kdBKzYEk8ji9xgyNZ/9Hlsq3JhJzuGKuXKuC3GKf8BcNw0JH+/VWmq+3kd30a8dy
+GgCW+YJok+zyo51WWVLeJ//5tZ2/GvRhbIivA6Gp2cxQlwl9Ouj7SkBNRWvjBJUr
+N0NF1FxKo1Yq9OECq2KgLn3l2vURW+LUtdXDbZcn9GcYbGHIE0xdCGQSH/H+Dkgl
+T63rQIgBN2MTQ4lhun/5ABLq7s82BAtakhQ5S+3gD+LykCcvCxgHApV28yJJT3ZZ
++Qt6uNtHf2y6T4eJpiE+bWJpG3ujCwzQxu3x5L76jOgiRaj6HcwzT79LpjZMzhnK
+1sKhDAuJP2VNIYhAXn8UF+z54dmBRK58t8zQVop+BpJAE7QM/DFDp3uLhwARAQAB
+tC1QZXRlciB2YW4gRGlqayA8cGV0ZXIudmFuLmRpamtAbmV0aGVybGFicy5ubD6I
+RgQQEQIABgUCUWu5/QAKCRAcXumQ0ucVdWzlAKC1r7xlQ54vi/tOqTDFid0eV+UG
+qwCeKBoxjQUwcICQpz4wi5u6fTDD1AeJAjgEEwECACIFAk5fJpECGwMGCwkIBwMC
+BhUIAgkKCwQWAgMBAh4BAheAAAoJENz1E/p+7RnzoQQQAJjEVUbLcBd4blXL6EW3
+VMqIMFbxBt4CiHRjsSo02+rUMWLOqZBERfynv0oufhrW3AqTO0OMoqPLWjWFNeOH
+OdKieBJdcXHDJPO8qRUpbcYh5CXr54X09d5WZU8sGipnd8wxO68J8g+5vux3xscE
+aZTwWZTwyelWA77OxJm6WlPPxJ+lTyIuhVC3KoBUWRwfNrxE/ij/0tkVFoIXvczb
+AQqB6+nApHZvtoR4Wys4bzmCWuo9PUj0r3+eyjsWEB0A4Ya1bwaJOchubi/Gq99w
+fp71zJC8FcSMWmoGPRnpg6oLpkxC8YreV/16DUgiMnxUPyJAEpb+AH0MMudmp6tn
+UaWBs/hWnpyWPXqjt6wzs7X31X2oj93ANKjnSpglOgUEBKk4GTyOuBo3S+kyXD9W
+W977kyKVtUQf3U5EHUR08UA/DuEJPGDnMa9lujXM17h//iyixa0RhJXX+ZRKRwEA
+Zqj6H8wNayF045JdwMJ6TIePuymV2ltyG5E0M5l5SOc4fELNHJyHvjhi1Fb23lqB
+xNhvdm8+RtwtFz+QtFwihP/cEBMue5lcj5Bkvwx3NERJxoPi/Qe82mLZLaMCdlP+
++jzvSrsVrRWkyw+i08T0+Dp9/V5YoEUkhSfNp1w26FtrFVqC4XpVxtjda32Ipw3a
+ygpOqEkCxNsy3+C1buzr/QK9iQIcBBABAgAGBQJSExvtAAoJEHcKmPCS30+Zo9wQ
+AI+4GsOZCtV1jd8M88KIDl5b0Kh0ogK/pg6orYu0kyDF9W16p5qEn0sTZP2QP4+D
+yZWDfPTe1fxHlSac3KXMTqGtLKDq0xP0WIoqjhSnMRmvhmODNxnODueSL7Jmg8cv
+XKvj7FEYaI+mqgChyikX9JSJdTWiMuQMOC6adGr2EL77e6e3jAaI31OtCTbam+EA
+JFwxpYSlBMop+SemBbeokHHRivEyg2huO6o7m4SprxkvZZWmiT7DmdIGhaPt5CHo
+DbUdUbj8ni2EdSZnYJcCUpPHJqF1FUkwsc8NDH6tiAo7cHsjkrDAx5Waetnt9mRk
+MkG0tUgnULcChcx6NJiG3lhIWNDnj9MLzhA7kDaktvtEwCyuQA8iyWWfOgIABofY
+Jk3zbPG63N1XhWyZ/ic3IbmnWWrEaK2xYDABvTN6s8nOgex0D+kArhsPE+RMWPzF
+9F+2YgrbP7R68ek2+/4SdQNifUloMDJJA38nmxkM0SsLNgbIWLaltw3GwT/0LQ7s
+TtcLLMhl7bkgyYLmmII8MxXPQhvr1oXX17t6fwJLiQjokO0CVw20CT6QeFo4P+pg
+oYkSPn7tFtfkB3sgZhea3Lr545NDpK5Vj/0WxMOYhqEUmgCRjyzmczklyoXPMFD7
+rX5LxEENXUnxkGGkKFB6OIMq7zrheBscB0/wcZcO05pFiQIcBBABAgAGBQJVB+GX
+AAoJEF5QcVvy/+GnMCIQAITJ/73QIgrsUFh6fWGfKMOgY8f2JUfwe5g/vSO2BQPS
+cSgTjoKdpy4DCILI3WzSZ2xzxOlS0SMj8hoDIwQxSydYuZhIfAmlUmaT0Q5p6Zae
+f7/+pFRVkas9CA4NE4V3ZCEhQjVvEI8bXabdld452PE2Fahi6m58JEFwFnU84sII
+sQJCiFFFFj7OxNGGMK63vZFxgE9dhW1kpMGBfxdKLFyglEpll2qGbCp13shFLeZS
+Cg5WJ/pC6R2t0K5tW2XAHz7TRj94dnFTVD8DMlydrBrxYh7DMVaeFLDgepxtT5n8
+yW/RLThHAvg7Qyvie/l5bt8Ukk12ISPv7sY9bYdM0wHWj0913RKbK5Ic22LM3RK3
+My/yXeKMI0u8PTUuCppIiCRhqNjFr23XsaixOYRDSsvo6ca70oCUzyVMJs1nmknF
+oimw9yRhT4bUN5yS30E9jqhgNb06cXUcCM/rPYvVqe2/OoUMYBHjRHqn64uHzvtn
+nrJKAGUk0EqTdqyRCfWzo3+mClCzeyes2P0zzGYzwZ/fo+fIVcT552wWCbJa7KW7
+XcW7CTzWgAucupK7tm9jOezvd2Zt68lROAVRL1F+P2HUQvzLcXVaSqOIHkiySMAK
+fVEBfwA6y2oBjUkBv0oKuSV8xk+cq3B3sxDQra4Vw2MjyyiCrw+piIntgqnSrzTv
+tCxQZXRlciB2YW4gRGlqayA8cGV0ZXIudmFuLmRpamtAcG93ZXJkbnMuY29tPokC
+HAQQAQIABgUCVQfhlwAKCRBeUHFb8v/hp2pbD/9YaX+vGZ8ZJTtXbwmbMQ6ZXC+a
+nWLygPxk6d3DTFfdbSOwYHH0RSaqymhJ84lVypoUP7tZxRBL5pGvBE5i7iZVTAj9
+y+mV95EKeM/bR/k4EYQi6nAgaSRKFnN/BkimfZRFBiV0ox/3TxBjIZFUG7P+0TgY
+/8C8jIpz2Gt1MuC6J6wI+DkUD7mFTjzQSz/HTIQctngcTq4lFvZiP8pcBDt/kg8u
+/ATlJih9LPfixyZo7YIunbj77jLomkKkShsNFQEDPOIWsQotjIYnFb3YK1vgIQEp
+CZZE9ElAEw/6yfXIVyqPzg8ZiP1UOEDQJw7/AVhfb1uiR/US2yPe8xxe+sVaYDgi
+M6VCG1lbun/l79bQEo2tjacOih/Uba5UPOzudseW7XMghwIf+1Y4U1r7HEEbRRd3
+pGqmjBFjGcdnw5XzpZ7KyzwGjie57uf0xzPwpjtIIr+HjiIfRmBuFSx92JvpVieu
+ciGT646F2d4vsxKMNsY89vSoO8dqXEFWOjTabZjk5ZWjz3tCbpqDAgAcj8VVn4XX
+HPM2Rood1mIENYp2IsICS2Js9RRRfxpY5Gk5E7zFXkHvJLfitwu3VKwB2iWP1m8L
+TxQHZFjLN2TPF09yt8qfrCUyuY92HJtoRqJ2X4N2DO0gTrtiCylefdJvkSf8NM4s
+z5OwEW38V/8Drni3S4kCOAQTAQIAIgUCVMofzwIbAwYLCQgHAwIGFQgCCQoLBBYC
+AwECHgECF4AACgkQ3PUT+n7tGfOjhg/+NtAiH9AVGTmbpdNHNyWrmPxgO5XKtfLZ
++4gz6D1QpFwFO/YPL9iN8RhzsZJCestI6tP9vuBPvku7Nd1IFfuZlUmg26gftUTQ
+UmRMd/lMm264WOYPXu12g8+PvkUwXfyoAcd32nOpSMkpiFymRN8GtTzIm4qOgWA2
++mdFWMl0xTSuKv60MiNIszEKD80UxDS2bSj1cv2VBxDFwmlrzPEa/ozxAI9t9CxY
+8Lv6CsEfr5yHSWOkV/mqSo+4OegdNjiRRoeMo0/bUBOtkZSykj2ONSVBn1oIOYQt
+ForUhtRyZFLJIiO11szngRDqRYOslmMMLuZ2k+/b/K4E9jvJvz7yt+Y3BPOsjRtV
+9tP7oSKJpTN43PTnTbKMM2RpVTN3bPtfhZ1+iQubk0y2H2XaKOnKX6wEdjaWOoBR
+6SRzVzSz8dgOqvkfBhA/bJchwWHnyckk7bZJXMszafnkJZzW/eVeuopUgkyWeMHp
+6lngpwpSqCPXn10zm3bBYNSOFdCCX2Qs3hB8fLi8OQGv9puikZmvdIwL1jWP1GEk
+kP94xFTtv4wqgBykySjTMBs9zEDOOGq2x2ndUJZuUpoY7AHBtzmMrfwuuHwyFHfT
+VpalE36S6f7D8UA631vO/BGcDj+5xnAhhTBiiFbworNjAfZs4/bfKam4v5rYb+ri
+z+OJYVa0m9K5Ag0ETl8mkQEQAM4WIsHIK/1+/39QZbh376iVXfc4NVdE3ID/Lozz
+9JDanjkpScpikwugDwguVx+8JdO2tTyo6JTzpiZ+CoaxmjudJpUTT7fD5ONcAd1s
+tpHKUQFwJczU6LSXpTQCpmhV5s13pwumxjymKRlotxLdr9+zxFl0e4VTFb5oj4Ik
+2wu6sehcIt73AxM38C8smFRrRegPQL2Xnq9BE+WUF2yyY3TOVAK5TP2MbwQTkrTO
+iTYJZdNHNlvjIpZaxHKOLqytNXSmXn1k20nitmyssIzv0aEC1UdktWIL/gD1Z+Sj
+rJQB7/y56Dx7o6gr6J2MZZeo7a211TLdblejD6bMjGaH4CTnjzmkMtDC/2b+FUc3
+x3/GlQF4hWB4iaT4aCjiKOVNQgaQyAeRTsv1BUoqf8LDytW1/MdalLYElKS77t69
+HEQ9HSyt7QHU3sjAG6qgso8yWn8ebYCefm1lyZSP3BbvZ/UpoKuB+aGlXjteaXQh
+IRLRA1TgijiGA3Yw1dTcz2Cb42w4UNZw4r55yN60QDRBH4l1yrRPltdyAaX3qEg4
+4U/Z7LU2YTDX+4JL1O4ZE+snDVsTPMpuZLvRFkxCLG1FTXZacZRXfzlFzw6YWhpn
+HUYORO3fGhb+PKMKYEloTyLywjkVLHFbvaPts96dCxWyDrcMOqhgiLOLJo7qC+/S
+q8k9ABEBAAGJAh8EGAECAAkFAk5fJpECGwwACgkQ3PUT+n7tGfNv1A//dYWV+vL1
+jiL+X4vRSCrDM8bBmt/cZfN5O0i3HYPMdSD9lVr9O+WYKJogxEXX1ofgEO74rwZx
+Gw0crrMN8VM9SgMZ3jioGI15NF3INnA1r53GNGhJ4JVnz0KV2NKtshk7CtSxrjoR
+8qplwbMMICVgTIERVP1enuOb3FEtbhI4rcy+2UTw3hwURBhIfUotVFO6SKu3ZLsc
+ItbiNxpTqTpL6AIp9UOrZjcqfCuFs8P+57uusAHcp6GYhhIhNIdXf64RQs7gtdLV
+W71z0diSxu3KFWlrXOx0rrm7RTAQn1VOLl4W5oBPvcF2ZVQvd84I74TMtpP0MRDF
+gLuK0HHFVyDff0vx76rubQgom6z8ajiIa6MfEmd7z9xhQT5PU0FApYY6H/kW7ao+
+f2h2IIjz/+QjHuYn0CqqcjkkLC76RAgQjHYO9NIpL9Gi9O+I2AFz8YjOK3hOpxMr
+F/LjPJtxBXGFEwP4ud+hzDMjwaa7PklcmDPUBuSDIgbNvsVNA6gn7AkbQn6NH+DI
+mdrpzgpSr1FHMbjIWqpXWbAZtmOurxn9f5ZXPKAgMvlV4TS4NZqnWT5HZCKs2b5P
+ed2L+zAdLP5NmyzJrSIyVTJ7JMLLfCLaWu/qsHRGt1w86gewg7uMPdA1IEvjjXaI
+WNhYKUq6ik+DNrq0Y3fUuRg35QHaPTcab+eZAQ0EVjikBwEIAIhTkdGQEbdVwF8l
+qp63Eigp0tHFbdeZ4LCu4sW3oM3erxtO2w25Awkdrw5jRopYmheM5BJsGgpIZUAU
+pOakJR8fi+ESu3wNarKCVF+KjYvdxN7jwZmOI5t1ctnGewg0DHZZtymgJEpON1Zf
+QwfYmD/J/k9Lqdv6CVyVGwNCZUZCO33a/bec12wKnwj2uM/X5tDLmIcHUiJC4Uno
+MFAmGBZDOSxPZrNnzdoAO9zj/4WDtUVhLNkeSn3w1/LNSSJTNiLQjk7Lgq/Khd5L
+8Jf1a1AYzW+NkBdeIP44MnQ68HYSwJRPq3iL2lZaH/4uc21FYhWfw8l5BsIA7bAm
+UzFfbwEAEQEAAbQoUmVtaSBHYWNvZ25lIDxyZW1pLmdhY29nbmVAcG93ZXJkbnMu
+Y29tPokBPQQTAQoAJwUCVrBxMgIbAwUJEswDAAULCQgHBAUVCgkICwUWAgMBAAIe
+AQIXgAAKCRCiCO1PivWERnTOB/4jLvex0M+TE5iL/FUki8EHyj6648sOCHnUHHnS
++slME2b71iAvLJxClDJjLD43Jj7FL0hu2LOnw+5PQZrhLyB1WEa1tC0tLvIkPuzC
+VJPI4FH7+AegmBrGYN6554Hy0C/YRF8mOGngL58hrumJTgjB7vC+CvDp0714WQG/
+SgcKqk4jkIz/Iep2vj3dCifdh+kJkaK/nnzIT1euiOzp8xLByiVbCOdlbvYoVetq
+vJcqIhOHCglv045lZcAp9kP9pm/kEzHM34PhkH6SrR/uodshOH4p3Ux0wGgwUbou
+DvHUtjlK+GB8cYXdRny0tvdGBYUO7CsFNzPoRC8CvD+VY8DltC1HYWNvZ25lLCBS
+ZW1pIDxyZW1pLmdhY29nbmVAb3Blbi14Y2hhbmdlLmNvbT6JATEEEwECABsFAlY4
+pAcCGwMECwkIBwYVCAIJCgsFCRLMAwAACgkQogjtT4r1hEbMMAf/WS0+yuheoWrx
+CZ4qYQo+AjlaenFTPQwrEDNioj6gjST/eAaQW1/+trFPzwNrBSenDE6bwPcPdL51
+mXg+30fNzHLWrBPDsMqBlPTIvpBbQ/bVqjV3JnU8I8dHfdKmInJRrCJM21gDTprQ
+dqfBfSHJHgM5TG2+fUxpdLIAhBRknXt4+TuE272DJf6gHxnDs1oqQ6kAxC0ANJyE
+ufFXJGeERN2OsFtSygOcUiHeXwWyM77RGf73gkS9+bCoftiuM4gbKSibk4BbUVBZ
+JCs28fDnAsmIstZldUGZgIuy0vUfH153DTJflN+CIGEvRUwk+nrDIwYkV0pr9eZ0
+lz/OFhwzJ7kBDQRWOKQHAQgAjr1xEZh1yglszi94+HLNFcgRPgRNktg2vxOGf64d
+AreJvL5iDrS2lrFMknh5BNuj7nJZ2r40OOS91oH1qkVk+v9Cyo/3xwCpCOPQCkhz
+HpuQWXoMGMw/3/0tG6zTxnYdC999faCH0lLA8oDwHCHlZSHgsH9+qSNyjaJXvS+H
+VoGYzyuanU6OTM7EM5c7RCPhNjT9JzHLISnwaxgDpwi7Ez6yudcrg6DqS/uUwkyN
+tWyesx1DF9y2VJUNwa4NKIJkSH+niEoxK9NBfBAmAKc4o5+KPs6BvpvpiYY9gTKa
+aLypPHNcveQTDFv/26XHyzrCZmwuGlcYBjboH/BWzKbhuQARAQABiQExBBgBAgAb
+BQJWOKQHAhsMBAsJCAcGFQgCCQoLBQkSzAMAAAoJEKII7U+K9YRGXJQH/3PtQG0A
+krXOpkOMXFLTKdCEViNNHN94VIaceVn60zbmXzxhYeKz7K345/EqATi3P3/yDHch
+t7j3uYPhvaMjy3smN6vEwX7Ue40PbFDWmm8mHpLdlOfPXF0SRUD8KTSD6+W2VJfE
+cDI6DDfUmCx9yYZ1U5u+O8Aj+1l2gdQbgAioPnQgqzf43qgnRcsfNmsVsXg7EbHs
+pRpJOR1XyXl/9KrDP7p6kjwWTQ1NoRjCw0qaX93odLeKIpd2riShlB7GteUTps0I
+fuiL94CA58PV2YvZapN1KmwDohHU8rndN7zte7jbCyv1Vv9tP6Ns0TvycBAqlOZY
+dgabrT+Pccb4jCeZAg0EVPRvsgEQAMeXMm92zU2ooQOE4AbQhYY3gn+MG87l088W
+rAMlpTfbH7jBPDQ47EJyAVh3NY+XXucXCMLzJ5e9sAIJk3PrYqDmjWVYDox3Hx5r
+MKIY65N1Rud1kMGWsgQCzU5RmarFNLJ0OdpE0K0tMTajS3gxqJ1zOOKdSfZGS+u2
++UKyLUelB07mZROv9uanu/ia8I/m8RG5jb6pVzUpuoWW0J5XQoA6mvWREbJDgP0s
+WWgWSvt+0XRtrcHR9sie7a4ynjowL6M+iTm4ShPrqX5TuxmwJSQcfTZjqz+5cnpp
+yTzj+mG2/jHBERGWkL3sx37s3uohhWt2EZVuyIcUQMigGssCp9216K+ihyC4tEj8
+RSfbon35t7OGYJlRS7V/raIm4GdYLCOkQs0yUIila6AcC5xpRnHXHIXvUNHrk1nA
+yz5PEER/6BiW+vMObonx+GR8oUfo+2uMg4LSYKx+o5jgWBFiSdNl3gQK5+RswpdF
+fzyq9gZf4WvVOCdBH0YKEQ8iJYX17drkn4OWMG7u0QnQs6GdZTcClQJMTWVBIaCt
+RMNCxuKX6XI9WOgwj6vHM/ijeugPLOsUSv+uWcK/fH7SSqmtJdMmpwNKFUmr4ZTa
+WZ5QLLv6kXbIoKqEKFwXkJAPgm36mVEE+ruy3FoNl0um9S0W8tGXgs7pMmM0AZ/n
+Hb6R++6lABEBAAG0KFBpZXRlciBMZXhpcyA8cGlldGVyLmxleGlzQHBvd2VyZG5z
+LmNvbT6JAhwEEAECAAYFAlT0dSoACgkQ3PUT+n7tGfPs5RAAlQXWPO+ZJNjCLFcu
+AKEyNfNl5ssCVykUmQzfem8Y/Z8NojoMkb1mBZlt1OIItYOcpB+bGiH2fnY4WUjd
+s1y29mPThrx8vpm4QY0/lxw+h89IrQrdXcHXS9KvedZfWCnseEMZo/xwGVCBiyDX
+LSFv7RwugnJr6VeLg7oYrGyGNgIeiax66OFJ7cWxKX7zG7Z3hfZ2YFM/djCyts2A
+Lgb8WKEd1+xaINpmXLIBZb6oKA7JrPHjGHXCtiOFccyONmW4ukcGOsPrn0Wm1rAX
+IbHl/sxC/KIONdPSjktuY9uOW8DtinW55eAMxlX91CHpX9XK3WENAqkkSL9hu8ov
+JPXrz58XdqHMKdgwGQdYXcUY8lU7YX1dXmp2th/8kMkSQXcqYv/LmbRFRwq7pd8A
+6U3LsIT9JIHC1LcTxyIaKZBQegHvtVm4oDOBBDJ9ImBH53FJhJ9hModrG5Hcfmwr
+nquITLicuBgMM2SgZmH/ykeDpO3atQrCSDzlozFR6SKsssnfmIrbyUJzrsJ6tuz1
+/3kNosdPuFaHIxLlNawAhUkJL4CrTRLcWuknJo5OOgCtYWRGcCBQj9iS10EZDcvg
+uLsotQbTvdeE5T+o97gaF0p7ww6XaFcII9HLUrEjMLQFSFmHAjyxdm1Esh8I8yEA
+oR3FXtdNwgs0CBuR2bY399oT26qJAj4EEwECACgFAlT0b7ICGwMFCQlmAYAGCwkI
+BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEF5QcVvy/+GnS1YQAMbqFxpsYGMsVRpw
+rFZ1/NXyfekLk7XCWD++YgwZ10d8jXQAv0BHl5ubkpCEuZdb7SqEkLgKbjRfw4KD
+qri7iDGjwoYckmqh1xl20qyTYkfeQnKizuBWmNsVL0JT0xUzKhcHBh1bWx4FPF+i
+ojOlZQLKwViPpGOacstlBcPfRPQhaP7QLKrOVvVQd0ebfbbsjSBP+olik6OWRiyX
+iIfsAmGq0OFDtk+f7jI2UIMC+/ADpulzMmJdr8l28wgoudtVM+w4LB6hbFOYSvVy
++kcMqWI+yQ64Dy6OnFVI5cZH3jxQXviqEs+QNzM4jUQ37Kp3TPBEDvHNQkdYllk7
+E24ecF/bi/3kDISBfVobSdMGMERxPJhnNWCG3sEP+WPT5beDKywcWX4kOHpmhL3w
+RxJbZzEusA7IXyeeb2AWfsJBdMtM5Ur2u4yW9Dq9uNFDvXvo4mQXqM0aPmpKN5Ir
+SzTBT/9bKvu6iOI+JBiEY/A5wCySrqIsjqeJ5Wac6dPCcNsxLV1qdEVMV2eg86A/
+lbymPiQg5xJzF6RSFwmVpbye7HFqcKTLwbZJHzf46CpgFyK0Wuavp90Nxrr0oTaH
+xfnAHlTDdXMybm3Z7wlEXEg2hSh5rrTKYfjgtoIJRkCjaRh40jRkIM0YLx5uR5PB
+k6hAzjik/7sYvgG5XuPra3L6Y8XGuQINBFT0b7IBEACjecdg3e1IF3zBMadFbFah
+7ZSPFK4Y8Q+OMbeiu0TzXP65rRXDQi595jdIcQY6/7gB1IguqC0HWUo/Ns7GFnNn
+WbrAaoVWpLjHXgMJ9hqdyIEgluoJECH53d0Y73oi+PBoYUU5z2tHi7AiiJc9qMG4
+m9q2P7xUrnqCqmGO4pU9nFJTFUAodf/ioNk9EdmciLmFUm7XkHNtUcKVQGWER7vi
+dedWLW1fhHAzhI1hYkN85ZfIULfrVNZBn1U/L4nry7P7HO0IQxoK7POs6apxU4Jy
+ATEyvsnjYU+UOCDPXRIKHAZ4joEnFhyHPyURgdMLxQb0s1hnbTEC+szvqb9kC0rC
+an1GRb6/VeW9eRi1CoBpHtQEwY6k+YgWpvcfR0w9+6BH5aqypGWnNDCWcOTINUro
+uALb68oxgnEAowhWIa0ujUYy+PMYF0AFArjLVxu1IBKaMD/Wsk0ws389xAnbVW81
+bhHN2Ye2NznDe3YfK5FkUyWXO6GA1tFQw+joxt6+TPcTxRJLS/MG/gXcluQE3Kv+
+jteqi/dbt5A+potX6qGN+F1GJwD/mQKyULklzlcZCIYZN9OnKVbSxfn2xQ89bjvk
+NvRjuO33x0IozIr/R/uz4T0H9Ve4UoNj2vT4pH/Ba/ergQSfrrAJMDyIB+SRIgY7
+LCQFB3rOIvg/HiqAY3VL1wARAQABiQIlBBgBAgAPBQJU9G+yAhsMBQkJZgGAAAoJ
+EF5QcVvy/+Gng+UP/0CjLMF30xjRim/+/qzx+2OZ1S6R6B2mp971lQxB8gCA7dn8
+0UhSZZMHfMeo2N34itI4HEhQb+2jTOgQvNjv36zMppZjHQUg4+xabvZU33FrB2hh
+D/ZdNTm7lCD87vKxz07flApkscw20VenY8E81z15GuWLK/UqE9wK5sbVoFB36mwN
+Fqgh8W3oBBJTJoxWBFnqZu7arsaXhEWpVW2+36I2SWaWFmIPwrUbuwIXSuv+h+ks
+EOdVXr87AgxX4qsPs44N6z57yhCIz6g3ow7R06IolsDSE8wyOWL313X6UE1R5Qyq
+AX1yQ0BtmcPRh05SC/vRe7WeP2q+TyHMrM1/YLN/W9X4Y7loPL38fpmWMEaNT/Rg
+ZPFjqDbqxYpyN6Kymdsfr3YPYNrzcYlc0WRplvpZh3D67PhI9XuRsb2c7GAU1jVz
+e9Za1ZrPpcCCJgp562I9E1D+a4x7w9fsiGDkPOm5Iy3HTg9FH8VUWM3uwret74Zl
+QyQkE1XYgRjqHrjqJOajJg8Qw4meItY0QB/5kxmAW1h96OoKBUZq5GaQ8AhtPnH+
+4peGQHG9fvSL58pukqeLGHkSwgdMPIFYZTHiIDt2tVkbi7vE3uvKPm1bZpvM2T6m
+9ZUkVWV39P1W9lkqWvXSVfit1GRUpFd2onM7Rs0jxbZ9VfiRi2OblZ9Wkvts
+=/oKT
+-----END PGP PUBLIC KEY BLOCK-----
--- /dev/null
+# Site Directory Pattern Version Script
+version=3
+opts="pgpsigurlmangle=s/$/.asc/,versionmangle=s/-/~/" https://downloads.powerdns.com/releases/ dnsdist-([0-9]+.*)\.tar\.bz2 debian uupdate
libedit-dev,
libfstrm-dev,
libgnutls28-dev,
- libh2o-evloop-dev,
liblmdb-dev,
libluajit-5.1-dev [!arm64 !s390x],
liblua5.3-dev [arm64 s390x],
Copyright: 2011-2014 by Shutterstock Images, LLC
License: Expat
+Files: */libdnsdist-quiche.so
+Copyright: 2018-2019, Cloudflare, Inc.
+License: BSD-2-clause
+
License: Unlicense
This is free and unencumbered software released into the public domain.
.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+License: BSD-2-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
License: BSD-3
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
dh_auto_clean
override_dh_auto_configure:
- # LIBS has been added because Ubuntu Bionic and Cosmic don't have the fix for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=908124 pulled in
- LIBS='-lwslay' \
./configure \
--host=$(DEB_HOST_GNU_TYPE) \
--build=$(DEB_BUILD_GNU_TYPE) \
--libexecdir='$${prefix}/lib' \
--enable-lto=thin \
--enable-dns-over-https \
+ --enable-dns-over-quic \
+ --enable-dns-over-http3 \
--enable-dns-over-tls \
--enable-dnscrypt \
--enable-dnstap \
+ --with-ebpf \
--with-gnutls \
+ --with-h2o \
--with-net-snmp \
--with-libcap \
--with-libsodium \
+ --with-quiche \
--with-re2 \
- --with-ebpf \
--with-service-user='_dnsdist' \
--with-service-group='_dnsdist' \
- $(CONFIGURE_ARGS)
+ $(CONFIGURE_ARGS) \
+ PKG_CONFIG_PATH=/opt/lib/pkgconfig
override_dh_auto_build-arch:
dh_auto_build -- V=1
override_dh_install:
dh_auto_install
+ install -Dm644 /usr/lib/libdnsdist-quiche.so debian/dnsdist/usr/lib/libdnsdist-quiche.so
ifeq ($(DEB_HOST_ARCH_BITS),32)
echo RestrictAddressFamilies is broken on 32bit, removing it from service file
perl -ni -e 'print unless /RestrictAddressFamilies/' debian/dnsdist/lib/systemd/system/*.service
install -d debian/pdns-recursor/usr/share/pdns-recursor/snmp
install -m 644 -t debian/pdns-recursor/usr/share/pdns-recursor/snmp RECURSOR-MIB.txt
rm -f debian/pdns-recursor/etc/powerdns/recursor.conf-dist
+ rm -f debian/pdns-recursor/etc/powerdns/recursor.yml-dist
./pdns_recursor --no-config --config=default | sed \
-e 's!^# config-dir=.*!config-dir=/etc/powerdns!' \
-e 's!^# hint-file=.*!&\nhint-file=/usr/share/dns/root.hints!' \
-FROM alpine:3.10 as pdns-authoritative
+FROM alpine:3.18 as pdns-authoritative
ARG BUILDER_CACHE_BUSTER=
RUN apk add --no-cache gcc g++ make tar autoconf automake protobuf-dev lua-dev \
libtool file boost-dev curl openssl-dev ragel python3 \
- flex bison git
+ flex bison git bash
# the pdns/ dir is a bit broad, but who cares :)
ADD configure.ac Makefile.am COPYING INSTALL NOTICE README /pdns-authoritative/
@ENDIF
@IF [ -n "$M_dnsdist$M_all" ]
+RUN mkdir /libh2o && cd /libh2o && \
+ apt-get update && apt-get install -y cmake curl libssl-dev zlib1g-dev && \
+ curl -f -L https://github.com/PowerDNS/h2o/archive/refs/tags/v2.2.6+pdns2.tar.gz | tar xz && \
+ CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6-pdns2 && \
+ make install
+
RUN builder/helpers/build-debs.sh dnsdist-${BUILDER_VERSION}
RUN mv dnsdist*.deb /dist; mv dnsdist*.ddeb /dist || true
@ENDIF
+
+# Generate provenance
+RUN apt-get install -y python-apt || apt-get install -y python3-apt
+@EVAL RUN python2 builder/helpers/generate-deb-provenance.py /dist/packages-${BUILDER_TARGET}.json || python3 builder/helpers/generate-deb-provenance.py /dist/packages-${BUILDER_TARGET}.json
FROM dist-base as package-builder
ARG APT_URL
-RUN DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends devscripts dpkg-dev build-essential python3-venv equivs
+RUN DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends devscripts dpkg-dev build-essential python3-venv equivs curl
RUN mkdir /dist /pdns
WORKDIR /pdns
ADD builder/helpers/ /pdns/builder/helpers/
+ADD builder-support/helpers/ /pdns/builder-support/helpers/
+
+@IF [ -n "$M_recursor$M_all" ]
+RUN /pdns/builder-support/helpers/install_rust.sh
+@ENDIF
+
+@IF [ -n "$M_dnsdist$M_all" ]
+RUN /pdns/builder-support/helpers/install_rust.sh
+RUN DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends git cmake clang
+RUN /pdns/builder-support/helpers/install_quiche.sh
+@ENDIF
# Used for -p option to only build specific packages
ARG BUILDER_PACKAGE_MATCH
@IF [ -n "$M_authoritative$M_all" ]
RUN tar xvf /sdist/pdns-${BUILDER_VERSION}.tar.bz2
+# create copy of source tarball with name that dpkg-source requires
+RUN cp /sdist/pdns-${BUILDER_VERSION}.tar.bz2 pdns_${BUILDER_VERSION}.orig.tar.bz2
@ENDIF
@IF [ -n "$M_recursor$M_all" ]
RUN tar xvf /sdist/pdns-recursor-${BUILDER_VERSION}.tar.bz2
+# create copy of source tarball with name that dpkg-source requires
+RUN cp /sdist/pdns-recursor-${BUILDER_VERSION}.tar.bz2 pdns-recursor_${BUILDER_VERSION}.orig.tar.bz2
@ENDIF
@IF [ -n "$M_dnsdist$M_all" ]
RUN tar xvf /sdist/dnsdist-${BUILDER_VERSION}.tar.bz2
+# create copy of source tarball with name that dpkg-source requires
+RUN cp /sdist/dnsdist-${BUILDER_VERSION}.tar.bz2 dnsdist_${BUILDER_VERSION}.orig.tar.bz2
@ENDIF
-FROM alpine:3.10 as dnsdist
+FROM alpine:3.18 as dnsdist
ARG BUILDER_CACHE_BUSTER=
RUN apk add --no-cache gcc g++ make tar autoconf automake protobuf-dev lua-dev \
- libtool file boost-dev ragel python3 git libedit-dev
+ libtool file boost-dev ragel python3 git libedit-dev bash
ADD builder/helpers/set-configure-ac-version.sh /dnsdist/builder/helpers/
ADD COPYING /dnsdist/
-FROM alpine:3.10 as pdns-recursor
+FROM alpine:3.18 as pdns-recursor
ARG BUILDER_CACHE_BUSTER=
RUN apk add --no-cache gcc g++ make tar autoconf automake protobuf-dev lua-dev \
libtool file boost-dev curl openssl-dev ragel python3 \
- flex bison git
+ flex bison git bash
ADD COPYING NOTICE /pdns-recursor/
@EXEC sdist_dirs=(build-aux m4 pdns ext docs)
ADD builder-support/gen-version /pdns-recursor/pdns/recursordist/builder-support/gen-version
WORKDIR /pdns-recursor/pdns/recursordist
+ADD builder-support/helpers/ /pdns/builder-support/helpers/
+RUN /pdns/builder-support/helpers/install_rust.sh
+
RUN mkdir /sdist
ARG BUILDER_VERSION
FROM dist-base as package-builder
-RUN touch /var/lib/rpm/* && \
- yum upgrade -y && \
- yum install -y rpm-build rpmdevtools python3 "@Development Tools"
+RUN touch /var/lib/rpm/* && if $(grep -q 'release 7' /etc/redhat-release); then \
+ yum upgrade -y && \
+ yum install -y rpm-build rpmdevtools python2 python3 curl "@Development Tools"; \
+ else \
+ yum upgrade -y && \
+ yum install --allowerasing -y rpm-build rpmdevtools python3 curl "@Development Tools"; \
+ fi
RUN mkdir /dist /pdns
WORKDIR /pdns
# Only ADD/COPY the files you really need for efficient docker caching.
ADD builder/helpers/ /pdns/builder/helpers/
+ADD builder-support/helpers/ /pdns/builder-support/helpers/
+
+@IF [ -n "$M_recursor$M_all" ]
+RUN /pdns/builder-support/helpers/install_rust.sh
+@ENDIF
+
+@IF [ -n "$M_dnsdist$M_all" ]
+RUN /pdns/builder-support/helpers/install_rust.sh
+# We do not build Quiche (DNS over QUIC support) on el-7 because the clang
+# version is too old to build the 'boring-sys' crate needed by Quiche
+RUN if ! $(grep -q 'release 7' /etc/redhat-release); then \
+ yum install -y git cmake clang; \
+ /pdns/builder-support/helpers/install_quiche.sh; \
+ fi
+@ENDIF
# Used for -p option to only build specific spec files
ARG BUILDER_PACKAGE_MATCH
# this is fine because --allowerasing is only there to deal with libcurl conflicting with libcurl-minimal on some el9 images
RUN touch /var/lib/rpm/* && mkdir /libh2o && cd /libh2o && \
yum install -y --allowerasing curl libcurl openssl-devel cmake || yum install -y curl libcurl openssl-devel cmake && \
- curl -L https://github.com/h2o/h2o/archive/v2.2.6.tar.gz | tar xz && \
- CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6 && \
+ curl -f -L https://github.com/PowerDNS/h2o/archive/refs/tags/v2.2.6+pdns2.tar.gz | tar xz && \
+ CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6-pdns2 && \
make install
RUN touch /var/lib/rpm/* && if $(grep -q 'release 7' /etc/redhat-release); then \
fi
@ENDIF
+# Generate provenance
+@IF [ "${BUILDER_TARGET}" = "el-7" -o "${BUILDER_TARGET}" = "centos-7" ]
+@EVAL RUN python builder/helpers/generate-yum-provenance.py /dist/packages-${BUILDER_TARGET}.json || python3 builder/helpers/generate-yum-provenance.py /dist/packages-${BUILDER_TARGET}.json
+@ENDIF
+@IF [ "${BUILDER_TARGET}" != "el-7" -a "${BUILDER_TARGET}" != "centos-7" ]
+@EVAL RUN python builder/helpers/generate-dnf-provenance.py /dist/packages-${BUILDER_TARGET}.json || python3 builder/helpers/generate-dnf-provenance.py /dist/packages-${BUILDER_TARGET}.json
+@ENDIF
+
# mv across layers with overlay2 is buggy in some kernel versions (results in empty dirs)
# See: https://github.com/moby/moby/issues/33733
#RUN mv /root/rpmbuild/RPMS/* /dist/
@ENDIF
@IF [ -n "$M_dnsdist$M_all" ]
-ADD builder-support/debian/dnsdist/debian-buster/ dnsdist-${BUILDER_VERSION}/debian/
+ADD builder-support/debian/dnsdist/debian-bookworm/ dnsdist-${BUILDER_VERSION}/debian/
@ENDIF
@INCLUDE Dockerfile.debbuild
# First do the source builds
@INCLUDE Dockerfile.target.sdist
-@IF [ ${BUILDER_TARGET} = ubuntu-bionic ]
-FROM ubuntu:bionic as dist-base
+@IF [ ${BUILDER_TARGET} = debian-trixie ]
+FROM debian:trixie as dist-base
@ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-bionic-amd64 ]
-FROM amd64/ubuntu:bionic as dist-base
+@IF [ ${BUILDER_TARGET} = debian-trixie-amd64 ]
+FROM amd64/debian:trixie as dist-base
@ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-bionic-arm64 ]
-FROM arm64v8/ubuntu:bionic as dist-base
+@IF [ ${BUILDER_TARGET} = debian-trixie-arm64 ]
+FROM arm64v8/debian:trixie as dist-base
@ENDIF
+
ARG BUILDER_CACHE_BUSTER=
ARG APT_URL
RUN apt-get update && apt-get -y dist-upgrade
@ENDIF
@IF [ -n "$M_dnsdist$M_all" ]
-ADD builder-support/debian/dnsdist/debian-buster/ dnsdist-${BUILDER_VERSION}/debian/
+ADD builder-support/debian/dnsdist/debian-bookworm/ dnsdist-${BUILDER_VERSION}/debian/
@ENDIF
@INCLUDE Dockerfile.debbuild
--- /dev/null
+Dockerfile.target.debian-trixie
\ No newline at end of file
--- /dev/null
+Dockerfile.target.debian-trixie
\ No newline at end of file
# Sphinx
-FROM ubuntu:bionic as pdns-docs
+FROM ubuntu:jammy as pdns-docs
RUN apt-get update && apt-get -y dist-upgrade && apt-get -y --no-install-recommends install \
ghostscript \
git \
latexmk \
make \
- python-minimal \
- python2.7 \
+ python3-minimal \
texlive \
texlive-font-utils \
texlive-fonts-extra \
@INCLUDE Dockerfile.dnsdist
@ENDIF
-FROM alpine:3.10 as sdist
+FROM alpine:3.18 as sdist
ARG BUILDER_CACHE_BUSTER=
@IF [ -z "$M_authoritative$M_recursor$M_dnsdist$M_all" ]
+++ /dev/null
-Dockerfile.target.ubuntu-bionic
\ No newline at end of file
+++ /dev/null
-Dockerfile.target.ubuntu-bionic
\ No newline at end of file
+++ /dev/null
-Dockerfile.target.ubuntu-kinetic
\ No newline at end of file
+++ /dev/null
-Dockerfile.target.ubuntu-kinetic
\ No newline at end of file
--- /dev/null
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+@IF [ ${BUILDER_TARGET} = ubuntu-mantic ]
+FROM ubuntu:mantic as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = ubuntu-mantic-amd64 ]
+FROM amd64/ubuntu:mantic as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = ubuntu-mantic-arm64 ]
+FROM arm64v8/ubuntu:mantic as dist-base
+@ENDIF
+
+ARG BUILDER_CACHE_BUSTER=
+ARG APT_URL
+RUN apt-get update && apt-get -y dist-upgrade
+
+@INCLUDE Dockerfile.debbuild-prepare
+
+@IF [ -n "$M_authoritative$M_all" ]
+ADD builder-support/debian/authoritative/debian-buster/ pdns-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@IF [ -n "$M_recursor$M_all" ]
+ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@IF [ -n "$M_dnsdist$M_all" ]
+ADD builder-support/debian/dnsdist/debian-bookworm/ dnsdist-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@INCLUDE Dockerfile.debbuild
+
+# Do a test install and verify
+# Can be skipped with skiptests=1 in the environment
+# @EXEC [ "$skiptests" = "" ] && include Dockerfile.debtest
--- /dev/null
+Dockerfile.target.ubuntu-mantic
\ No newline at end of file
--- /dev/null
+Dockerfile.target.ubuntu-mantic
\ No newline at end of file
# First do the source builds
@INCLUDE Dockerfile.target.sdist
-@IF [ ${BUILDER_TARGET} = ubuntu-kinetic ]
-FROM ubuntu:kinetic as dist-base
+@IF [ ${BUILDER_TARGET} = ubuntu-noble ]
+FROM ubuntu:noble as dist-base
@ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-kinetic-amd64 ]
-FROM amd64/ubuntu:kinetic as dist-base
+@IF [ ${BUILDER_TARGET} = ubuntu-noble-amd64 ]
+FROM amd64/ubuntu:noble as dist-base
@ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-kinetic-arm64 ]
-FROM arm64v8/ubuntu:kinetic as dist-base
+@IF [ ${BUILDER_TARGET} = ubuntu-noble-arm64 ]
+FROM arm64v8/ubuntu:noble as dist-base
@ENDIF
ARG BUILDER_CACHE_BUSTER=
ARG APT_URL
RUN apt-get update && apt-get -y dist-upgrade
+# FIXME: Package usrmerge missing sha256 str
+RUN apt-get purge -y usrmerge
@INCLUDE Dockerfile.debbuild-prepare
--- /dev/null
+Dockerfile.target.ubuntu-noble
\ No newline at end of file
--- /dev/null
+Dockerfile.target.ubuntu-noble
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+set -v
+set -e
+
+readonly QUICHE_VERSION='0.20.1'
+readonly QUICHE_TARBALL="${QUICHE_VERSION}.tar.gz"
+readonly QUICHE_TARBALL_URL="https://github.com/cloudflare/quiche/archive/${QUICHE_TARBALL}"
+readonly QUICHE_TARBALL_HASH='9c460d8ecf6c80c06bf9b42f91201ef33f912e2615a871ff2d0e50197b901c71'
+
+INSTALL_PREFIX=/usr
+SOEXT=so
+if [ $(uname) = Darwin ]; then
+ if [ $(id -u) = 0 ]; then
+ echo Do not run as root on macOS
+ exit 1
+ fi
+ INSTALL_PREFIX="${HOMEBREW_PREFIX}"
+ SOEXT=dylib
+fi
+
+cd /tmp
+echo $0: Downloading $QUICHE_TARBALL
+curl -L -o "${QUICHE_TARBALL}" "${QUICHE_TARBALL_URL}"
+# Line below should echo two spaces between digest and name
+echo "${QUICHE_TARBALL_HASH}" "${QUICHE_TARBALL}" | sha256sum -c -
+tar xf "${QUICHE_TARBALL}"
+cd "quiche-${QUICHE_VERSION}"
+RUST_BACKTRACE=1 cargo build --release --no-default-features --features ffi,boringssl-boring-crate --package quiche
+
+install -m644 quiche/include/quiche.h "${INSTALL_PREFIX}"/include
+install -m644 target/release/libquiche.${SOEXT} "${INSTALL_PREFIX}"/lib/libdnsdist-quiche.${SOEXT}
+
+if [ $(uname) = Darwin ]; then
+ install_name_tool -id "${INSTALL_PREFIX}"/lib/libdnsdist-quiche.${SOEXT} "${INSTALL_PREFIX}"/lib/libdnsdist-quiche.${SOEXT}
+fi
+
+if [ ! -d "${INSTALL_PREFIX}"/lib/pkgconfig/ ]; then
+ mkdir "${INSTALL_PREFIX}"/lib/pkgconfig/
+fi
+install -m644 /dev/stdin "${INSTALL_PREFIX}"/lib/pkgconfig/quiche.pc <<PC
+# quiche
+Name: quiche
+Description: quiche library
+URL: https://github.com/cloudflare/quiche
+Version: ${QUICHE_VERSION}
+Cflags: -I${INSTALL_PREFIX}/include
+Libs: -L${INSTALL_PREFIX}/lib -ldnsdist-quiche
+PC
+
+cd ..
+rm -rf "${QUICHE_TARBALL}" "quiche-${QUICHE_VERSION}"
--- /dev/null
+#!/bin/sh
+
+set -e
+
+ARCH=$(arch)
+
+# Default version
+RUST_VERSION=rust-1.75.0-$ARCH-unknown-linux-gnu
+
+if [ $# -ge 1 ]; then
+ RUST_VERSION=$1
+ shift
+fi
+
+SITE=https://downloads.powerdns.com/rust
+RUST_TARBALL=$RUST_VERSION.tar.gz
+
+SHA256SUM_x86_64=473978b6f8ff216389f9e89315211c6b683cf95a966196e7914b46e8cf0d74f6
+SHA256SUM_aarch64=30828cd904fcfb47f1ac43627c7033c903889ea4aca538f53dcafbb3744a9a73
+
+NAME=SHA256SUM_$ARCH
+eval VALUE=\$$NAME
+if [ -z "$VALUE" ]; then
+ echo "$0: No SHA256 defined for $ARCH" > /dev/stderr
+ exit 1
+fi
+
+# Procedure to update the Rust tarball:
+# 1. Download tarball and signature (.asc) file from
+# https://forge.rust-lang.org/infra/other-installation-methods.html "Standalone installers" section
+# 2. Import Rust signing key into your gpg if not already done so
+# 3. Run gpg --verify $RUST_TARBALL.asc and make sure it is OK
+# 4. Run sha256sum $RUST_TARBALL and set SHA256SUM above, don't forget to update RUST_VERSION as well
+# 5. Make $RUST_TARBALL available from https://downloads.powerdns.com/rust
+#
+cd /tmp
+echo $0: Downloading $RUST_TARBALL
+
+curl -f -o $RUST_TARBALL $SITE/$RUST_TARBALL
+# Line below should echo two spaces between digest and name
+echo $VALUE" "$RUST_TARBALL | sha256sum -c -
+tar -zxf $RUST_TARBALL
+cd $RUST_VERSION
+./install.sh --prefix=/usr
+cd ..
+rm -rf $RUST_TARBALL $RUST_VERSION
BuildRequires: fstrm-devel
%systemd_requires
%endif
+%if 0%{?rhel} >= 8
+BuildRequires: libbpf-devel
+BuildRequires: libxdp-devel
+%endif
%description
dnsdist is a high-performance DNS loadbalancer that is scriptable in Lua.
%prep
%autosetup -p1 -n %{name}-%{getenv:BUILDER_VERSION}
-# run as dnsdist user
-sed -i '/^ExecStart/ s/dnsdist/dnsdist -u dnsdist -g dnsdist/' dnsdist.service.in
-
%build
%if 0%{?rhel} < 8
export CPPFLAGS=-I/usr/include/boost169
--enable-unit-tests \
--enable-lto=thin \
--enable-dns-over-tls \
+ --with-h2o \
%if 0%{?suse_version}
--disable-dnscrypt \
--without-libsodium \
--enable-systemd --with-systemd=%{_unitdir} \
--without-net-snmp
%endif
-%if 0%{?rhel} >= 7
- --with-gnutls \
+%if 0%{?rhel} >= 7 || 0%{?amzn} == 2023
--enable-dnstap \
- --with-lua=%{lua_implementation} \
- --with-libcap \
- --with-libsodium \
- --enable-dnscrypt \
--enable-dns-over-https \
--enable-systemd --with-systemd=%{_unitdir} \
+ --with-gnutls \
+ --with-libcap \
+ --with-lua=%{lua_implementation} \
--with-re2 \
+%if 0%{?amzn} != 2023
+ --enable-dnscrypt \
+ --with-libsodium \
--with-net-snmp \
- PKG_CONFIG_PATH=/opt/lib64/pkgconfig
+%endif
+%if 0%{?rhel} >= 8 || 0%{?amzn} == 2023
+ --enable-dns-over-quic \
+ --enable-dns-over-http3 \
+ --with-quiche \
+%endif
+ PKG_CONFIG_PATH=/usr/lib/pkgconfig:/opt/lib64/pkgconfig
%endif
make %{?_smp_mflags}
%install
%make_install
install -d %{buildroot}/%{_sysconfdir}/dnsdist
+%if 0%{?rhel} >= 8 || 0%{?amzn} == 2023
+install -Dm644 /usr/lib/libdnsdist-quiche.so %{buildroot}/%{_libdir}/libdnsdist-quiche.so
+%endif
%{__mv} %{buildroot}%{_sysconfdir}/dnsdist/dnsdist.conf-dist %{buildroot}%{_sysconfdir}/dnsdist/dnsdist.conf
chmod 0640 %{buildroot}/%{_sysconfdir}/dnsdist/dnsdist.conf
-sed -i "s,/^\(ExecStart.*\)dnsdist\(.*\)\$,\1dnsdist -u dnsdist -g dnsdist\2," %{buildroot}/%{_unitdir}/dnsdist.service
-sed -i "s,/^\(ExecStart.*\)dnsdist\(.*\)\$,\1dnsdist -u dnsdist -g dnsdist\2," %{buildroot}/%{_unitdir}/dnsdist@.service
%pre
getent group dnsdist >/dev/null || groupadd -r dnsdist
%{!?_licensedir:%global license %%doc}
%doc README.md
%{_bindir}/*
+%if 0%{?rhel} >= 8 || 0%{?amzn} == 2023
+%define __requires_exclude libdnsdist-quiche\\.so
+%{_libdir}/libdnsdist-quiche.so
+%endif
%{_mandir}/man1/*
%dir %{_sysconfdir}/dnsdist
-%config(noreplace) %{_sysconfdir}/%{name}/dnsdist.conf
+%attr(-, root, dnsdist) %config(noreplace) %{_sysconfdir}/%{name}/dnsdist.conf
%{_unitdir}/dnsdist*
%dir %{_sysconfdir}/%{name}
%dir %{_sysconfdir}/%{name}/recursor.d
%config(noreplace) %{_sysconfdir}/%{name}/recursor.conf
+%config %{_sysconfdir}/%{name}/recursor.yml-dist
%doc README
+++ /dev/null
-
-# chkconfig: - 80 75
-# description: PDNS is a versatile high performance authoritative nameserver
-
-### BEGIN INIT INFO
-# Provides: pdns
-# Required-Start: $remote_fs $network $syslog
-# Required-Stop: $remote_fs $network $syslog
-# Should-Start:
-# Should-Stop:
-# Default-Start:
-# Default-Stop: 0 1 6
-# Short-Description: PowerDNS authoritative server
-# Description: PowerDNS authoritative server
-### END INIT INFO
-
-set -e
-
-prefix=/usr
-exec_prefix=/usr
-BINARYPATH=/usr/bin
-SBINARYPATH=/usr/sbin
-SOCKETPATH=/var/run/pdns
-
-[ -f "$SBINARYPATH/pdns_server" ] || exit 0
-
-[ -r /etc/default/pdns ] && . /etc/default/pdns
-
-mkdir -p $SOCKETPATH
-cd $SOCKETPATH
-suffix=$(basename $0 | cut -d- -f2- -s)
-if [ -n "$suffix" ]
-then
- EXTRAOPTS=--config-name=$suffix
- PROGNAME=pdns-$suffix
-else
- PROGNAME=pdns
-fi
-
-pdns_server="$SBINARYPATH/pdns_server $EXTRAOPTS"
-
-doPC()
-{
- ret=$($BINARYPATH/pdns_control $EXTRAOPTS $1 $2 2> /dev/null)
-}
-
-NOTRUNNING=0
-doPC ping || NOTRUNNING=$?
-
-case "$1" in
- status)
- if test "$NOTRUNNING" = "0"
- then
- doPC status
- echo $ret
- else
- echo "not running"
- exit 3
- fi
- ;;
-
- stop)
- echo -n "Stopping PowerDNS authoritative nameserver: "
- if test "$NOTRUNNING" = "0"
- then
- doPC quit
- rm -f /var/lock/subsys/pdns
- echo $ret
- else
- echo "not running"
- fi
- ;;
-
-
- force-stop)
- echo -n "Stopping PowerDNS authoritative nameserver: "
- killall -v -9 pdns_server
- rm -f /var/lock/subsys/pdns
- echo "killed"
- ;;
-
- start)
- echo -n "Starting PowerDNS authoritative nameserver: "
- if test "$NOTRUNNING" = "0"
- then
- echo "already running"
- else
- if $pdns_server --daemon --guardian=yes
- then
- touch /var/lock/subsys/pdns
- echo "started"
- else
- echo "starting failed"
- exit 1
- fi
- fi
- ;;
-
- condrestart)
- if [ -f /var/lock/subsys/pdns ];
- then
- echo "running, restarting"
- $0 restart
- else
- echo "not running"
- fi
- ;;
-
- force-reload | restart)
- echo -n "Restarting PowerDNS authoritative nameserver: "
- if test "$NOTRUNNING" = "1"
- then
- echo "not running, starting"
- else
-
- echo -n stopping and waiting..
- doPC quit
- sleep 3
- echo done
- fi
- $0 start
- ;;
-
- reload)
- echo -n "Reloading PowerDNS authoritative nameserver: "
- if test "$NOTRUNNING" = "0"
- then
- doPC cycle
- echo requested reload
- else
- echo not running yet
- $0 start
- fi
- ;;
-
- monitor)
- if test "$NOTRUNNING" = "0"
- then
- echo "already running"
- else
- $pdns_server --daemon=no --guardian=no --control-console --loglevel=9
- fi
- ;;
-
- dump)
- if test "$NOTRUNNING" = "0"
- then
- doPC list
- echo $ret
- else
- echo "not running"
- fi
- ;;
-
- show)
- if [ $# -lt 2 ]
- then
- echo Insufficient parameters
- exit
- fi
- if test "$NOTRUNNING" = "0"
- then
- echo -n "$2="
- doPC show $2 ; echo $ret
- else
- echo "not running"
- fi
- ;;
-
- mrtg)
- if [ $# -lt 2 ]
- then
- echo Insufficient parameters
- exit
- fi
- if test "$NOTRUNNING" = "0"
- then
- doPC show $2 ; echo $ret
- if [ "$3x" != "x" ]
- then
- doPC show $3 ; echo $ret
- else
- echo 0
- fi
- doPC uptime ; echo $ret
- echo PowerDNS daemon
- else
- echo "not running"
- fi
-
- ;;
-
- cricket)
- if [ $# -lt 2 ]
- then
- echo Insufficient parameters
- exit
- fi
- if test "$NOTRUNNING" = "0"
- then
- doPC show $2 ; echo $ret
- else
- echo "not running"
- fi
-
- ;;
-
- *)
- echo pdns [start|stop|condrestart|force-reload|reload|restart|status|dump|show|mrtg|cricket|monitor]
-
- ;;
-esac
-
PDNS_CHECK_OS
PTHREAD_SET_NAME
AC_FUNC_STRERROR_R
+AX_CXX_CXXFS
PDNS_WITH_LUA([mandatory])
PDNS_CHECK_LUA_HPP
)],
[have_mmap=no]
)
+AC_CHECK_HEADERS([sys/random.h])
PDNS_WITH_LIBSODIUM
PDNS_WITH_LIBDECAF
PDNS_ENABLE_BACKEND_UNIT_TESTS
PDNS_ENABLE_REPRODUCIBLE
PDNS_ENABLE_FUZZ_TARGETS
+PDNS_ENABLE_COVERAGE
PDNS_WITH_SQLITE3
dnl Checks for library functions.
dnl the *_r functions are in posix so we can use them unconditionally, but the ext/yahttp code is
dnl using the defines.
-AC_CHECK_FUNCS_ONCE([strcasestr localtime_r gmtime_r recvmmsg sched_setscheduler getrandom arc4random])
+AC_CHECK_FUNCS_ONCE([strcasestr localtime_r gmtime_r recvmmsg sched_setscheduler])
+AC_CHECK_FUNCS_ONCE([getrandom getentropy arc4random arc4random_uniform arc4random_buf])
+PDNS_CHECK_SECURE_MEMSET
AM_CONDITIONAL([HAVE_RECVMMSG], [test "x$ac_cv_func_recvmmsg" = "xyes"])
CFLAGS="$PIE_CFLAGS $CFLAGS"
CXXFLAGS="$PIE_CFLAGS $CXXFLAGS"
PROGRAM_LDFLAGS="$PIE_LDFLAGS $PROGRAM_LDFLAGS"
+AS_IF([test "$ax_cxx_cv_filesystem_lib" != "none"],
+ [PROGRAM_LDFLAGS="$PROGRAM_LDFLAGS -l$ax_cxx_cv_filesystem_lib"],
+ []
+)
AC_SUBST([PROGRAM_LDFLAGS])
PDNS_ENABLE_COVERAGE
pdns/Makefile
codedocs/Makefile
docs/Makefile
- pdns/pdns.init
ext/Makefile
+ ext/arc4random/Makefile
ext/ipcrypt/Makefile
ext/yahttp/Makefile
ext/yahttp/yahttp/Makefile
for entry in mt.value.stringVal:
values = ', '.join([values, entry]) if values != '' else entry
for entry in mt.value.intVal:
- values = ', '.join([values, entry]) if values != '' else entry
+ values = ', '.join([values, str(entry)]) if values != '' else str(entry)
print('- %s -> %s' % (mt.key, values))
thread = threading.Thread(name='Connection Handler',
target=PDNSPBConnHandler.run,
args=[handler])
- thread.setDaemon(True)
+ thread.daemon = True
thread.start()
self._sock.close()
def setUp(self):
self.addTypeEqualityFunc(dns.message.Message, self.assertEqualDNSMessage)
+ self.addTypeEqualityFunc(dns.message.QueryMessage, self.assertEqualDNSMessage)
- super(AssertEqualDNSMessageMixin, self).setUp()
\ No newline at end of file
+ super(AssertEqualDNSMessageMixin, self).setUp()
#include "xdp.h"
+#define DISABLE_LOGGING 1
+
BPF_TABLE_PINNED("hash", uint32_t, struct map_value, v4filter, 1024, "/sys/fs/bpf/dnsdist/addr-v4");
BPF_TABLE_PINNED("hash", struct in6_addr, struct map_value, v6filter, 1024, "/sys/fs/bpf/dnsdist/addr-v6");
BPF_TABLE_PINNED("hash", struct dns_qname, struct map_value, qnamefilter, 1024, "/sys/fs/bpf/dnsdist/qnames");
+#ifndef DISABLE_LOGGING
BPF_TABLE_PINNED("prog", int, int, progsarray, 2, "/sys/fs/bpf/dnsdist/progs");
+#endif /* DISABLE_LOGGING */
/*
* bcc has added BPF_TABLE_PINNED7 to the latest commit of the master branch, but it has not yet been released.
BPF_TABLE_PINNED7("lpm_trie", struct CIDR4, struct map_value, cidr4filter, 1024, "/sys/fs/bpf/dnsdist/cidr4", BPF_F_NO_PREALLOC);
BPF_TABLE_PINNED7("lpm_trie", struct CIDR6, struct map_value, cidr6filter, 1024, "/sys/fs/bpf/dnsdist/cidr6", BPF_F_NO_PREALLOC);
+#ifdef UseXsk
+#define BPF_XSKMAP_PIN(_name, _max_entries, _pinned) \
+ struct _name##_table_t \
+ { \
+ u32 key; \
+ int leaf; \
+ int* (*lookup)(int*); \
+ /* xdp_act = map.redirect_map(index, flag) */ \
+ u64 (*redirect_map)(int, int); \
+ u32 max_entries; \
+ }; \
+ __attribute__((section("maps/xskmap:" _pinned))) struct _name##_table_t _name = {.max_entries = (_max_entries)}
+
+BPF_XSKMAP_PIN(xsk_map, 16, "/sys/fs/bpf/dnsdist/xskmap");
+BPF_TABLE_PINNED("hash", struct IPv4AndPort, bool, xskDestinationsV4, 1024, "/sys/fs/bpf/dnsdist/xsk-destinations-v4");
+BPF_TABLE_PINNED("hash", struct IPv6AndPort, bool, xskDestinationsV6, 1024, "/sys/fs/bpf/dnsdist/xsk-destinations-v6");
+#endif /* UseXsk */
+
+#define COMPARE_PORT(x, p) ((x) == bpf_htons(p))
+
/*
* Recalculate the checksum
* Copyright 2020, NLnet Labs, All rights reserved.
* Set the TC bit and swap UDP ports
* Copyright 2020, NLnet Labs, All rights reserved.
*/
-static inline enum dns_action set_tc_bit(struct udphdr *udp, struct dnshdr *dns)
+static inline void set_tc_bit(struct udphdr* udp, struct dnshdr* dns)
{
uint16_t old_val = dns->flags.as_value;
dns->flags.as_bits_and_pieces.tc = 1;
// change the UDP destination to the source
- udp->dest = udp->source;
- udp->source = bpf_htons(DNS_PORT);
+ uint16_t tmp = udp->dest;
+ udp->dest = udp->source;
+ udp->source = tmp;
// calculate and write the new checksum
update_checksum(&udp->check, old_val, dns->flags.as_value);
-
- // bounce
- return TC;
}
/*
* TC if (modified) message needs to be replied
* DROP if message needs to be blocke
*/
-static inline enum dns_action check_qname(struct cursor *c)
+static inline struct map_value* check_qname(struct cursor* c)
{
struct dns_qname qkey = {0};
uint8_t qname_byte;
uint16_t qtype;
int length = 0;
- for(int i = 0; i<255; i++) {
- if (bpf_probe_read_kernel(&qname_byte, sizeof(qname_byte), c->pos)) {
- return PASS;
- }
- c->pos += 1;
- if (length == 0) {
- if (qname_byte == 0 || qname_byte > 63 ) {
- break;
+ for (int i = 0; i < 255; i++) {
+ if (bpf_probe_read_kernel(&qname_byte, sizeof(qname_byte), c->pos)) {
+ return NULL;
+ }
+ c->pos += 1;
+ if (length == 0) {
+ if (qname_byte == 0 || qname_byte > 63) {
+ break;
}
length += qname_byte;
- } else {
+ }
+ else {
length--;
}
- if (qname_byte >= 'A' && qname_byte <= 'Z') {
- qkey.qname[i] = qname_byte + ('a' - 'A');
- } else {
- qkey.qname[i] = qname_byte;
- }
+ if (qname_byte >= 'A' && qname_byte <= 'Z') {
+ qkey.qname[i] = qname_byte + ('a' - 'A');
+ }
+ else {
+ qkey.qname[i] = qname_byte;
+ }
}
// if the last read qbyte is not 0 incorrect QName format), return PASS
if (qname_byte != 0) {
- return PASS;
+ return NULL;
}
// get QType
- if(bpf_probe_read_kernel(&qtype, sizeof(qtype), c->pos)) {
- return PASS;
+ if (bpf_probe_read_kernel(&qtype, sizeof(qtype), c->pos)) {
+ return NULL;
}
struct map_value* value;
qkey.qtype = bpf_htons(qtype);
value = qnamefilter.lookup(&qkey);
if (value) {
- __sync_fetch_and_add(&value->counter, 1);
- return value->action;
+ return value;
}
// check with Qtype 255 (*)
qkey.qtype = 255;
- value = qnamefilter.lookup(&qkey);
- if (value) {
- __sync_fetch_and_add(&value->counter, 1);
- return value->action;
- }
-
- return PASS;
+ return qnamefilter.lookup(&qkey);
}
/*
* Parse IPv4 DNS mesage.
- * Returns PASS if message needs to go through (i.e. pass)
- * TC if (modified) message needs to be replied
- * DROP if message needs to be blocked
+ * Returns XDP_PASS if message needs to go through (i.e. pass)
+ * XDP_REDIRECT if message needs to be redirected (for AF_XDP, which needs to be translated to the caller into XDP_PASS outside of the AF_XDP)
+ * XDP_TX if (modified) message needs to be replied
+ * XDP_DROP if message needs to be blocked
*/
-static inline enum dns_action udp_dns_reply_v4(struct cursor *c, struct CIDR4 *key)
+static inline enum xdp_action parseIPV4(struct xdp_md* ctx, struct cursor* c)
{
- struct udphdr *udp;
- struct dnshdr *dns;
+ struct iphdr* ipv4;
+ struct udphdr* udp = NULL;
+ struct dnshdr* dns = NULL;
+ if (!(ipv4 = parse_iphdr(c))) {
+ return XDP_PASS;
+ }
+ switch (ipv4->protocol) {
+ case IPPROTO_UDP: {
+ if (!(udp = parse_udphdr(c))) {
+ return XDP_PASS;
+ }
+#ifdef UseXsk
+ struct IPv4AndPort v4Dest;
+ memset(&v4Dest, 0, sizeof(v4Dest));
+ v4Dest.port = udp->dest;
+ v4Dest.addr = ipv4->daddr;
+ if (!xskDestinationsV4.lookup(&v4Dest)) {
+ return XDP_PASS;
+ }
+#else /* UseXsk */
+ if (!IN_DNS_PORT_SET(udp->dest)) {
+ return XDP_PASS;
+ }
+#endif /* UseXsk */
+ if (!(dns = parse_dnshdr(c))) {
+ return XDP_DROP;
+ }
+ break;
+ }
- if (!(udp = parse_udphdr(c)) || udp->dest != bpf_htons(DNS_PORT)) {
- return PASS;
+#ifdef UseXsk
+ case IPPROTO_TCP: {
+ return XDP_PASS;
}
+#endif /* UseXsk */
- // check that we have a DNS packet
- if (!(dns = parse_dnshdr(c))) {
- return PASS;
- }
+ default:
+ return XDP_PASS;
+ }
+
+ struct CIDR4 key;
+ key.addr = bpf_htonl(ipv4->saddr);
// if the address is blocked, perform the corresponding action
- struct map_value* value = v4filter.lookup(&key->addr);
+ struct map_value* value = v4filter.lookup(&key.addr);
if (value) {
- __sync_fetch_and_add(&value->counter, 1);
- if (value->action == TC) {
- return set_tc_bit(udp, dns);
- } else {
- return value->action;
- }
+ goto res;
}
- key->cidr = 32;
- key->addr = bpf_htonl(key->addr);
- value = cidr4filter.lookup(key);
+ key.cidr = 32;
+ key.addr = bpf_htonl(key.addr);
+ value = cidr4filter.lookup(&key);
if (value) {
+ goto res;
+ }
+
+ // ignore the DF flag
+ const uint16_t fragMask = htons(~(1 << 14));
+ uint16_t frag = ipv4->frag_off & fragMask;
+ if (frag != 0) {
+ // MF flag is set, or Fragment Offset is != 0
+ return XDP_PASS;
+ }
+
+ if (dns) {
+ value = check_qname(c);
+ }
+ if (value) {
+ res:
__sync_fetch_and_add(&value->counter, 1);
- if (value->action == TC) {
- return set_tc_bit(udp, dns);
+ if (value->action == TC && udp && dns) {
+ set_tc_bit(udp, dns);
+ // swap src/dest IP addresses
+ uint32_t swap_ipv4 = ipv4->daddr;
+ ipv4->daddr = ipv4->saddr;
+ ipv4->saddr = swap_ipv4;
+
+#ifndef DISABLE_LOGGING
+ progsarray.call(ctx, 1);
+#endif /* DISABLE_LOGGING */
+ return XDP_TX;
}
- else {
- return value->action;
+
+ if (value->action == DROP) {
+#ifndef DISABLE_LOGGING
+ progsarray.call(ctx, 0);
+#endif /* DISABLE_LOGGING */
+ return XDP_DROP;
}
}
- enum dns_action action = check_qname(c);
- if (action == TC) {
- return set_tc_bit(udp, dns);
- }
- return action;
+ return XDP_REDIRECT;
}
/*
* Parse IPv6 DNS mesage.
- * Returns PASS if message needs to go through (i.e. pass)
- * TC if (modified) message needs to be replied
- * DROP if message needs to be blocked
+ * Returns XDP_PASS if message needs to go through (i.e. pass)
+ * XDP_REDIRECT if message needs to be redirected (for AF_XDP, which needs to be translated to the caller into XDP_PASS outside of the AF_XDP)
+ * XDP_TX if (modified) message needs to be replied
+ * XDP_DROP if message needs to be blocked
*/
-static inline enum dns_action udp_dns_reply_v6(struct cursor *c, struct CIDR6* key)
+static inline enum xdp_action parseIPV6(struct xdp_md* ctx, struct cursor* c)
{
- struct udphdr *udp;
- struct dnshdr *dns;
+ struct ipv6hdr* ipv6;
+ struct udphdr* udp = NULL;
+ struct dnshdr* dns = NULL;
+ if (!(ipv6 = parse_ipv6hdr(c))) {
+ return XDP_PASS;
+ }
+ switch (ipv6->nexthdr) {
+ case IPPROTO_UDP: {
+ if (!(udp = parse_udphdr(c))) {
+ return XDP_PASS;
+ }
+#ifdef UseXsk
+ struct IPv6AndPort v6Dest;
+ memset(&v6Dest, 0, sizeof(v6Dest));
+ v6Dest.port = udp->dest;
+ memcpy(&v6Dest.addr, &ipv6->daddr, sizeof(v6Dest.addr));
+ if (!xskDestinationsV6.lookup(&v6Dest)) {
+ return XDP_PASS;
+ }
+#else /* UseXsk */
+ if (!IN_DNS_PORT_SET(udp->dest)) {
+ return XDP_PASS;
+ }
+#endif /* UseXsk */
+ if (!(dns = parse_dnshdr(c))) {
+ return XDP_DROP;
+ }
+ break;
+ }
-
- if (!(udp = parse_udphdr(c)) || udp->dest != bpf_htons(DNS_PORT)) {
- return PASS;
+#ifdef UseXsk
+ case IPPROTO_TCP: {
+ return XDP_PASS;
}
+#endif /* UseXsk */
- // check that we have a DNS packet
- ;
- if (!(dns = parse_dnshdr(c))) {
- return PASS;
+ default:
+ return XDP_PASS;
}
+ struct CIDR6 key;
+ key.addr = ipv6->saddr;
+
// if the address is blocked, perform the corresponding action
- struct map_value* value = v6filter.lookup(&key->addr);
+ struct map_value* value = v6filter.lookup(&key.addr);
+ if (value) {
+ goto res;
+ }
+ key.cidr = 128;
+ value = cidr6filter.lookup(&key);
if (value) {
- __sync_fetch_and_add(&value->counter, 1);
- if (value->action == TC) {
- return set_tc_bit(udp, dns);
- } else {
- return value->action;
- }
+ goto res;
}
- key->cidr = 128;
- value = cidr6filter.lookup(key);
+ if (dns) {
+ value = check_qname(c);
+ }
if (value) {
+ res:
__sync_fetch_and_add(&value->counter, 1);
- if (value->action == TC) {
- return set_tc_bit(udp, dns);
+ if (value->action == TC && udp && dns) {
+ set_tc_bit(udp, dns);
+ // swap src/dest IP addresses
+ struct in6_addr swap_ipv6 = ipv6->daddr;
+ ipv6->daddr = ipv6->saddr;
+ ipv6->saddr = swap_ipv6;
+#ifndef DISABLE_LOGGING
+ progsarray.call(ctx, 1);
+#endif /* DISABLE_LOGGING */
+ return XDP_TX;
}
- else {
- return value->action;
+ if (value->action == DROP) {
+#ifndef DISABLE_LOGGING
+ progsarray.call(ctx, 0);
+#endif /* DISABLE_LOGGING */
+ return XDP_DROP;
}
}
-
- enum dns_action action = check_qname(c);
- if (action == TC) {
- return set_tc_bit(udp, dns);
- }
- return action;
+ return XDP_REDIRECT;
}
int xdp_dns_filter(struct xdp_md* ctx)
struct cursor c;
struct ethhdr *eth;
uint16_t eth_proto;
- struct iphdr *ipv4;
- struct ipv6hdr *ipv6;
- int r = 0;
+ enum xdp_action r;
// initialise the cursor
cursor_init(&c, ctx);
// pass the packet if it is not an ethernet one
if ((eth = parse_eth(&c, ð_proto))) {
// IPv4 packets
- if (eth_proto == bpf_htons(ETH_P_IP))
- {
- if (!(ipv4 = parse_iphdr(&c)) || bpf_htons(ipv4->protocol != IPPROTO_UDP)) {
- return XDP_PASS;
- }
-
- struct CIDR4 key;
- key.addr = bpf_htonl(ipv4->saddr);
- // if TC bit must not be set, apply the action
- if ((r = udp_dns_reply_v4(&c, &key)) != TC) {
- if (r == DROP) {
- progsarray.call(ctx, 0);
- return XDP_DROP;
- }
- return XDP_PASS;
- }
-
- // swap src/dest IP addresses
- uint32_t swap_ipv4 = ipv4->daddr;
- ipv4->daddr = ipv4->saddr;
- ipv4->saddr = swap_ipv4;
+ if (eth_proto == bpf_htons(ETH_P_IP)) {
+ r = parseIPV4(ctx, &c);
+ goto res;
}
// IPv6 packets
else if (eth_proto == bpf_htons(ETH_P_IPV6)) {
- if (!(ipv6 = parse_ipv6hdr(&c)) || bpf_htons(ipv6->nexthdr != IPPROTO_UDP)) {
- return XDP_PASS;
- }
- struct CIDR6 key;
- key.addr = ipv6->saddr;
-
- // if TC bit must not be set, apply the action
- if ((r = udp_dns_reply_v6(&c, &key)) != TC) {
- if (r == DROP) {
- progsarray.call(ctx, 0);
- return XDP_DROP;
- }
- return XDP_PASS;
- }
-
- // swap src/dest IP addresses
- struct in6_addr swap_ipv6 = ipv6->daddr;
- ipv6->daddr = ipv6->saddr;
- ipv6->saddr = swap_ipv6;
+ r = parseIPV6(ctx, &c);
+ goto res;
}
// pass all non-IP packets
- else {
- return XDP_PASS;
- }
+ return XDP_PASS;
}
- else {
+ return XDP_PASS;
+res:
+ switch (r) {
+ case XDP_REDIRECT:
+#ifdef UseXsk
+ return xsk_map.redirect_map(ctx->rx_queue_index, 0);
+#else
return XDP_PASS;
+#endif /* UseXsk */
+ case XDP_TX: { // swap MAC addresses
+ uint8_t swap_eth[ETH_ALEN];
+ memcpy(swap_eth, eth->h_dest, ETH_ALEN);
+ memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
+ memcpy(eth->h_source, swap_eth, ETH_ALEN);
+ // bounce the request
+ return XDP_TX;
+ }
+ default:
+ return r;
}
-
- // swap MAC addresses
- uint8_t swap_eth[ETH_ALEN];
- memcpy(swap_eth, eth->h_dest, ETH_ALEN);
- memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
- memcpy(eth->h_source, swap_eth, ETH_ALEN);
-
- progsarray.call(ctx, 1);
-
- // bounce the request
- return XDP_TX;
}
struct in6_addr addr;
};
+struct IPv4AndPort
+{
+ uint32_t addr;
+ uint16_t port;
+};
+
+struct IPv6AndPort
+{
+ struct in6_addr addr;
+ uint16_t port;
+};
+
/*
* Store the matching counter and the associated action for a blocked element
*/
c->pos = (void *)(long)ctx->data;
}
-/*
+/*
* Header parser functions
* Copyright 2020, NLnet Labs, All rights reserved.
*/
return eth;
}
-#endif
+#endif
#!/usr/bin/env python3
-
-from bcc import BPF
+import argparse
import ctypes as ct
import netaddr
import socket
+from bcc import BPF
+
# Constants
QTYPES = {'LOC': 29, '*': 255, 'IXFR': 251, 'UINFO': 100, 'NSEC3': 50, 'AAAA': 28, 'CNAME': 5, 'MINFO': 14, 'EID': 31, 'GPOS': 27, 'X25': 19, 'HINFO': 13, 'CAA': 257, 'NULL': 10, 'DNSKEY': 48, 'DS': 43, 'ISDN': 20, 'SOA': 6, 'RP': 17, 'UID': 101, 'TALINK': 58, 'TKEY': 249, 'PX': 26, 'NSAP-PTR': 23, 'TXT': 16, 'IPSECKEY': 45, 'DNAME': 39, 'MAILA': 254, 'AFSDB': 18, 'SSHFP': 44, 'NS': 2, 'PTR': 12, 'SPF': 99, 'TA': 32768, 'A': 1, 'NXT': 30, 'AXFR': 252, 'RKEY': 57, 'KEY': 25, 'NIMLOC': 32, 'A6': 38, 'TLSA': 52, 'MG': 8, 'HIP': 55, 'NSEC': 47, 'GID': 102, 'SRV': 33, 'DLV': 32769, 'NSEC3PARAM': 51, 'UNSPEC': 103, 'TSIG': 250, 'ATMA': 34, 'RRSIG': 46, 'OPT': 41, 'MD': 3, 'NAPTR': 35, 'MF': 4, 'MB': 7, 'DHCID': 49, 'MX': 15, 'MAILB': 253, 'CERT': 37, 'NINFO': 56, 'APL': 42, 'MR': 9, 'SIG': 24, 'WKS': 11, 'KX': 36, 'NSAP': 22, 'RT': 21, 'SINK': 40}
INV_QTYPES = {v: k for k, v in QTYPES.items()}
DROP_ACTION = 1
TC_ACTION = 2
-# The interface on wich the filter will be attached
-DEV = "eth0"
-
# The list of blocked IPv4, IPv6 and QNames
# IP format : (IPAddress, Action)
# CIDR format : (IPAddress/cidr, Action)
blocked_qnames = [("localhost", "A", DROP_ACTION), ("test.com", "*", TC_ACTION)]
# Main
-xdp = BPF(src_file="xdp-filter.ebpf.src")
+parser = argparse.ArgumentParser(description='XDP helper for DNSDist')
+parser.add_argument('--xsk', action='store_true', help='Enable XSK (AF_XDP) mode', default=False)
+parser.add_argument('--interface', '-i', type=str, default='eth0', help='The interface on which the filter will be attached')
+
+parameters = parser.parse_args()
+cflag = []
+if parameters.xsk:
+ print(f'Enabling XSK (AF_XDP) on {parameters.interface}..')
+ cflag.append("-DUseXsk")
+else:
+ Ports = [53]
+ portsStr = ', '.join(str(port) for port in Ports)
+ print(f'Enabling XDP on {parameters.interface} and ports {portsStr}..')
+ IN_DNS_PORT_SET = "||".join("COMPARE_PORT((x),"+str(i)+")" for i in Ports)
+ cflag.append(r"-DIN_DNS_PORT_SET(x)=(" + IN_DNS_PORT_SET + r")")
+
+xdp = BPF(src_file="xdp-filter.ebpf.src", cflags=cflag)
fn = xdp.load_func("xdp_dns_filter", BPF.XDP)
-xdp.attach_xdp(DEV, fn, 0)
+xdp.attach_xdp(parameters.interface, fn, 0)
v4filter = xdp.get_table("v4filter")
v6filter = xdp.get_table("v6filter")
cidr6filter = xdp.get_table("cidr6filter")
qnamefilter = xdp.get_table("qnamefilter")
+if parameters.xsk:
+ xskDestinations = xdp.get_table("xskDestinationsV4")
+
for ip in blocked_ipv4:
print(f"Blocking {ip}")
key = v4filter.Key(int(netaddr.IPAddress(ip[0]).value))
leaf.action = qname[2]
qnamefilter[key] = leaf
-print("Filter is ready")
+print(f"Filter is ready on {parameters.interface}")
+
try:
- xdp.trace_print()
+ xdp.trace_print()
except KeyboardInterrupt:
pass
for item in qnamefilter.items():
print(f"{''.join(map(chr, item[0].qname)).strip()}/{INV_QTYPES[item[0].qtype]} ({ACTIONS[item[1].action]}): {item[1].counter}")
-xdp.remove_xdp(DEV, 0)
+xdp.remove_xdp(parameters.interface, 0)
elif product == 'dnsdist':
args = ['--supervised', '--disable-syslog']
apienvvar = 'DNSDIST_API_KEY'
- apiconftemplate = """webserver("0.0.0.0:8083", '{{ apikey }}', '{{ apikey }}', {}, '0.0.0.0/0')
+ apiconftemplate = """webserver("0.0.0.0:8083")
+ setWebserverConfig({password='{{ apikey }}', apiKey='{{ apikey }}', acl='0.0.0.0/0'})
controlSocket('0.0.0.0:5199')
setKey('{{ apikey }}')
setConsoleACL('0.0.0.0/0')
templateroot = '/etc/dnsdist/templates.d'
templatedestination = '/etc/dnsdist/conf.d'
+debug = os.getenv("DEBUG_CONFIG", 'no').lower() == 'yes'
+
apikey = os.getenv(apienvvar)
if apikey is not None:
webserver_conf = jinja2.Template(apiconftemplate).render(apikey=apikey)
conffile = os.path.join(templatedestination, '_api.conf')
with open(conffile, 'w') as f:
f.write(webserver_conf)
- print("Created {} with content:\n{}\n".format(conffile, webserver_conf))
+ if debug:
+ print("Created {} with content:\n{}\n".format(conffile, webserver_conf))
templates = os.getenv('TEMPLATE_FILES')
if templates is not None:
target = os.path.join(templatedestination, templateFile + '.conf')
with open(target, 'w') as f:
f.write(rendered)
- print("Created {} with content:\n{}\n".format(target, rendered))
+ if debug:
+ print("Created {} with content:\n{}\n".format(target, rendered))
os.execv(program, [program]+args+sys.argv[1:])
mans/.complete: manpages := $(addprefix manpages/,$(addsuffix .rst,$(MANPAGES_DIST)))
mans/.complete: .venv
rm -rf "$(@D).tmp"
- .venv/bin/python -msphinx -b man . "$(@D).tmp" $(manpages) && rm -rf "$(@D)" && mv "$(@D).tmp" "$(@D)"
+ (cd "${srcdir}" && $(CURDIR)/.venv/bin/python -msphinx -b man . "$(CURDIR)/$(@D).tmp" $(manpages)) && rm -rf "$(@D)" && mv "$(@D).tmp" "$(@D)"
touch "$@"
rm -rf "$(@D).tmp"
.venv: requirements.txt
$(PYTHON) -m venv .venv
.venv/bin/pip install -U pip setuptools setuptools-git wheel
- .venv/bin/pip install -r requirements.txt
+ .venv/bin/pip install -r ${srcdir}/requirements.txt
.NOTPARALLEL: \
all-docs \
Older releases are marked end of life and receive no updates at all.
Pre-releases do not receive immediate security updates.
-The currently supported release train of PowerDNS Authoritative Server is 4.7.
+The currently supported release train of PowerDNS Authoritative Server is 4.9.
-PowerDNS Authoritative Server 4.6 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 4.9 is released.
+PowerDNS Authoritative Server 4.8 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 5.1 is released.
-PowerDNS Authoritative Server 4.5 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 4.8 is released.
+PowerDNS Authoritative Server 4.7 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 5.0 is released.
-PowerDNS Authoritative Server 4.0 through 4.4, 3.x, and 2.x are End of Life.
+PowerDNS Authoritative Server 4.0 through 4.6, 3.x, and 2.x are End of Life.
Note: Users with a commercial agreement with PowerDNS.COM BV or Open-Xchange
can receive extended support for releases which are End Of Life. If you are
- Release date
- Critical-Only updates
- End of Life
+ * - 4.9
+ - 15th of March 2024
+ - ~September 2024
+ - ~September 2025
+ * - 4.8
+ - 1st of June 2023
+ - ~ December 2023
+ - ~ December 2024
* - 4.7
- 20th of October 2022
- ~ April 2023
* - 4.6
- 25th of January 2022
- 20th of October 2022
- - ~ October 2023
+ - EOL March 2024
* - 4.5
- July 13 2021
- 25th of January 2022
- - ~ March 2023
+ - EOL June 2023
* - 4.4
- December 18 2020
- 25th of January 2022
ZONEMD
------
-The ZONEMD record, specified in :rfc:`8796`, is used to validate zones.
+The ZONEMD record, specified in :rfc:`8976`, is used to validate zones.
Other types
-----------
* ``file``
* ``type``
* ``masters``
+ * ``primaries`` (added in version 4.9.0)
* ``also-notify``
+Unknown directives will be ignored.
+
.. _setting-bind-check-interval:
``bind-check-interval``
Setting this option to ``yes`` makes PowerDNS ignore out of zone records
when loading zone files.
-.. _setting-bind-supermasters:
+Autoprimary support (experimental)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-``bind-supermasters``
-~~~~~~~~~~~~~~~~~~~~~
+.. _setting-bind-autoprimaries:
+
+``bind-autoprimaries``
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionchanged:: 4.9.0
+
+ This was called ``bind-supermasters`` before 4.9.0.
Specifies file where to read list of autoprimaries.
BIND backend only checks IP address of primary server.
BIND backend can only read this file, not write it.
-.. _setting-bind-supermaster-config:
+.. _setting-bind-autoprimary-config:
-``bind-supermaster-config``
+``bind-autoprimary-config``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. versionchanged:: 4.9.0
+
+ This was called ``bind-supermaster-config`` before 4.9.0.
+
When a new zone is configured via the autosecondary mechanism, bindbackend *writes* a zone entry to this file.
Your ``bind-config`` file should have an ``include`` statement to make sure this file is read on startup.
-.. _setting-bind-supermaster-destdir:
+.. _setting-bind-autoprimary-destdir:
-``bind-supermaster-destdir``
+``bind-autoprimary-destdir``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. versionchanged:: 4.9.0
+
+ This was called ``bind-supermaster-destdir`` before 4.9.0.
+
Each new zone configured via the autosecondary mechanism gets a zone file in this directory.
This directory must be writable.
``gmysql-ssl``
^^^^^^^^^^^^^^^^^^
-Send the CLIENT_SSL capability flag to the server. SSL support is announced by the server via CLIENT_SSL and is enabled if the client returns the same capability. Default: no.
+.. deprecated:: 5.0.0
+
+Before 5.0.0: Send the CLIENT_SSL capability flag to the server. SSL support is announced by the server via CLIENT_SSL and is enabled if the client returns the same capability. Default: no.
+
+5.0.0 and up: this option does nothing. Use ``gmysql-group`` and put your TLS settings in ``my.cnf``.
.. _setting-gmysql-timeout:
Records can now be added using ``pdnsutil add-record`` or ``pdnsutil edit-zone``.
-Slave operation
-^^^^^^^^^^^^^^^
+Secondary operation
+^^^^^^^^^^^^^^^^^^^
-These backends are fully slave capable. To become a slave of the
-'example.com' domain, using 198.51.100.6 as the master execute this::
+These backends are fully secondary capable. To become a secondary of the
+'example.com' domain, using 198.51.100.6 as the primary execute this::
- pdnsutil create-slave-zone example.com 198.51.100.6
+ pdnsutil create-secondary-zone example.com 198.51.100.6
And wait a while for PowerDNS to pick up the addition - which happens
within one minute (this is determined by the
-:ref:`setting-slave-cycle-interval`
+:ref:`setting-xfr-cycle-interval`
setting). There is no need to inform PowerDNS that a new domain was
added. Typical output is::
- Apr 09 13:34:29 All slave domains are fresh
- Apr 09 13:35:29 1 slave domain needs checking
- Apr 09 13:35:29 Domain example.com is stale, master serial 1, our serial 0
+ Apr 09 13:34:29 All secondary domains are fresh
+ Apr 09 13:35:29 1 secondary domain needs checking
+ Apr 09 13:35:29 Domain example.com is stale, primary serial 1, our serial 0
Apr 09 13:35:30 [gPgSQLBackend] Connected to database
Apr 09 13:35:30 AXFR started for 'example.com'
Apr 09 13:35:30 AXFR done for 'example.com'
Periodically, PowerDNS schedules checks to see if domains are still
fresh. The default
-:ref:`setting-slave-cycle-interval` is 60
+:ref:`setting-xfr-cycle-interval` is 60
seconds, large installations may need to raise this value. Once a domain
has been checked, it will not be checked before its SOA refresh timer
has expired. Domains whose status is unknown get checked every 60
seconds by default.
-PowerDNS has support for multiple masters per zone, and also port numbers for these masters::
+PowerDNS has support for multiple primaries per zone, and also port numbers for these primaries::
- pdnsutil create-slave-zone example.com 198.51.100.6 2001:0DB8:15:4AF::4
- pdnsutil create-slave-zone example.net 198.51.100.20:5301 '[2001:0DB8:11:6E::4]:54'
+ pdnsutil create-secondary-zone example.com 198.51.100.6 2001:0DB8:15:4AF::4
+ pdnsutil create-secondary-zone example.net 198.51.100.20:5301 '[2001:0DB8:11:6E::4]:54'
-Superslave operation
-^^^^^^^^^^^^^^^^^^^^
+Autoprimary operation
+^^^^^^^^^^^^^^^^^^^^^
-To configure a :ref:`supermaster <supermaster-operation>` with IP address 203.0.113.53 which lists this
-installation as 'autoslave.example.com', issue the following::
+To configure a :ref:`autoprimary <supermaster-operation>` with IP address 203.0.113.53 which lists this
+installation as 'autosecondary.example.com', issue the following::
- pdnsutil add-supermaster 203.0.113.53 autoslave.example.com internal
+ pdnsutil add-autoprimary 203.0.113.53 autosecondary.example.com internal
From now on, valid notifies from 203.0.113.53 for which the zone lists an NS record
-containing 'autoslave.example.com' will lead to the provisioning of a
-slave domain under the account 'internal'. See :ref:`supermaster-operation`
+containing 'autosecondary.example.com' will lead to the provisioning of a
+secondary domain under the account 'internal'. See :ref:`autoprimary-operation`
for details.
-Master operation
-^^^^^^^^^^^^^^^^
+Primary operation
+^^^^^^^^^^^^^^^^^
-The generic SQL backend is fully master capable with automatic discovery
+The generic SQL backend is fully primary capable with automatic discovery
of serial changes. Raising the serial number of a domain suffices to
trigger PowerDNS to send out notifications. To configure a domain for
-master operation instead of the default native replication, issue::
+primary operation instead of the default native replication, issue::
pdnsutil create-zone example.com
pdnsutil set-kind example.com MASTER
Effects: the record (or domain, respectively) will not be visible to DNS
clients. The REST API will still see the record (or domain). Even if a
-domain is disabled, slaving still works. Slaving considers a disabled
-domain to have a serial of 0; this implies that a slaved domain will not
+domain is disabled, xfr still works. A secondary considers a disabled
+domain to have a serial of 0; this implies that a secondary domain will not
stay disabled.
.. _generic-sql-handling-dnssec-signed-zones:
a zone.
- ``remove-domain-key-query``: Called to remove a crypto key.
-Master/slave queries
-^^^^^^^^^^^^^^^^^^^^
+Primary/secondary queries
+^^^^^^^^^^^^^^^^^^^^^^^^^
-These queries are used to manipulate the master/slave information in the
+These queries are used to manipulate the primary/secondary information in the
database. Most installations will have zero need to change the following
queries.
-On masters
-~~~~~~~~~~
+On primaries
+~~~~~~~~~~~~
-- ``info-all-master-query``: Called to get data on all domains for
- which the server is master.
-- ``update-serial-query`` Called to update the last notified serial of
- a master domain.
+- ``info-all-primary-query``: Called to get data on all domains for which the server is primary.
+- ``update-serial-query`` Called to update the last notified serial of a primary domain.
-On slaves
-~~~~~~~~~
+On secondaries
+~~~~~~~~~~~~~~
-- ``info-all-slaves-query``: Called to retrieve all slave domains.
-- ``update-lastcheck-query``: Called to update the last time a slave
- domain was successfully checked for freshness.
-- ``update-master-query``: Called to update the master address of a
- domain.
+- ``info-all-secondaries-query``: Called to retrieve all secondary domains.
+- ``update-lastcheck-query``: Called to update the last time a secondary domain was successfully checked for freshness.
+- ``update-primary-query``: Called to update the primary address of a domain.
-On superslaves
+On autoprimary
~~~~~~~~~~~~~~
-- ``supermaster-query``: Called to determine if a certain host is a
- supermaster for a certain domain name.
-- ``supermaster-name-to-ips``: Called to the IP and account for a
- supermaster.
+- ``autoprimary-query``: Called to determine if a certain host is a autoprimary for a certain domain name.
+- ``autoprimary-name-to-ips``: Called to the IP and account for a autoprimary.
TSIG
^^^^
format (e.g. %cc).
:custom_mapping: Defines the mapping between the lookup format and a custom value to replace ``%mp`` placeholder.
+:zones_dir: Directory to load zones from. Each file must contain exactly one ``zone:`` object, formatted like individual domains in the example configuration above.
:mapping_lookup_formats: Same as per domain, but used as default value if not defined at the domain level.
:custom_mapping: Same as per domain, but used as default value if not defined at the domain level.
To use this kind of record, add the dnsdomain2 schema to the
configuration of the LDAP server.
-**CAUTION:** ``ldap-method=strict`` can not be used if zone transfers
+**CAUTION:** ``ldap-method=strict`` cannot be used if zone transfers
(AXFR) are needed to other name servers. Distributing zones can only be
done directly via LDAP replication in this case, because for a full zone
transfer the reverse records are missing.
``lmdb-filename``
^^^^^^^^^^^^^^^^^
-Path to the LMDB file (e.g. */var/spool/powerdns/pdns.lmdb*)
+Path to the LMDB file (e.g. */var/lib/powerdns/pdns.lmdb*)
.. warning::
On systemd systems,
``lmdb-sync-mode``
^^^^^^^^^^^^^^^^^^
-* Synchronisation mode: sync, nosync, nometasync, mapasync
-* Default: mapasync
+ .. versionchanged:: 4.9.0
-``sync``
+ ``mapasync`` choice removed
+
+* Synchronisation mode: sync, nosync, nometasync
+* Default: sync
+
+``sync`` (default since 4.9.0)
LMDB synchronous mode. Safest option, but also slightly slower. Can also be enabled with ``lmdb-sync-mode=``
``nosync``
``nometasync``
flush system buffers to disk only once per transaction, omit the metadata flush. This maintains database integrity, but can potentially lose the last committed transaction if the operating system crashes.
-``mapasync`` (default)
- Use asynchronous flushes to disk. As with nosync, a system crash can then corrupt the database or lose the last transactions.
+``mapasync`` (default before 4.9.0)
+ Due to a bug before version 4.9.0, this actually gave ``sync`` behaviour.
+ The ``mapasync`` choice has been removed in version 4.9.0.
.. _setting-lmdb-schema-version:
.. versionadded:: 4.8.0
+- Boolean
+- Default: no
+
Instead of deleting items from the database, flag them as deleted in the item's `Lightning Stream <https://doc.powerdns.com/lightningstream>`_ header.
Only enable this if you are using Lightning Stream.
+``lmdb-lightning-stream``
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ .. versionadded:: 4.8.0
+
+- Boolean
+- Default: no
+
+Run in Lightning Stream compatible mode. This:
+
+* forces ``flag-deleted`` on
+* forces ``random-ids`` on
+* handles duplicate entries in databases that can result from domains being added on two Lightning Stream nodes at the same time
+* aborts startup if ``shards`` is not set to ``1``
+
LMDB Structure
--------------
Important notices
-----------------
-Please do not use remotebackend shipped before version 3.3. This version
-has severe bug that can crash the entire process.
-
There is a breaking change on v4.0 and later. Before version 4.0, the
DNS names passed in queries were without trailing dot, after version 4.0
the DNS names are sent with trailing dot. F.ex. example.org is now sent
Get DomainInfo records for all domains in your backend.
-- Mandatory: no
+- Mandatory: unless the zone cache has been disabled by setting :ref:`setting-zone-cache-refresh-interval` to ``0`` (not recommended for performance reasons)(since 4.5.0)
- Parameters: include_disabled
- Reply: array of DomainInfo
-Catalog Zones (RFC TBD)
+Catalog Zones (RFC 9432)
========================
Starting with the PowerDNS Authoritative Server 4.7.0, catalog zone support is available.
+=================+==========+==========+
| 1 (ISC) | No | Yes |
+-----------------+----------+----------+
-| 2 (RFC TBD) | Yes | Yes |
+| 2 (:rfc:`9432`) | Yes | Yes |
+-----------------+----------+----------+
All the important features of catalog zones version "2" are supported.
------------------------
.. note::
- Catalog zone specification and operation is described in `DNS Catalog Zones <https://datatracker.ietf.org/doc/draft-ietf-dnsop-dns-catalog-zones/>`__.
+ Catalog zone specification and operation is described in :rfc:`9432`.
Setting up a producer zone
~~~~~~~~~~~~~~~~~~~~~~~~~~
pdnsutil set-catalog example.com catalog.example
Setting catalog values is supported in the :doc:`API <http-api/zone>`, by setting the ``catalog`` property in the zone properties.
+Setting the catalog to an empty ``""`` removes the member zone from the catalog it is in.
-Each member zone may have one or more additional properties as defined in the draft.
+Each member zone may have one or more additional properties as defined in the RFC.
PowerDNS currently supports the following properties:
- coo - A single DNSName
Compared to the last release candidate, one more bug has been fixed.
- The LMDB backend is incomplete in this version. Slaving zones works, loading zones with pdnsutil works, but more fine grained edits (using edit-zone, or the REST API) fail. We hope to fix this soon in a 4.2.x release.
+ The LMDB backend is incomplete in this version. Slaving zones works, loading zones with pdnsutil works, but more fine-grained edits (using edit-zone, or the REST API) fail. We hope to fix this soon in a 4.2.x release.
For an overview of features new since 4.1.x, please see `the 4.2.0 announcement blog post <http://blog.powerdns.com/2019/08/29/powerdns-authoritative-server-4-2-0/>`__.
:tags: Improvements
:pullreq: 12029
- clang14 has reached MacOS
+ clang14 has reached macOS
.. change::
:tags: Improvements
Changelogs for 4.8.x
====================
+.. changelog::
+ :version: 4.8.4
+ :released: 21st of December 2023
+
+ This is release 4.8.4 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13626
+
+ extend the systemd startup timeout during lmdb schema migrations
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13625
+
+ Add supervisor to Auth container image
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13624
+
+ ixfrdist: Fix the validation of 'max-soa-refresh'
+
+.. changelog::
+ :version: 4.8.3
+ :released: 5th of October 2023
+
+ This is release 4.8.3 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+ This release contains one new feature (``default-catalog-zone``), one bugfix (in ixfrdist), and a workaround for a bug in the MySQL client libraries.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13271
+
+ smysql: stop explicitly setting MYSQL_OPT_RECONNECT to 0
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13240
+
+ add default-catalog-zone setting
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13316
+
+ ixfrdist: set AA=1 on SOA responses
+
+.. changelog::
+ :version: 4.8.2
+ :released: 7th of September 2023
+
+ This is release 4.8.2 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+ This release contains a small collection of fixes:
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13186
+
+ (I)XFR: handle partial read of len prefix
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13187
+
+ fix code producing json
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13188
+
+ calidns: fix setting an ECS source of 0
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13189
+
+ Fix incorrect optsize
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13099
+
+ lmdb: when broadcasting indexes, -do- rewrite them even if they are unchanged
+
+.. changelog::
+ :version: 4.8.1
+ :released: 7th of July 2023
+
+ This is release 4.8.1 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+ This release contains a small collection of fixes:
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12996
+
+ lmdb: in Lightning Stream mode, during deleteDomain, use RW transaction to get ID list
+
+ .. change::
+ :tags: New Features
+ :pullreq: 12997
+
+ lmdb: add backend commands for checking & refreshing indexes
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12993
+
+ Stop using the now deprecated ERR_load_CRYPTO_strings() to detect OpenSSL
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12992
+
+ YaHTTP: Prevent integer overflow on very 3large chunks
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12991
+
+ Work around Red Hat 8 pooping the bed in OpenSSL's headers
+
+.. changelog::
+ :version: 4.8.0
+ :released: 1st of June 2023
+
+ This is release 4.8.0 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+ In 4.8, the LMDB backend gains a new Lightning Stream-compatible schema, which requires a data migration (this is automatic, and there is no migration back to the old schema).
+ LMDB backend users should pay extra attention to the :doc:`Upgrade Notes <../upgrading>`.
+
+ `Lightning Stream <https://doc.powerdns.com/lightningstream>`_ is an `open source <https://github.com/PowerDNS/lightningstream>`_ data syncer that allows multiple nodes to sync LMDB (Lightning Memory-Mapped Database) data to and from an S3 (compatible) bucket. This has particular advantages in distributed and/or large-scale applications (i.e. ~1 million records), making DNS replication much, much easier to manage.
+
+ We are excited about how Lightning Stream simplifies running multiple distributed PowerDNS Authoritative servers, with full support for keeping record data and DNSSEC keys in sync, from multiple writers.
+
+ 4.8.0 improves the handling of accidental duplicate domains -- deleting a zone now deletes all versions of it.
+ This release also contains a few other fixes, please see the list below.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12869
+
+ do not answer with broken TYPE0 data when expanding an ENT wildcard
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12872
+
+ lmdb: delete duplicate domain entries in deleteDomain
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12868
+
+ pdnsutil: if user pushes unknown key in response to "problem with zone" prompt, do not throw away their changes
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12828
+
+ add setting workaround-11804-max-chunk-records
+
+.. changelog::
+ :version: 4.8.0-beta1
+ :released: 4th of May 2023
+
+ This is release 4.8.0-beta1 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+ In 4.8, the LMDB backend gains a new Lightning Stream-compatible schema, which requires a data migration (this is automatic, and there is no migration back to the old schema).
+ LMDB backend users should pay extra attention to the :doc:`Upgrade Notes <../upgrading>`.
+
+ `Lightning Stream <https://doc.powerdns.com/lightningstream>`_ is an `open source <https://github.com/PowerDNS/lightningstream>`_ data syncer that allows multiple nodes to sync LMDB (Lightning Memory-Mapped Database) data to and from an S3 (compatible) bucket. This has particular advantages in distributed and/or large-scale applications (i.e. ~1 million records), making DNS replication much, much easier to manage.
+
+ We are excited about how Lightning Stream simplifies running multiple distributed PowerDNS Authoritative servers, with full support for keeping record data and DNSSEC keys in sync, from multiple writers.
+
+ 4.8.0-beta1 adds logic to deal with domains existing twice in the database when two Lightning Stream nodes manage to add it at the same time. It also contains a few other fixes, please see the list below.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12729
+
+ LMDB: handle duplicate domain existence consistently
+
+ .. change::
+ :tags: New Features
+ :pullreq: 12768
+
+ ixfrdist: add a per domain max-soa-refresh option
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12636
+
+ lmdb: handle lack of support for RRset comments better
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12740
+
+ Pick the right signer name when a NSEC name is also a delegation point (Kees Monshouwer)
+
+ .. change::
+ :tags: New Features
+ :pullreq: 12669
+
+ LUA records: enhance ifportup() with lists of sets of addresses like ifurlup()
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12721
+
+ calm down the communicator loop (Kees Monshouwer)
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12706
+
+ Fixes a typo in pdnsutil clear-zone help output (san983)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12664
+
+ DNSRecord: Ensure that the content can be read or replaced, not edited
+
.. changelog::
:version: 4.8.0-alpha1
:released: 21st of March 2023
This is release 4.8.0-alpha1 of the Authoritative Server.
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
In this release, the LMDB backend gains a new Lightning Stream-compatible schema, which requires a data migration (this is automatic, and there is no migration back to the old schema).
LMDB backend users should pay extra attention to the :doc:`Upgrade Notes <../upgrading>`.
--- /dev/null
+Changelogs for 4.9.x
+====================
+
+.. changelog::
+ :version: 4.9.0
+ :released: 15th of March 2024
+
+ This is release 4.9.0 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.9.x.
+
+ 4.9 contains improvements to the API, ALIAS handling, catalog zones, and some tool improvements.
+ It also contains various bug fixes and smaller improvements, please see the list below.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13901
+
+ on OpenBSD, try harder to send on a non-blocking socket
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13900
+
+ LUA dblookup: switch qtype argument to int
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13899
+
+ revive remotebackend tests and fix failures
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13898
+
+ Docker: Only print config if debug flag is set
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13897
+
+ do not disable ns records at apex in consumer zones
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13896
+
+ catalog: include groups in hash calculation
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13895
+
+ LUA: support returning empty set in filterForward #13879
+
+.. changelog::
+ :version: 4.9.0-beta2
+ :released: 16th of February 2024
+
+ This is release 4.9.0-beta2 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.9.x.
+
+ 4.9 contains improvements to the API, ALIAS handling, catalog zones, and some tool improvements.
+ It also contains various bug fixes and smaller improvements, please see the list below.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13803
+
+ lmdb: remove mapasync mode, it was always a lie
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13753
+
+ ixfrdist: add support for outgoing notify
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13752
+
+ LUA records, pickchashed function
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13391
+
+ Add Lua function to pick records via name hash (Brian Rak)
+
+ .. change::
+ :tags: New Features
+ :pullreq: 12359
+
+ LUA records: add dblookup function
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13743
+
+ API: reject priority element in record
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13724
+
+ dnsname: Optimize parsing of uncompressed labels
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13738
+
+ debian: adjust option names in shipped configs
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13110
+
+ Log port with all freshness check failure scenarios. (Sander Smeenk)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13723
+
+ DNSName: correct len and offset types
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13725
+
+ fix tinydnsbackend compilation issue
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13729
+
+ getAllDomains catalog: avoid useless copy
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13722
+
+ LUA createForward: allow non-hex word prefix
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13633
+
+ set catalog in gsql getAllDomains
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13649
+
+ add a configurable delay for notifications
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13481
+
+ Add and document a `localwho()` function for LUA records (Bert Hubert)
+
+.. changelog::
+ :version: 4.9.0-beta1
+ :released: not released
+
+ This version number was skipped.
+
+.. changelog::
+ :version: 4.9.0-alpha1
+ :released: 12th of January 2024
+
+ This is release 4.9.0-alpha1 of the Authoritative Server.
+
+ Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.9.x.
+
+ This version contains improvements to the API, ALIAS handling, catalog zones, and some tool improvements.
+ It also contains various bug fixes and smaller improvements, please see the list below.
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13441
+
+ forward EDNS Client Subnet option during ALIAS processing
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13693
+
+ iputils: avoid unused warnings on !linux
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13613
+
+ Remove the `extern`ed `StatBag` from `ws-auth`
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13642
+
+ allow building in separate build directory (Chris Hofstaedtler)
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13635
+
+ improve wildcard CNAME handling (Kees Monshouwer)
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13514
+
+ auth api: flush all caches when flushing (Chris Hofstaedtler)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13153, 13641
+
+ Move method checking to Router (Aki Tuomi)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13619
+
+ Add supervisor to Auth container image
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13062
+
+ add loglevel-show setting to get logs formatted like structured logs
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13072
+
+ CAA records: handle empty value more gracefully, fixes #13070
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13023
+
+ Remove legacy terms from the codebase (Kees Monshouwer)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13191
+
+ Wrap ``DIR*`` objects in unique pointers to prevent memory leaks
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13322
+
+ ixfrdist: add NOTIFY receive support
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13028
+
+ bindparser add primary/secondary/etc. keywords (Kees Monshouwer)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13340
+
+ Netmask: Normalize subnet masks coming from a string
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13287
+
+ dnsscope: Add a `--port` option to select a custom port
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13014
+
+ Report auth settings deprecated in 4.5 (Josh Soref)
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13293
+
+ sdig: add rudimentary EDE output
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13192
+
+ Improve error message for missing GSS-TSIG feature (Andreas Jakum)
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13238
+
+ add default-catalog-zone setting
+
+ .. change::
+ :tags: New Features
+ :pullreq: 12086
+
+ API: replace zone contents et al (Chris Hofstaedtler)
+
+ .. change::
+ :tags: New Features
+ :pullreq: 11597
+
+ geoipbackend: Support reading zones from directory (Aki Tuomi)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13162
+
+ Print the list of loaded modules next to the config.h preset
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13168
+
+ Change the default for building with net-snmp from `auto` to `no`
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12565
+
+ harmonize \*xfr log messages (Josh Soref)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12949
+
+ Refactor the MultiThreadDistributor using `pdns::channel`
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13018
+
+ calidns: Fix setting an ECS source of 0
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13019
+
+ calidns: Prevent a crash on an empty domains file
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13065
+
+ report which backend failed to instantiate
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13063
+
+ add remote to logs when tcp thread dies (Chris Hofstaedtler)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13049
+
+ Add missing tools to pdns-tools package description (control) (Andreas Jakum)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12753
+
+ pkcs11signers: If private key object has `CKA_ALWAYS_AUTHENTICATE` attribute, perform `CKU_CONTEXT_SPECIFIC` login after `OperationInit` to make it actually work. (Aki Tuomi)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13029
+
+ wait for `mysql.service` (Andras Kovacs)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12877
+
+ bump sdist builders to alpine 3.18
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 11510
+
+ new option 'ignore-errors' for setting 'outgoing-axfr-expand-alias' (Klaus Darilion)
+
.. toctree::
:maxdepth: 2
+ 4.9
4.8
4.7
4.6
We want to explicitly thank Kees Monshouwer for digging up all the
DNSSEC improvements and porting them back to this release.
-When upgrading, please run "pdnssec rectify-all-zones" and trigger an
+When upgrading, please run ``pdnssec rectify-all-zones`` and trigger an
AXFR for all DNSSEC zones to make sure you benefit from all the
compliance improvements present in this version.
- `commit a7aa9be <https://github.com/PowerDNS/pdns/commit/a7aa9be>`__:
Replace hardcoded make with variable
- `commit e4fe901 <https://github.com/PowerDNS/pdns/commit/e4fe901>`__:
- make sure to run PKG\_PROG\_PKG\_CONFIG before the first PKG\_\*
+ make sure to run ``PKG_PROG_PKG_CONFIG`` before the first ``PKG_*``
usage
- `commit 29bf169 <https://github.com/PowerDNS/pdns/commit/29bf169>`__:
fix hmac-md5 TSIG key lookup
**Warning**: Version 3.3 of the PowerDNS Authoritative Server is a major
upgrade if you are coming from 2.9.x. There are also some important
changes if you are coming from 3.0, 3.1 or 3.2. Please refer to the
-`Upgrade documentation <authoritative/upgrading.md>`__ for important
+`Upgrade documentation <../upgrading.rst>`__ for important
information on correct and stable operation, as well as notes on
performance and memory use.
- We imported the TinyDNS backend by Ruben d'Arco. Code mostly in
`commit
2559 <http://wiki.powerdns.com/projects/trac/changeset/2559>`__. See
- `TinyDNS Backend <authoritative/backend-tinydns.md>`__.
+ `TinyDNS Backend <../backends/tinydns.rst>`__.
- Overriding C(XX)FLAGS is easier now. Problem pointed out by Jose
Arthur Benetasso Villanova and others, fix suggested by Sten Spans.
Patch in `commit
all important algorithms are supported.
Complete detail can be found in `Serving authoritative DNSSEC
-data <authoritative/dnssec.md>`__. The goal of 'PowerDNSSEC' is to allow
-existing PowerDNS installations to start serving DNSSEC with as little
-hassle as possible, while maintaining performance and achieving high
-levels of security.
-
-Tutorials and examples of how to use DNSSEC in PowerDNS can be found
-linked from http://powerdnssec.org.
+data <../dnssec/intro.rst>`__. The goal of PowerDNS's DNSSEC support
+is to allow existing PowerDNS installations to start serving DNSSEC with
+as little hassle as possible, while maintaining performance and
+achieving high levels of security.
PowerDNS Authoritative Server 3.0 development has been made possible by
the financial and moral support of
DNS <http://www.ipcom.at/en/dns/rcodezero_anycast/>`__, a subsidiary
of NIC.AT, the Austrian registry
- `SIDN, the Dutch registry <http://www.sidn.nl/>`__
-- .. (awaiting details) ..
This release has received exceptional levels of community support, and
we'd like to thank the following people in addition to those mentioned
Additionally, the bind2backend is almost ready to replace the stock bind
backend. If you run with Bind zones, you are cordially invited to
-substitute 'launch=bind2' for 'launch=bind'. This will happen
+substitute ``launch=bind2`` for ``launch=bind``. This will happen
automatically in 2.9.19!
In other news, the entire Wikipedia constellation now runs on PowerDNS
Recursor improvements and fixes.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-See `Recursion <authoritative/recursion.md>`__ for details. The changes
+See `Recursion <../guides/recursion.rst>`__ for details. The changes
below mean that all of the caveats listed for the recursor have now been
addressed.
- PostgreSQL now only depends on the C API and not on the deprecated
C++ one
- PowerDNS can now fully overrule external zones when doing recursion.
- See `Recursion <authoritative/recursion.md>`__.
+ See `Recursion <../guides/recursion.rst>`__.
Version 2.9.13
--------------
be restarted without having to restart the rest of the nameserver, for
example. Cooperation between the both halves of PowerDNS is also almost
seamless. As a result, 'non-lazy recursion' has been dropped. See
-`Recursion <authoritative/recursion.md>`__ for more details.
+`Recursion <../guides/recursion.rst>`__ for more details.
Furthermore, the recursor only works on Linux, Windows and Solaris (not
entirely). FreeBSD does not support the required functions. If you know
PowerDNS than yet know about it. So spread the word!
In other news, we now have a security page at
-`Security <security/index.md>`__. Furthermore, Maurice Nonnekes
+`Security <../security.rst>`__. Furthermore, Maurice Nonnekes
contributed an OpenBSD port! See `his
page <http://www.codeninja.nl/openbsd/powerdns/>`__ for more details!
the operator is in charge.
For more about all this coolness, see
-`“pdns\_control” <authoritative/running.md#pdnscontrol>`__ and
+`“pdns\_control” <running.rst#pdnscontrol>`__ and
`“pdns\_control
-commands” <authoritative/backend-bind.md#bind-control-commands>`__.
+commands” <backends/bind.rst#bind-control-commands>`__.
**Warning**: Again some changes in compilation instructions. The hybrid
pgmysql backend has been split up into 'gmysql' and 'gpgsql', sharing a
Developers: this version needs the new pdns-2.5.1 development kit,
available on http://downloads.powerdns.com/releases/dev. See also
-`Backend writers' guide <appendix/backend-writers-guide.md>`__.
+`Backend writers' guide <../appendices/backend-writers-guide.rst>`__.
And some small changes
The webserver also displays the efficiency of the new Query Cache.
The old Packet Cache is still there (and useful) but see
- `Authoritative Server Performance <authoritative/performance.md>`__
+ `Authoritative Server Performance <../performance.rst>`__
for more details.
- There is now the ability to shut off some logging at a very early
Developers: this version is compatible with the pdns-2.1 development
kit, available on http://downloads.powerdns.com/releases/dev. See also
-`*Backend writers' guide* <appendix/backend-writers-guide.md>`__.
+`*Backend writers' guide* <../appendices/backend-writers-guide.rst>`__.
This version fixes some stability issues with malformed or malcrafted
packets. An upgrade is advised. Furthermore, there are interesting new
Developers: this version is compatible with the pdns-2.1 development
kit, available on http://downloads.powerdns.com/releases/dev. See also
-`Backend writers' guide <appendix/backend-writers-guide.md>`__
+`Backend writers' guide <../appendices/backend-writers-guide.rst>`__
This release adds the Generic MySQL backend which allows full
master/slave semantics with MySQL and InnoDB tables (or other tables
Developers: this version is compatible with the pdns-2.1 development
kit, available on http://downloads.powerdns.com/releases/dev. See also
-`Backend writers' guide <appendix/backend-writers-guide.md>`__
+`Backend writers' guide <../appendices/backend-writers-guide.rst>`__
Again a big release. PowerDNS is seeing some larger deployments in more
demanding environments and these are helping shake out remaining issues,
- **pdns\_control purge** can now also purge based on suffix, allowing
operators to purge an entire domain from the packet cache instead of
only specific records. See also
- `pdns\_control <authoritative/running.md#pdnscontrol>`__ Thanks to
+ `pdns\_control <running.rst#pdnscontrol>`__ Thanks to
Mike Benoit for this suggestion.
- **soa-serial-offset** for installations with small SOA serial numbers
wishing to register .DE domains with DENIC which demands six-figure
SOA serial numbers. See also `Chapter 21, *Index of all Authoritative
- Server settings* <authoritative/settings.md>`__.
+ Server settings* <../settings.rst>`__.
Version 2.1
-----------
with user expectations. If a recursive question can be answered
entirely from local data, it is. To restore old behaviour, disable
**lazy-recursion**. Also see
- `Recursion <authoritative/recursion.md>`__.
+ `Recursion <../guides/recursion.rst>`__.
Features
^^^^^^^^
- Zone2sql now accepts ^^transactions to wrap zones in a transaction
for PostgreSQL and Oracle output. This is a major speedup and also
makes for better isolation of inserts. See
- `Zone2sql <authoritative/migration.md#zone2sql>`__.
+ `Zone2sql <migration.rst#zone2sql>`__.
- **pdns\_control** now has the ability to purge the PowerDNS cache or
parts of it. This enables operators to raise the TTL of the Packet
Cache to huge values and only to invalidate the cache when changes
are made. See also `Authoritative Server
- Performance <authoritative/performance.md>`__ and
- `pdns\_control <authoritative/running.md#pdnscontrol>`__.
+ Performance <../performance.rst>`__ and
+ `pdns\_control <../running.rst#pdnscontrol>`__.
Version 2.0.1
-------------
Version 2.0 Release Candidate 1
-------------------------------
-The MacOS X release! A very experimental OS X 10.2 build has been added.
+The Mac OS X release! A very experimental OS X 10.2 build has been added.
Furthermore, the Windows version is now in line with Unix with respect
to capabilities. The ODBC backend now has the code to function as both a
master and a slave.
^^^^^^^^
- pdns\_control (see
- `pdns\_control <authoritative/running.md#pdnscontrol>`__) now opens
+ `pdns\_control <running.rst#pdnscontrol>`__) now opens
the local end of its socket in ``/tmp`` instead of next to the remote
socket (by default ``/var/run``). This eases the way for allowing
non-root access to pdns\_control. When running chrooted (see
`Chapter 7, *Security settings &
- considerations* <common/security.md>`__), the local socket again
+ considerations* <../security.rst>`__), the local socket again
moves back to ``/var/run``.
- pdns\_control now has a 'version' command. See `Section 1.1,
- “pdns\_control” <authoritative/running.md#pdnscontrol>`__.
+ “pdns\_control” <../running.rst#pdnscontrol>`__.
Version 1.99.11 Prerelease
--------------------------
`Supermaster automatic provisioning of
slaves <authoritative/modes-of-operation.md#supermaster>`__.
- Recursing backend can now live on a non-standard (!=53) port. See
- `Recursion <authoritative/recursion.md>`__.
+ `Recursion <../guides/recursion.rst>`__.
- Slave zone retrieval is now queued instead of immediate, which scales
better and is more resilient to temporary failures.
- **max-queue-length** parameter. If this many packets are queued for
Feature enhancements
^^^^^^^^^^^^^^^^^^^^
-- Recursing backend. See `Recursion <authoritative/recursion.md>`__.
+- Recursing backend. See `Recursion <../guides/recursion.rst>`__.
Allows recursive and authoritative DNS on the same IP address.
-- `NAPTR support <types.md#naptr>`__, which is especially useful for
+- `NAPTR support <appendices/types.rst#naptr>`__, which is especially useful for
the ENUM/E.164 community.
- Zone transfers can now be allowed per `netmask instead of only per IP
- address <authoritative/settings.md#allow-axfr-ips>`__.
+ address <../settings.rst#allow-axfr-ips>`__.
- Preliminary support for slave operation included. Only for the
adventurous right now! See `Slave
- operation <authoritative/modes-of-operation.md>`__
+ operation <../modes-of-operation.rst>`__
- All record types now documented, see `Supported record types and
- their storage <types.md>`__.
+ their storage <../appendices/types.rst>`__.
Known bugs
^^^^^^^^^^
If resolution fails, and the previous security-status was 1, the new security-status becomes 0 ('no data').
If the security-status was higher than 1, it will remain that way, and not get set to 0.
-In this way, security-status of 0 really means 'no data', and can not mask a known problem.
+In this way, security-status of 0 really means 'no data', and cannot mask a known problem.
Distributions
~~~~~~~~~~~~~
------------------------
If you have a security problem to report, please email us at both peter.van.dijk@powerdns.com and remi.gacogne@powerdns.com.
-In case you want to encrypt your report using PGP, please use: https://www.powerdns.com/powerdns-keyblock.asc
+In case you want to encrypt your report using PGP, please use: https://doc.powerdns.com/powerdns-keyblock.asc
Please do not mail security issues to public lists, nor file a ticket, unless we do not get back to you in a timely manner.
We fully credit reporters of security issues, and respond quickly, but please allow us a reasonable timeframe to coordinate a response.
* `16E1 2866 B773 8C73 976A 5743 6FFC 3343 9B0D 04DF <https://pgp.mit.edu/pks/lookup?op=get&search=0x6FFC33439B0D04DF>`_
* `990C 3D0E AC7C 275D C6B1 8436 EACA B90B 1963 EC2B <https://pgp.mit.edu/pks/lookup?op=get&search=0xEACAB90B1963EC2B>`_
-There is a PGP keyblock with these keys available on `https://www.powerdns.com/powerdns-keyblock.asc <https://www.powerdns.com/powerdns-keyblock.asc>`_.
+There is a PGP keyblock with these keys available on `https://doc.powerdns.com/powerdns-keyblock.asc <https://www.powerdns.com/powerdns-keyblock.asc>`_.
Older releases (4.3.x and earlier) can also be signed with one of the following keys:
# General information about the project.
project = 'PowerDNS Authoritative Server'
-copyright = '2001-' + str(datetime.date.today().year) + ', PowerDNS.COM BV'
+copyright = 'PowerDNS.COM BV'
author = 'PowerDNS.COM BV'
# The version info for the project you're documenting, acts as replacement for
DNSSEC is a major change in the way DNS works. Furthermore, there is a
bewildering array of settings that can be configured.
-It is well possible to configure DNSSEC in such a way that your domain
+It is easy to (mis)configure DNSSEC in such a way that your domain
will not operate reliably, or even, at all. We advise operators to stick
to the keying defaults of ``pdnsutil secure-zone``.
- Morten Stevens
- Pieter Lexis
-This list is far from complete yet ..
+and everyone else who contributed to making this possible.
DNSSEC is a complicated subject, but it is not required to know all the
ins and outs of this protocol to be able to use PowerDNS. In this
section, we explain the core concepts that are needed to operate a
-PowerDNSSEC installation.
+PowerDNS installation with DNSSEC.
-Zone material is enhanced with signatures using 'keys'. Such a signature
+Zone material is enhanced with signatures using ``keys``. Such a signature
(called an RRSIG) is a cryptographic guarantee that the data served is
the original data. DNSSEC keys are asymmetric (RSA, DSA, ECSDA or GOST),
the public part is published in DNS and is called a DNSKEY record, and
key, we are done in theory.
However, for a variety of reasons, most DNSSEC operations run with
-another layer of keys. The so called 'Key Signing Key' is sent to the
+another layer of keys. The so called ``Key Signing Key`` is sent to the
parent zone, and this Key Signing Key is used to sign a new set of keys
called the Zone Signing Keys.
This setup allows us to change our keys without having to tell the zone
operator about it.
-A final challenge is how to DNSSEC sign the answer 'no such domain'. In
-the language of DNS, the way to say 'there is no such domain' (NXDOMAIN)
+A final challenge is how to DNSSEC sign the answer *no such domain*. In
+the language of DNS, the way to say *there is no such domain* (``NXDOMAIN``)
or there is no such record type is to send an empty answer. Such empty
answers are universal, and can't be signed.
-In DNSSEC parlance we therefore sign a record that says 'there are no
-domains between A.powerdnssec.org and C.powerdnssec.org'. This securely
-tells the world that B.powerdnssec.org does not exist. This solution is
-called NSEC, and is simple but has downsides - it also tells the world
+In DNSSEC parlance we therefore sign a record that says *there are no
+domains between* ``A.powerdnssec.org`` *and* ``C.powerdnssec.org``. This securely
+tells the world that ``B.powerdnssec.org`` does not exist. This solution is
+called ``NSEC``, and is simple but has downsides - it also tells the world
exactly which records DO exist.
So alternatively, we can say that if a certain mathematical operation
-(an 'iterated salted hash') is performed on a question, that no valid
+(an *iterated salted hash*) is performed on a question, that no valid
answers exist that have as outcome of this operation an answer between
-two very large numbers. This leads to the same 'proof of non-existence'.
-This solution is called NSEC3.
+two very large numbers. This leads to the same *proof of non-existence*.
+This solution is called ``NSEC3``.
-A PowerDNS zone can either be operated in NSEC or in one of two NSEC3
-modes ('inclusive' and 'narrow').
+A PowerDNS zone can either be operated in ``NSEC`` or in one of two ``NSEC3``
+modes (``inclusive`` and ``narrow``).
-Dynamic DNS Update (RFC2136)
-============================
+Dynamic DNS Update (RFC 2136)
+=============================
Starting with the PowerDNS Authoritative Server 3.4.0, DNS update
support is available. There are a number of items NOT supported:
.. note::
Powerdns will always use :ref:`metadata-soa-edit` when serving SOA
- records, thus a query for the SOA record of the recently update domain,
+ records, thus a query for the SOA record of the recently updated domain,
might have an unexpected result due to a SOA-EDIT setting.
An example::
zone behaves in certain circumstances.
.. warning::
- Domain metadata is only available for DNSSEC capable
- backends! Make sure to enable the proper '-dnssec' setting to benefit.
-
-.. warning::
- When multiple backends are in use, domain metadata is only retrieved from
- and written to the first DNSSEC-capable backend, no matter where the related
- zones live.
+ When multiple backends are in use, domain metadata is only retrieved from and written to the first DNSSEC-capable or metadata-capable backend, no matter where the related zones live.
For the BIND backend, this information is either stored in the
:ref:`setting-bind-dnssec-db` or the hybrid database,
* PRESIGNED
* TSIG-ALLOW-AXFR
-The option SOA-EDIT-API can not be written or read via the HTTP API metadata endpoint.
+The option SOA-EDIT-API cannot be written or read via the HTTP API metadata endpoint.
.. _metadata-allow-axfr-from:
Our zone is currently fully signed with two algorithms, and keys for both algorithms are published.
This means that a DS for either the old or new algorithm is sufficient for validation.
-So, we can switch the DS - there is no need to have DSes for both algorithms in the parent zone.
+We can now switch the DS - there is no need to have DSes for both algorithms in the parent zone.
Using ``pdnsutil show-zone example.com`` or ``pdnsutil export-zone-ds example.com``, extract the new DNSKEYs or new DSes, depending on what the parent zone operator takes as input.
Note that these commands print DNSKEYs and/or DSes for both the old and the new algorithm.
Conclusion
----------
-In another hours-to-a-few-days, the old signatures will have expired from caches.
+In another hours-to-a-few-days, the old signatures will expire from caches.
+
Your algorithm roll is complete.
\ No newline at end of file
``example.net``, it will resolve the A record for
``mywebapp.paas-provider.net`` and serve an answer for ``example.net``
with that A record.
+If the ALIAS target cannot be resolved (SERVFAIL) or does not exist (NXDOMAIN) the authoritative server will answer SERVFAIL.
-When a zone containing ALIAS records is transferred over AXFR, the
-:ref:`setting-outgoing-axfr-expand-alias`
-setting controls the behaviour of ALIAS records. When set to 'no' (the
-default), ALIAS records are sent as-is (RRType 65401 and a DNSName in
-the RDATA) in the AXFR. When set to 'yes', PowerDNS will lookup the A
-and AAAA records of the name in the ALIAS-record and send the results in
-the AXFR.
+.. _alias_axfr:
+
+AXFR Zone transfers
+-------------------
+
+When a zone containing ALIAS records is transferred over AXFR, the :ref:`setting-outgoing-axfr-expand-alias` setting controls the behaviour of ALIAS records.
+
+When set to 'no' (the default), ALIAS records are sent as-is (RRType 65401 and a DNSName in the RDATA) in the AXFR.
+
+When set to 'yes', PowerDNS will look up the A and AAAA records of the name in the ALIAS-record and send the results in the AXFR.
+This is useful when your secondary servers do not understand ALIAS, or should not look up the addresses themselves.
+Note that secondaries will not automatically follow changes in those A/AAAA records unless you AXFR regularly.
+
+If the ALIAS target cannot be resolved, the AXFR will fail.
+When set to 'ignore-errors', an unresolvable ALIAS target will be omitted from the outgoing transfer.
+
+.. warning::
+ Setting ``setting-outgoing-axfr-expand-alias`` to 'ignore-errors', will allow an outgoing AXFR with a broken ALIAS target to complete, but the secondary server will receive an incomplete zone.
+ There is no standard mechanism for automatic re-transfer for zones broken in this way.
+ You should make sure this behaviour is acceptable in your use case, provide custom integration tooling to monitor such problems, and possibly fix them automatically.
-Set ``outgoing-axfr-expand-alias`` to 'yes' if your slaves don't
-understand ALIAS or should not look up the addresses themselves. Note
-that slaves will not automatically follow changes in those A/AAAA
-records unless you AXFR regularly.
.. note::
The ``expand-alias`` setting does not exist in PowerDNS
of ALIAS records is supported on AXFR (**not** on live-signing). Set
``outgoing-axfr-expand-alias`` to 'yes' and enable DNSSEC for the zone
on the master. PowerDNS will sign the A/AAAA records during the AXFR.
-
-
KSK Rollover
============
-Before attempting a KSK rollover, please read :rfc:`RFC 6781 "DNSSEC
-Operational Practices, Version 2", section 4 <6781#section-4>` carefully to
-understand the terminology, actions and timelines (TTL and RRSIG expiry)
-involved in rolling a KSK.
+Before attempting a KSK rollover, please read :rfc:`RFC 6781 "DNSSEC Operational Practices, Version 2", section 4 <6781#section-4>` carefully to understand the terminology, actions and timelines (TTL and RRSIG expiry) involved in rolling a KSK.
-This How To describes the "Double-Signature Key Signing Key Rollover"
-from the above mentioned RFC. The following instruction work for
-both a KSK and a CSK.
+This How To describes the "Double-Signature" scheme from the above mentioned RFC, as specified in :rfc:`section 4.1.2 <6781#section-4.1.2>`.
+Phases are named after the steps in the diagram in that section.
-To start the rollover, add an **active** new KSK to the zone
-(example.net in this case):
+After every change, use your favourite DNSSEC checker (`DNSViz <https://dnsviz.net/>`__, `VeriSign DNSSEC Analyzer <https://dnssec-debugger.verisignlabs.com/>`__, a validating resolver) to make sure no mistakes have crept in.
-.. code-block:: shell
+.. warning::
+
+ For every mutation to your zone make sure that your serial is bumped, so your secondaries pick up the changes too.
+ If you are using AXFR replication, this usually is as simple as ``pdnsutil increase-serial example.com``
+
+Phase: Initial
+--------------
+
+In the ``initial`` situation, we have a KSK and the parent zone contains a DS matching that KSK.
+Assuming this situation has existed for a few days, or perhaps way longer, we can move on to the ``new DNSKEY`` phase without delay.
+
+Phase: new DNSKEY
+-----------------
- pdnsutil add-zone-key example.net ksk active
+At first note down algorithm of currently used KSK, because new KSK shall use the same one, by running following command:
-Note that a key with same algorithm as the KSK to be replaced should be
-created, as this is not an algorithm roll over.
+.. code-block:: shell
+
+ pdnsutil show-zone example.com
-If this zone is of the type 'MASTER', increase the SOA serial. The
-rollover is now in the "New KSK" stage. Retrieve the DS record(s) for
-the new KSK:
+To create a new **active** and **published** KSK with the same algorithm for the zone, run something like:
.. code-block:: shell
- pdnsutil show-zone example.net
+ pdnsutil add-zone-key example.com ksk active published ALGORITHM
+
+Please note down the key ID that ``add-zone-key`` reports. You can also retrieve it later with ``pdnsutil show-zone example.com``.
+
+After this the DNSKEY set will be signed by both KSKs.
+
+Please check that your secondaries now show both the old and new DNSKEYs when queried for them with ``dig DNSKEY example.com @...``.
-And communicate this securely to your registrar/parent zone, replacing
-the existing data. Now wait until the new DS is published in the
-parent zone and at least the TTL for the DS records has passed. The
-rollover is now in the "DS Change" state and can continue to the
-"DNSKEY Removal" stage by actually deleting the old KSK.
+Now that the new DNSKEY is active and published, we need to wait for caches to pick it up. Check the DNSKEY TTL and then wait for at least that long.
-.. note::
- The key-id for the old KSK is shown in the output of
- ``pdnsutil show-zone example.net``.
+Phase: DS change
+----------------
+
+The DNSKEY set is currently signed with both KSKs and keys of both are published.
+This means that a DS for either old or new KSK is sufficient for validation.
+We can now switch the DS record in the parent zone - there is no need to have DSes for both KSKs in the parent zone.
+
+Using ``pdnsutil show-zone example.com`` or ``pdnsutil export-zone-ds example.com``, extract the DNSKEY or DS for new KSK, depending on what the parent zone operator takes as input.
+Note that these commands print DNSKEYs and/or DSes for both the old and the new KSK.
+
+Check the DS TTL at the parent, for example: ``dig DS example.com @c.gtld-servers.net`` for a delegation from ``.com``.
+
+Submit the new DNSKEY and/or DS for of new KSK to the parent, and make sure to delete those for the old KSK.
+
+Check again with the parent to see whether the new DS is published.
+
+Then, wait for at least as long as the TTL for the old DS was.
+
+Phase: DNSKEY removal
+---------------------
+
+The parent DS is pointing at the new KSK and the old DS has expired from all caches.
+However, both sets of DNSKEYs are still in caches.
+It is time to remove the old DNSKEY:
.. code-block:: shell
- pdnsutil remove-zone-key example.net KEY-ID
+ pdnsutil remove-zone-key example.com OLD_KSK_ID
+
+Please check that your secondaries now only show the new set of keys when queried with ``dig DNSKEY example.com @...``.
+
+Conclusion
+----------
+
+After at least another DNSKEY TTL time the old DNSKEY shall expire from caches.
-If this zone is of the type 'MASTER', increase the SOA serial.
-The rollover is now complete.
+Your KSK Rollover is complete.
\ No newline at end of file
This is most likely an ``apt-get`` or ``yum install`` away, see the
`Recursor documentation <https://doc.powerdns.com/recursor/getting-started.html#installation>`__ for more information.
-It might be possible that the Recursor can not start as the listen
+It might be possible that the Recursor cannot start as the listen
address is in use by the Authoritative Server, this is fine for now.
Now configure the listen addresses and ACL for the Recursor to be the
`Recursor's Install Guide <https://doc.powerdns.com/recursor/getting-started.html#installation>`__ for more
information.
-It might be possible that the Recursor can not start as the listen
+It might be possible that the Recursor cannot start as the listen
address is in use by the Authoritative Server, this is fine for now.
Configure the recursor to listen on the local loopback interface on a
no-ipv6.example.org IN HTTPS 1 . ipv4hint=auto ipv6hint=auto
no-ipv6.example.org IN A 192.0.2.2
-Here, no AAAA record exists for www.example.org, so PowerDNS can not put any data in the ipv6hint.
+Here, no AAAA record exists for www.example.org, so PowerDNS cannot put any data in the ipv6hint.
In this case, the ipv6hint parameter is dropped when answering the query (and on AXFR)::
;; QUESTION SECTION:
ZSK Rollover
============
-This how to describes the way to roll a ZSK that is not a secure
-entrypoint (a ZSK that is not tied to a DS record in the parent zone)
-using the :rfc:`"RFC 6781 Pre-Publish Zone Signing Key
-Rollover" <6781#section-4.1.1.1>`
-method. The documentation linked above also lists the minimum time
-between stages. **PLEASE READ THAT DOCUMENT CAREFULLY**
+Before attempting a ZSK rollover, please read :rfc:`RFC 6781 "DNSSEC Operational Practices, Version 2", section 4 <6781#section-4>` carefully to understand the terminology, actions and timelines (TTL and RRSIG expiry) involved in rolling a ZSK.
-First, create a new inactive ZSK for the zone (if one already exists,
-you can skip this step), we add an ECDSA 256 bit key (algorithm 13)
-here:
+This How To describes the "Pre-Publish" approach from the above mentioned RFC, as specified in :rfc:`section 4.1.1.1 <6781#section-4.1.1.1>`.
+Phases are named after the steps in the diagram in that section.
+
+.. warning::
+
+ The following instructions assume rollover of a key which is NOT a Secure Entry Point (SEP), please confirm this fact before proceeding any further.
+
+After every change, use your favourite DNSSEC checker (`DNSViz <https://dnsviz.net/>`__, `VeriSign DNSSEC Analyzer <https://dnssec-debugger.verisignlabs.com/>`__, a validating resolver) to make sure no mistakes have crept in.
+
+.. warning::
+
+ For every mutation to your zone make sure that your serial is bumped, so your secondaries pick up the changes too.
+ If you are using AXFR replication, this usually is as simple as ``pdnsutil increase-serial example.com``
+
+Phase: Initial
+--------------
+
+In the ``initial`` situation, we have old ZSK key used to sign all the data in the zone.
+Assuming this situation has existed for a few days, or perhaps way longer, we can move on to the ``new DNSKEY`` phase without delay.
+
+Phase: new DNSKEY
+-----------------
+
+At first note down algorithm of currently used ZSK, because new ZSK shall use the same one, by running following command:
+
+.. code-block:: shell
+
+ pdnsutil show-zone example.com
+
+To create a new **inactive** but **published** ZSK with the same algorithm, run something like:
.. code-block:: shell
- pdnsutil add-zone-key example.net zsk inactive ecdsa256
+ pdnsutil add-zone-key example.com zsk inactive published ALGORITHM
+
+Please note down the key ID that ``add-zone-key`` reports. You can also retrieve it later with ``pdnsutil show-zone example.com``.
+
+PowerDNS will now publish the new DNSKEY while the old DNSKEY remains published and active for signing.
-You are now almost at the "new DNSKEY"-stage of the rollover, if the
-zone is of type 'MASTER' you'll need to update the SOA serial in the
-database and wait for the slaves to pickup the zone change.
+Please check that your secondaries now show both the old and new DNSKEYs when queried for them with ``dig DNSKEY example.com @...``.
-To change the RRSIGs on your records, the new key must be made active.
-Note: you can get the key-ids with ``pdnsutil show-zone example.net``:
+Now that the new DNSKEY is published, we need to wait for caches to pick it up. Check the DNSKEY TTL and then wait at least that long.
+
+Phase: new RRSIGs
+-----------------
+
+To change the RRSIGs on records in the zone, the new DNSKEY must be made active and the old DNSKEY must be made inactive.
.. code-block:: shell
- pdnsutil activate-zone-key example.net new-key-id
- pdnsutil deactivate-zone-key example.net previous-key-id
+ pdnsutil activate-zone-key example.com NEW-ZSK-ID
+ pdnsutil deactivate-zone-key example.com OLD-ZSK-ID
+
+After this, PowerDNS will sign all records in the zone with the new ZSK and remove all signatures made with the old ZSK.
-Again, if this is a 'MASTER'-zone, update the SOA serial. You are now at
-the "new RRSIGs" stage of the roll over.
+Please check that your secondaries now show only the new signatures.
-The last step is to remove the old key from the completely:
+In your zone, check for the highest TTL you can find.
+This includes the SOA TTL and the SOA MINIMUM, which affect negative caching, including NSEC/NSEC3 records.
+:ref:`The DNSKEY TTL is also taken from the SOA MINIMUM.<dnssec-ttl-notes>`
+
+Now wait for at least that long.
+Depending on your setup, this will usually be between a few hours and a few days.
+
+Phase: DNSKEY removal
+---------------------
+
+The last step is to remove the old DNSKEY from the zone:
.. code-block:: shell
- pdnsutil remove-zone-key example.net previous-key-id
+ pdnsutil remove-zone-key example.com OLD-ZSK-ID
+
+Please check that your secondaries now show only the new DNSKEY when queried with ``dig DNSKEY example.com @...``.
+
+Conclusion
+----------
-Don't forget to update the SOA serial for 'MASTER' zones. The rollover
-is now at the "DNSKEY removal" stage and complete.
+After at least another DNSKEY TTL time the old DNSKEY shall expire from caches.
+Your ZSK Rollover is complete.
\ No newline at end of file
required: true
description: The kind of metadata
responses:
- '200':
+ '204':
description: OK
<<: *commonErrors
.. code-block:: http
- PATCH /api/v1/servers/localhost/example.org. HTTP/1.1
+ PATCH /api/v1/servers/localhost/zones/example.org. HTTP/1.1
X-API-Key: secret
Content-Type: application/json
.. code-block:: http
- PATCH /api/v1/servers/localhost/example.org. HTTP/1.1
+ PATCH /api/v1/servers/localhost/zones/example.org. HTTP/1.1
X-API-Key: secret
Content-Type: application/json
resolver. This is a :class:`ComboAddress`.
``who``
IP address of requesting resolver as a :class:`ComboAddress`.
+``localwho``
+ IP address (including port) of socket on which the question arrived.
Functions available
-------------------
This function also works for CNAME or TXT records.
+.. function:: pickchashed(values)
+
+ Based on the hash of ``bestwho``, returns a string from the list
+ supplied, as weighted by the various ``weight`` parameters and distributed consistently.
+ Performs no uptime checking.
+
+ :param values: table of weight, string (such as IPv4 or IPv6 address).
+
+ This function works almost like :func:`pickwhashed` while bringing the following properties:
+ - reordering the list of entries won't affect the distribution
+ - updating the weight of an entry will only affect a part of the distribution
+ - because of the previous properties, the CPU and memory cost is a bit higher than :func:`pickwhashed`
+
+ Hashes will be pre computed the first time such a record is hit and refreshed if needed. If updating the list is done often,
+ the cash may grow. A cleanup routine is performed every :ref:`setting-lua-consistent-hashes-cleanup-interval` seconds (default 1h)
+ and cleans cached entries for records that haven't been used for :ref:`setting-lua-consistent-hashes-expire-delay` seconds (default 24h)
+
+ An example::
+
+ mydomain.example.com IN LUA A ("pickchashed({ "
+ " {15, "192.0.2.1"}, "
+ " {100, "198.51.100.5"} "
+ "}) ")
+
+
.. function:: pickwhashed(values)
Based on the hash of ``bestwho``, returns a string from the list
" {100, "198.51.100.5"} "
"}) ")
+.. function:: picknamehashed(values)
+
+ Based on the hash of the DNS record name, returns a string from the list supplied, as weighted by the various ``weight`` parameters.
+ Performs no uptime checking.
+
+ :param values: table of weight, string (such as IPv4 or IPv6 address).
+
+ This allows basic persistent load balancing across a number of backends.
+ It means that ``test.mydomain.example.com`` will always resolve to the same IP, but ``test2.mydomain.example.com`` may go elsewhere.
+ This function is only useful for wildcard records.
+
+ This works similar to round-robin load balancing, but has the advantage of making traffic for the same domain always end up on the same server which can help cache hit rates.
+
+ This function also works for CNAME or TXT records.
+
+ An example::
+
+ *.mydomain.example.com IN LUA A ("picknamehashed({ "
+ " {15, "192.0.2.1"}, "
+ " {100, "198.51.100.5"} "
+ "}) ")
+
.. function:: pickwrandom(values)
.. function:: createReverse(format, [exceptions])
- Used for generating default hostnames from IPv4 wildcard reverse DNS records, e.g. ``*.0.0.127.in-addr.arpa``
-
+ Used for generating default hostnames from IPv4 wildcard reverse DNS records, e.g. ``*.0.0.127.in-addr.arpa``
+
See :func:`createReverse6` for IPv6 records (ip6.arpa)
See :func:`createForward` for creating the A records on a wildcard record such as ``*.static.example.com``
-
+
Returns a formatted hostname based on the format string passed.
:param format: A hostname string to format, for example ``%1%.%2%.%3%.%4%.static.example.com``.
- ``%6`` would be ``7f00000f`` (127 is 7f, and 15 is 0f in hexadecimal)
Example records::
-
+
*.0.0.127.in-addr.arpa IN LUA PTR "createReverse('%1%.%2%.%3%.%4%.static.example.com')"
*.1.0.127.in-addr.arpa IN LUA PTR "createReverse('%5%.static.example.com')"
*.2.0.127.in-addr.arpa IN LUA PTR "createReverse('%6%.static.example.com')"
-
+
When queried::
-
+
# -x is syntactic sugar to request the PTR record for an IPv4/v6 address such as 127.0.0.5
# Equivalent to dig PTR 5.0.0.127.in-addr.arpa
$ dig +short -x 127.0.0.5 @ns1.example.com
7f000205.static.example.com.
.. function:: createForward()
-
+
Used to generate the reverse DNS domains made from :func:`createReverse`
-
+
Generates an A record for a dotted or hexadecimal IPv4 domain (e.g. 127.0.0.1.static.example.com)
-
+
It does not take any parameters, it simply interprets the zone record to find the IP address.
-
+
An example record for zone ``static.example.com``::
-
+
*.static.example.com IN LUA A "createForward()"
-
+
This function supports the forward dotted format (``127.0.0.1.static.example.com``), and the hex format, when prefixed by two ignored characters (``ip40414243.static.example.com``)
-
+
When queried::
-
+
$ dig +short A 127.0.0.5.static.example.com @ns1.example.com
127.0.0.5
-
+
Since 4.8.0: the hex format can be prefixed by any number of characters (within DNS label length limits), including zero characters (so no prefix).
.. function:: createReverse6(format[, exceptions])
Used for generating default hostnames from IPv6 wildcard reverse DNS records, e.g. ``*.1.0.0.2.ip6.arpa``
-
+
**For simplicity purposes, only small sections of IPv6 rDNS domains are used in most parts of this guide,**
**as a full ip6.arpa record is around 80 characters long**
-
+
See :func:`createReverse` for IPv4 records (in-addr.arpa)
See :func:`createForward6` for creating the AAAA records on a wildcard record such as ``*.static.example.com``
-
+
Returns a formatted hostname based on the format string passed.
:param format: A hostname string to format, for example ``%33%.static6.example.com``.
:param exceptions: An optional table of overrides. For example ``{['2001:db8::1'] = 'example.example.com.'}`` would, when generating a name for IP ``2001:db8::1``, return ``example.example.com`` instead of something like ``2001--db8.example.com``.
Formatting options:
-
+
- ``%1%`` to ``%32%`` are individual characters (nibbles)
- **Example PTR record query:** ``a.0.0.0.1.0.0.2.ip6.arpa``
- ``%1%`` = 2
- ``%34%`` - returns ``2001`` (chunk 1)
- ``%35%`` - returns ``000a`` (chunk 2)
- ``%41%`` - returns ``0123`` (chunk 8)
-
+
Example records::
-
+
*.1.0.0.2.ip6.arpa IN LUA PTR "createReverse6('%33%.static6.example.com')"
*.2.0.0.2.ip6.arpa IN LUA PTR "createReverse6('%34%.%35%.static6.example.com')"
-
+
When queried::
-
+
# -x is syntactic sugar to request the PTR record for an IPv4/v6 address such as 2001::1
# Equivalent to dig PTR 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.0.0.0.a.0.0.0.1.0.0.2.ip6.arpa
# readable version: 1.0.0.0 .0.0.0.0 .0.0.0.0 .0.0.0.0 .0.0.0.0 .b.0.0.0 .a.0.0.0 .1.0.0.2 .ip6.arpa
-
+
$ dig +short -x 2001:a:b::1 @ns1.example.com
2001-a-b--1.static6.example.com.
-
+
$ dig +short -x 2002:a:b::1 @ns1.example.com
2002.000a.static6.example.com
.. function:: createForward6()
-
+
Used to generate the reverse DNS domains made from :func:`createReverse6`
-
+
Generates an AAAA record for a dashed compressed IPv6 domain (e.g. ``2001-a-b--1.static6.example.com``)
-
+
It does not take any parameters, it simply interprets the zone record to find the IP address.
-
+
An example record for zone ``static.example.com``::
-
+
*.static6.example.com IN LUA AAAA "createForward6()"
-
+
This function supports the dashed compressed format (i.e. ``2001-a-b--1.static6.example.com``), and the dot-split uncompressed format (``2001.db8.6.5.4.3.2.1.static6.example.com``)
-
+
When queried::
-
+
$ dig +short AAAA 2001-a-b--1.static6.example.com @ns1.example.com
2001:a:b::1
*.static4.example.com IN LUA A "filterForward(createForward(), newNMG({'192.0.2.0/24', '10.0.0.0/8'}))"
+ Since 4.9.0: if the fallback parameter is an empty string, ``filterForward`` returns an empty set, yielding a NODATA answer.
+ You cannot combine this feature with DNSSEC.
+
Helper functions
~~~~~~~~~~~~~~~~
Returns true if ``bestwho`` is within any of the listed subnets.
:param [string] netmasks: The list of IP addresses to check against
+
+.. function:: dblookup(name, type)
+
+ .. versionadded:: 4.9.0
+
+ Does a database lookup for name and type, and returns a (possibly empty) array of string results.
+
+ Please keep the following in mind:
+
+ * it does not evaluate any LUA code found
+ * if you needed just one string, perhaps you want ``dblookup('www.example.org', pdns.A)[1]`` to take the first item from the array
+ * some things, like ifurlup, don't like empty tables, so be careful not to accidentally look up a name that does not have any records of that type, if you are going to use the result in ``ifurlup``
+
+ Example usage: ::
+
+ www IN LUA A "ifurlup('https://www.example.com/', {dblookup('www1.example.com', pdns.A), dblookup('www2.example.com', pdns.A), dblookup('www3.example.com', pdns.A)})"
+
+ :param string name: Name to look up in the database
+ :param int type: DNS type to look for
interoperability, and strive to turn this functionality into a broadly
supported standard.
-To enable this feature, either set 'enable-lua-records' in the configuration,
-or set the 'ENABLE-LUA-RECORDS' per-zone metadata item to 1.
+To enable this feature, either set `:ref:`setting-enable-lua-records` in the configuration,
+or set the ``ENABLE-LUA-RECORDS`` per-zone metadata item to ``1``.
In addition, to benefit from the geographical features, make sure the PowerDNS
launch statement includes the ``geoip`` backend.
Before delving into the details, some examples may be of use to explain what
dynamic records can do.
-Here is a very basic example::
+Here is a very basic example using :func:`ifportup`::
www IN LUA A "ifportup(443, {'192.0.2.1', '192.0.2.2'})"
synchronously. In the background, a process periodically determines if IP
addresses mentioned in availability rules are, in fact, available.
-Another example::
+Another example using :func:`pickclosest`::
www IN LUA A "pickclosest({'192.0.2.1','192.0.2.2','198.51.100.1'})"
the requester and the listed IP addresses. It will return with one of the closest
addresses.
-``pickclosest`` and ifportup can be combined as follows::
+:func:`pickclosest` and :func:`ifportup` can be combined as follows::
www IN LUA A ("ifportup(443, {'192.0.2.1', '192.0.2.2', '198.51.100.1'}"
", {selector='pickclosest'}) ")
LUA records can also contain more complex code, for example::
- www IN LUA A ";if countryCode('US') then return {'192.0.2.1','192.0.2.2','198.51.100.1'} else return '192.0.2.2' end"
+ www IN LUA A ";if country('US') then return {'192.0.2.1','192.0.2.2','198.51.100.1'} else return '192.0.2.2' end"
+
+As you can see you can return both single string value or array of strings.
+
+An example Lua record accessing ``qname``::
+
+ *.example.net 10 IN LUA TXT "; return 'Got a TXT query for ' .. qname:toString() .. '; First label is: ' .. qname:getRawLabels()[1]"
+
+``qtype`` cannot be accessed from a Lua script, the value is fixed per Lua record.
+See :doc:`functions` for available variables.
-As you can see you can return both single string value or array of strings.
Using LUA Records with Generic SQL backends
-------------------------------------------
"{stringmatch='Programming in Lua'}) " )
In this case, IP addresses are tested to see if they will serve
-https for 'www.lua.org', and if that page contains the string 'Programming
-in Lua'.
+https for 'www.lua.org', and if that page contains the string
+``Programming in Lua``.
Two sets of IP addresses are supplied. If an IP address from the first set
is available, it will be returned. If no addresses work in the first set,
Advanced topics
---------------
-By default, LUA records are executed with 'return ' prefixed to them. This saves
-a lot of typing for common cases. To run actual Lua scripts, start a record with a ';'
-which indicates no 'return ' should be prepended.
+By default, LUA records are executed with ``return `` prefixed to them. This saves
+a lot of typing for common cases. To run actual Lua scripts, start a record with a ``;``
+which indicates no ``return `` should be prepended.
To keep records more concise and readable, configuration can be stored in
separate records. The full example from above can also be written as::
LUA records are synthesized on query. They can also be transferred via AXFR
to other PowerDNS servers.
-LUA records themselves can not be queried however, as this would allow third parties to see load balancing internals
+LUA records themselves cannot be queried however, as this would allow third parties to see load balancing internals
they do not need to see.
A non-supporting DNS server will also serve a zone with LUA records, but
:param int domainId: The optional domain ID of the zone the record belongs to
:param int auth: ?
- .. todo complete LUA example bellow
+ .. todo complete LUA example below
.. code-block:: lua
name = newDN("www.example.org.")
--full-histogram <msec> Write out histogram with specified bin-size to 'full-histogram'
--log-histogram Write out a log-histogram of response times to 'log-histogram'
--no-servfail-stats Remove servfail responses from latency statistics
+--port The source and destination port to consider. Default is looking at packets from and to ports 53 and 5300.
--servfail-tree Figure out subtrees that generate servfails.
--stats-dir <directory> Drop statistics files in this directory. Defaults to ./
-l, --load-stats Emit per-second load statistics (questions, answers, outstanding).
domains:
- domain: example.com
master: 192.0.2.18:5301
+ max-soa-refresh: 1800
+ notify:
+ - 192.0.3.1
+ - 192.0.3.2:5301
- domain: example.net
master: 2001:DB8:ABCD::2
Mandatory.
:master: IP address of the server to transfer this domain from.
Mandatory.
+ :max-soa-refresh: Cap the refresh time to the given maximum (in seconds).
+ Optional.
+ :notify: The list of destinations to send NOTIFY to.
+ Optional.
:webserver-address:
IP address to listen on for the built-in webserver.
set-option *ZONE* [*producer*|*consumer*] [*coo*|*unique*|*group*] *VALUE* [*VALUE* ...]
Set or remove an option for *ZONE*. Providing an empty value removes an option.
set-catalog *ZONE* *CATALOG*
- Change the catalog of *ZONE* to *CATALOG*
+ Change the catalog of *ZONE* to *CATALOG*. Setting *CATALOG* to an empty "" removes *ZONE* from the catalog it is in.
set-account *ZONE* *ACCOUNT*
Change the account (owner) of *ZONE* to *ACCOUNT*.
add-meta *ZONE* *ATTRIBUTE* *VALUE* [*VALUE*]...
when using DoT, read the trusted CA certificates from *file*. Default is to use the system provided CA store.
tlsProvider *name*
when using DoT, use TLS provider *name*. Currently supported (if compiled in): `openssl` and `gnutls`. Default is `openssl` if available.
-xpf *XPFCODE* *XPFVERSION* *XPFPROTO* *XPFSRC* *XPFDST*
- Send an *XPF* additional with these parameters.
opcode *OPNUM*
Use opcode *OPNUM* instead of 0 (Query). For example, ``sdig 192.0.2.1 53 example.com SOA opcode 4`` sends a ``NOTIFY``.
List all options
--on-error-resume-next
Ignore missing zone files during parsing. Dangerous.
---slave
+--secondary
Maintain slave status of zones listed in named.conf as being slaves.
The default behaviour is to convert all zones to native operation.
--verbose
slave zones as slaves, and not convert them to native operation.
``zone2sql`` can generate SQL for nearly all the Generic SQL backends.
-See `its manpage <manpages/zone2sql.1>` for more information.
+See :doc:`its manpage <manpages/zone2sql.1>` for more information.
An example call to ``zone2sql`` could be:
ENT records. A 'pdnsutil rectify-zone' may be required.
PowerDNS itself is currently only able to retrieve updates via IXFR. It
-can not serve IXFR updates.
+cannot serve IXFR updates.
.. _supermaster-operation:
.. _autoprimary-operation:
packets waiting for database attention. During normal operations the
queue should be small.
+The value of :ref:`setting-queue-limit` should be set to only keep queries in
+queue for as long as someone would be interested in knowing the answer. Many
+resolvers will query other name servers for the zone quite aggressively.
+
Logging truly kills performance as answering a question from the cache
is an order of magnitude less work than logging a line about it. Busy
sites will prefer to turn :ref:`setting-log-dns-details` off.
.. _stat-open-tcp-connections:
open-tcp-connections
-~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^
Number of currently open TCP connections
.. _stat-overload-drops:
overload-drops
^^^^^^^^^^^^^^
-Number of questions dropped because backends overloaded
+Number of questions dropped because backends overloaded (backends are overloaded if they have more outstanding queries than the value of :ref:`setting-overload-queue-length`)
.. _stat-packetcache-hit:
qsize-q
^^^^^^^
-Number of packets waiting for database attention
+Number of packets waiting for database attention, only available if :ref:`setting-receiver-threads` > 1
.. _stat-query-cache-hit:
^^^^^^^^^^
Number of packets sent by clients requesting recursion (regardless of if we'll be providing them with recursion).
+.. _stat-receive-latency:
+
+receive-latency
+^^^^^^^^^^^^^^^
+Average number of microseconds needed to receive a query
+
.. _stat-recursing-answers:
recursing-answers
^^^^^^^^^^^^^^^^
Amount of packets that were dropped because they had to wait too long internally
+.. _stat-send-latency:
+
+send-latency
+^^^^^^^^^^^^
+Average number of microseconds needed to send the answer
+
.. _stat-udp-answers-bytes:
udp-answers-bytes
--- /dev/null
+# To generate requirements.txt, install pip-tools and run:
+# pip-compile --generate-hashes requirements.in
+
+Sphinx>=1.5.0,!=1.8.0,<2.0
+https://github.com/PowerDNS/sphinxcontrib-openapi/archive/refs/heads/use-jsondomain-pdns-py3.10-noscm.zip
+https://github.com/PowerDNS/sphinx-jsondomain/archive/refs/heads/no-type-links.zip
+changelog>=0.5.6,<0.6
+sphinxcontrib-fulltoc
+guzzle_sphinx_theme
+docutils!=0.15,<0.18
+jinja2<3.1.0
+pyyaml==6.0.1
-Sphinx>=1.5.0,!=1.8.0,<2.0
-git+https://github.com/PowerDNS/sphinxcontrib-openapi@use-jsondomain-pdns-py3.10
-git+https://github.com/PowerDNS/sphinx-jsondomain@no-type-links
-changelog>=0.5.6,<0.6
-sphinxcontrib-fulltoc
-guzzle_sphinx_theme
-docutils!=0.15,<0.18
-jinja2<3.1.0
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# pip-compile --generate-hashes requirements.in
+#
+alabaster==0.7.13 \
+ --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \
+ --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2
+ # via sphinx
+attrs==23.1.0 \
+ --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
+ --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
+ # via jsonschema
+babel==2.12.1 \
+ --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \
+ --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455
+ # via sphinx
+certifi==2023.7.22 \
+ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \
+ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9
+ # via requests
+changelog==0.5.8 \
+ --hash=sha256:43b21840874130666b7534b76b402bbb914f8c9c413d5ea9d45850ca4767dafb \
+ --hash=sha256:cd67a8a30e1a38731ebc25568788fe499748113d27324a7e67ad8ee443509415
+ # via -r requirements.in
+charset-normalizer==3.1.0 \
+ --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \
+ --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \
+ --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \
+ --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \
+ --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \
+ --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \
+ --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \
+ --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \
+ --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \
+ --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \
+ --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \
+ --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \
+ --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \
+ --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \
+ --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \
+ --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \
+ --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \
+ --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \
+ --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \
+ --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \
+ --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \
+ --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \
+ --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \
+ --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \
+ --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \
+ --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \
+ --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \
+ --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \
+ --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \
+ --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \
+ --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \
+ --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \
+ --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \
+ --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \
+ --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \
+ --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \
+ --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \
+ --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \
+ --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \
+ --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \
+ --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \
+ --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \
+ --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \
+ --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \
+ --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \
+ --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \
+ --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \
+ --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \
+ --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \
+ --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \
+ --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \
+ --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \
+ --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \
+ --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \
+ --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \
+ --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \
+ --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \
+ --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \
+ --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \
+ --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \
+ --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \
+ --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \
+ --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \
+ --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \
+ --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \
+ --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \
+ --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \
+ --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \
+ --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \
+ --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \
+ --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \
+ --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \
+ --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \
+ --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \
+ --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab
+ # via requests
+docutils==0.17.1 \
+ --hash=sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125 \
+ --hash=sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61
+ # via
+ # -r requirements.in
+ # sphinx
+fake-factory==0.5.11 \
+ --hash=sha256:24950d2cf028080f70830b79e8ceba8711cd2b9dccd99e3b6992dcf7da6e46cd \
+ --hash=sha256:cc450de0e0e9f3f4f89fea715b772b38bb5ad9d458e594fd7fed0f8b934ba636
+ # via sphinx-jsondomain
+guzzle-sphinx-theme==0.7.11 \
+ --hash=sha256:9b8c1639c343c02c3f3db7df660ddf6f533b5454ee92a5f7b02edaa573fed3e6
+ # via -r requirements.in
+idna==3.4 \
+ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
+ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
+ # via requests
+imagesize==1.4.1 \
+ --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \
+ --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a
+ # via sphinx
+jinja2==3.0.3 \
+ --hash=sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 \
+ --hash=sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7
+ # via
+ # -r requirements.in
+ # sphinx
+jsonschema==4.17.3 \
+ --hash=sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d \
+ --hash=sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6
+ # via sphinxcontrib-openapi
+markupsafe==2.1.3 \
+ --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
+ --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
+ --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
+ --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
+ --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
+ --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
+ --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
+ --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
+ --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
+ --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
+ --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
+ --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
+ --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
+ --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
+ --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
+ --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
+ --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
+ --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
+ --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
+ --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
+ --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
+ --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
+ --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
+ --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
+ --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
+ --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
+ --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
+ --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
+ --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
+ --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
+ --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
+ --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
+ --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
+ --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
+ --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
+ --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
+ --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
+ --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
+ --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
+ --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
+ --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
+ --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
+ --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
+ --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
+ --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
+ --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
+ --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
+ --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
+ --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
+ --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2
+ # via jinja2
+packaging==23.1 \
+ --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \
+ --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f
+ # via sphinx
+pygments==2.15.1 \
+ --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \
+ --hash=sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1
+ # via sphinx
+pyrsistent==0.19.3 \
+ --hash=sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8 \
+ --hash=sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440 \
+ --hash=sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a \
+ --hash=sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c \
+ --hash=sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3 \
+ --hash=sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393 \
+ --hash=sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9 \
+ --hash=sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da \
+ --hash=sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf \
+ --hash=sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64 \
+ --hash=sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a \
+ --hash=sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3 \
+ --hash=sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98 \
+ --hash=sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2 \
+ --hash=sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8 \
+ --hash=sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf \
+ --hash=sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc \
+ --hash=sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7 \
+ --hash=sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28 \
+ --hash=sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2 \
+ --hash=sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b \
+ --hash=sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a \
+ --hash=sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64 \
+ --hash=sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19 \
+ --hash=sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1 \
+ --hash=sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9 \
+ --hash=sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c
+ # via jsonschema
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via fake-factory
+pyyaml==6.0.1 \
+ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
+ --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
+ --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
+ --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
+ --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
+ --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
+ --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
+ --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
+ --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
+ --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
+ --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
+ --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
+ --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
+ --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
+ --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
+ --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
+ --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
+ --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
+ --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
+ --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
+ --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
+ --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
+ --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
+ --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
+ --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
+ --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
+ --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
+ --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
+ --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
+ --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
+ --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
+ --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
+ --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
+ --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
+ --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
+ --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
+ --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
+ --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
+ --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
+ --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
+ # via
+ # -r requirements.in
+ # sphinxcontrib-openapi
+requests==2.31.0 \
+ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
+ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
+ # via sphinx
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via
+ # fake-factory
+ # python-dateutil
+ # sphinx
+ # sphinxcontrib-httpdomain
+snowballstemmer==2.2.0 \
+ --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \
+ --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a
+ # via sphinx
+sphinx==1.8.6 \
+ --hash=sha256:5973adbb19a5de30e15ab394ec8bc05700317fa83f122c349dd01804d983720f \
+ --hash=sha256:e096b1b369dbb0fcb95a31ba8c9e1ae98c588e601f08eada032248e1696de4b1
+ # via
+ # -r requirements.in
+ # guzzle-sphinx-theme
+ # sphinx-jsondomain
+ # sphinxcontrib-httpdomain
+sphinx-jsondomain @ https://github.com/PowerDNS/sphinx-jsondomain/archive/refs/heads/no-type-links.zip \
+ --hash=sha256:0df65f5b93902e9e4f03935f39d91ae2a6a782116078f8af99ec1e370f999257
+ # via
+ # -r requirements.in
+ # sphinxcontrib-openapi
+sphinxcontrib-fulltoc==1.2.0 \
+ --hash=sha256:c845d62fc467f3135d4543e9f10e13ef91852683bd1c90fd19d07f9d36757cd9
+ # via -r requirements.in
+sphinxcontrib-httpdomain==1.8.1 \
+ --hash=sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5 \
+ --hash=sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b
+ # via sphinxcontrib-openapi
+sphinxcontrib-openapi @ https://github.com/PowerDNS/sphinxcontrib-openapi/archive/refs/heads/use-jsondomain-pdns-py3.10-noscm.zip \
+ --hash=sha256:ad6659a5e86e4899386d249f1eae18a459175a92da0dd851ce20f8b8ff6569b0
+ # via -r requirements.in
+sphinxcontrib-serializinghtml==1.1.5 \
+ --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \
+ --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952
+ # via sphinxcontrib-websupport
+sphinxcontrib-websupport==1.2.4 \
+ --hash=sha256:4edf0223a0685a7c485ae5a156b6f529ba1ee481a1417817935b20bde1956232 \
+ --hash=sha256:6fc9287dfc823fe9aa432463edd6cea47fa9ebbf488d7f289b322ffcfca075c7
+ # via sphinx
+urllib3==2.0.7 \
+ --hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \
+ --hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e
+ # via requests
+
+# WARNING: The following packages were not pinned, but pip requires them to be
+# pinned when the requirements file includes hashes and the requirement is not
+# satisfied by a package already installed. Consider using the --allow-unsafe flag.
+# setuptools
-@ 86400 IN SOA pdns-public-ns1.powerdns.com. peter\.van\.dijk.powerdns.com. 2023041700 10800 3600 604800 10800
+@ 86400 IN SOA pdns-public-ns1.powerdns.com. peter\.van\.dijk.powerdns.com. 2024031500 10800 3600 604800 10800
@ 3600 IN NS pdns-public-ns1.powerdns.com.
@ 3600 IN NS pdns-public-ns2.powerdns.com.
auth-4.1.11.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
auth-4.1.12.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
auth-4.1.13.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
-auth-4.1.14.security-status 60 IN TXT "2 Unsupported release (EOL)"
+auth-4.1.14.security-status 60 IN TXT "2 Unsupported release (EOL and known vulnerabilities)"
auth-4.2.0-alpha1.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
auth-4.2.0-beta1.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
auth-4.2.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
auth-4.7.2.security-status 60 IN TXT "1 OK"
auth-4.7.3.security-status 60 IN TXT "1 OK"
auth-4.7.4.security-status 60 IN TXT "1 OK"
-auth-4.8.0-alpha1.security-status 60 IN TXT "1 Unsupported pre-release"
+auth-4.8.0-alpha1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.8.0-beta1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.8.0.security-status 60 IN TXT "1 OK"
+auth-4.8.1.security-status 60 IN TXT "1 OK"
+auth-4.8.2.security-status 60 IN TXT "1 OK"
+auth-4.8.3.security-status 60 IN TXT "1 OK"
+auth-4.8.4.security-status 60 IN TXT "1 OK"
+auth-4.9.0-alpha1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.9.0-beta2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.9.0.security-status 60 IN TXT "1 OK"
; Auth Debian
auth-3.4.1-2.debian.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2015-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-05/"
recursor-4.1.15.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
recursor-4.1.16.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-04.html"
recursor-4.1.17.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
-recursor-4.1.18.security-status 60 IN TXT "3 Unsupported release (EOL)"
+recursor-4.1.18.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
recursor-4.2.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.2.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.2.2.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-04.html"
recursor-4.2.3.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
recursor-4.2.4.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
-recursor-4.2.5.security-status 60 IN TXT "3 Unsupported release (EOL)"
+recursor-4.2.5.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
recursor-4.3.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.3.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.4.5.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
recursor-4.4.6.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
recursor-4.4.7.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
-recursor-4.4.8.security-status 60 IN TXT "3 Unsupported release (EOL)"
+recursor-4.4.8.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
recursor-4.5.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.5.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.5.0-alpha3.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.5.7.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
recursor-4.5.8.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-02.html"
recursor-4.5.9.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-02.html"
-recursor-4.5.10.security-status 60 IN TXT "2 Unsupported release (EOL)"
-recursor-4.5.11.security-status 60 IN TXT "2 Unsupported release (EOL)"
-recursor-4.5.12.security-status 60 IN TXT "2 Unsupported release (EOL)"
+recursor-4.5.10.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
+recursor-4.5.11.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
+recursor-4.5.12.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
recursor-4.6.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.6.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.6.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.6.3.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
recursor-4.6.4.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
recursor-4.6.5.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
-recursor-4.6.6.security-status 60 IN TXT "1 OK"
+recursor-4.6.6.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
recursor-4.7.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.7.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.7.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.7.2.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
recursor-4.7.3.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
recursor-4.7.4.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
-recursor-4.7.5.security-status 60 IN TXT "1 OK"
+recursor-4.7.5.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
+recursor-4.7.6.security-status 60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
recursor-4.8.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.8.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.8.0-beta2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
recursor-4.8.1.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
recursor-4.8.2.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
recursor-4.8.3.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
-recursor-4.8.4.security-status 60 IN TXT "1 OK"
-recursor-4.9.0-alpha1.security-status 60 IN TXT "1 Unsupported pre-release"
+recursor-4.8.4.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.8.5.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.8.6.security-status 60 IN TXT "1 OK"
+recursor-4.8.7.security-status 60 IN TXT "1 OK"
+recursor-4.9.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.9.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.9.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.9.0.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.9.1.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.9.2.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.9.3.security-status 60 IN TXT "1 OK"
+recursor-4.9.4.security-status 60 IN TXT "1 OK"
+recursor-5.0.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0-rc2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.1.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-5.0.2.security-status 60 IN TXT "1 OK"
+recursor-5.0.3.security-status 60 IN TXT "1 OK"
; Recursor Debian
recursor-3.6.2-2.debian.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/"
; dnsdist
dnsdist-1.3.3.security-status 60 IN TXT "1 OK"
dnsdist-1.4.0-alpha1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-alpha2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-beta1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-rc1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-rc2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-rc3.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-rc4.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-rc5.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0.security-status 60 IN TXT "1 OK"
-dnsdist-1.5.0-alpha1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.5.0-rc1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.5.0-rc2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.5.0-rc3.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.5.0-rc4.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.5.0.security-status 60 IN TXT "1 OK"
-dnsdist-1.5.1.security-status 60 IN TXT "1 OK"
-dnsdist-1.5.2.security-status 60 IN TXT "1 OK"
-dnsdist-1.6.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.6.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.6.0-alpha3.security-status 60 IN TXT "3 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.6.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.6.0-rc2.security-status 60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.6.0.security-status 60 IN TXT "1 OK"
-dnsdist-1.6.1.security-status 60 IN TXT "1 OK"
-dnsdist-1.7.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0-beta2.security-status 60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0.security-status 60 IN TXT "1 OK"
-dnsdist-1.7.1.security-status 60 IN TXT "1 OK"
-dnsdist-1.7.2.security-status 60 IN TXT "1 OK"
-dnsdist-1.7.3.security-status 60 IN TXT "1 OK"
-dnsdist-1.7.4.security-status 60 IN TXT "1 OK"
-dnsdist-1.8.0-rc1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.8.0-rc2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.8.0-rc3.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.8.0.security-status 60 IN TXT "1 OK"
+dnsdist-1.4.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc3.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc4.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc5.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.5.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0-rc2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0-rc3.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0-rc4.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.5.1.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.5.2.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.6.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0-alpha3.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0-rc2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.6.1.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0-alpha2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0-beta1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0-beta2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.1.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.2.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.3.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.4.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.5.security-status 60 IN TXT "1 OK"
+dnsdist-1.8.0-rc1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.8.0-rc2.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.8.0-rc3.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.8.0.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.8.1.security-status 60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.8.2.security-status 60 IN TXT "1 OK"
+dnsdist-1.8.3.security-status 60 IN TXT "1 OK"
+dnsdist-1.9.0-alpha1.security-status 60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.9.0-alpha2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.9.0-alpha3.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.9.0-alpha4.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.9.0-rc1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.9.0.security-status 60 IN TXT "3 Upgrade now, see https://blog.powerdns.com/2024/03/14/powerdns-dnsdist-1.9.1-released"
+dnsdist-1.9.1.security-status 60 IN TXT "1 OK"
Allow AXFR NOTIFY from these IP ranges. Setting this to an empty string
will drop all incoming notifies.
+.. note::
+ IPs allowed by this setting, still go through the normal NOTIFY processing as described in :ref:`secondary-operation`
+ The IP the NOTIFY is received from, still needs to be a nameserver for the secondary domain. Explicitly setting this parameter will not bypass those checks.
+
.. _setting-allow-unsigned-autoprimary:
``allow-unsigned-autoprimary``
- 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.
.. note::
Pre 4.2.0 the default was always no.
+.. _setting-default-catalog-zone:
+
+``default-catalog-zone``
+------------------------
+
+- String:
+- Default: empty
+
+.. versionadded:: 4.8.3
+
+When a primary zone is created via the API, and the request does not specify a catalog zone, the name given here will be used.
+
.. _setting-default-ksk-algorithms:
.. _setting-default-ksk-algorithm:
The default keysize for the ZSK generated with :doc:`pdnsutil secure-zone <dnssec/pdnsutil>`.
Only relevant for algorithms with non-fixed keysizes (like RSA).
+.. _setting-delay-notifications:
+
+``delay-notifications``
+-----------------------
+
+- Integer
+- Default: 0 (no delay, send them directly)
+
+Configure a delay to send out notifications, no delay by default.
+
.. _setting-direct-dnskey:
``direct-dnskey``
- Boolean
- Default: no
-Do not log to syslog, only to stdout. Use this setting when running
+Do not log to syslog, only to stderr. Use this setting when running
inside a supervisor that handles logging (like systemd).
.. warning::
- Bool
- Default: yes
-When printing log lines to stdout, prefix them with timestamps.
+When printing log lines to stderr, prefix them with timestamps.
Disable this if the process supervisor timestamps these lines already.
.. note::
Each level includes itself plus the lower levels before it.
Not recommended to set this below 3.
+.. _setting-loglevel-show:
+
+``loglevel-show``
+-------------------
+
+- Bool
+- Default: no
+
+.. versionadded:: 4.9.0
+
+When enabled, log messages are formatted like structured logs, including their log level/priority: ``msg="Unable to launch, no backends configured for querying" prio="Error"``
+
.. _setting-lua-axfr-script:
``lua-axfr-script``
Script to be used to edit incoming AXFRs, see :ref:`modes-of-operation-axfrfilter`
+.. _setting-lua-consistent-hashes-cleanup-interval:
+
+``lua-consistent-hashes-cleanup-interval``
+------------------------------------------
+
+- Integer
+- Default: 3600
+
+.. versionadded:: 4.9.0
+
+Amount of time (in seconds) between subsequent cleanup routines for pre-computed hashes related to :func:`pickchashed()`.
+
+.. _setting-lua-consistent-hashes-expire-delay:
+
+``lua-consistent-hashes-expire-delay``
+--------------------------------------
+
+- Integer
+- Default: 86400
+
+.. versionadded:: 4.9.0
+
+Amount of time (in seconds) a pre-computed hash entry will be considered as expired when unused. See :func:`pickchashed()`.
+
.. _setting-lua-health-checks-expire-delay:
``lua-health-checks-expire-delay``
.. deprecated:: 4.5.0
Renamed to :ref:`setting-primary`.
-
+
- Boolean
- Default: no
``outgoing-axfr-expand-alias``
------------------------------
-- Boolean
+- One of ``no``, ``yes``, or ``ignore-errors``, String
- Default: no
+.. versionchanged:: 4.9.0
+ Option `ignore-errors` added.
+
If this is enabled, ALIAS records are expanded (synthesized to their
A/AAAA) during outgoing AXFR. This means slaves will not automatically
follow changes in those A/AAAA records unless you AXFR regularly!
during outgoing AXFR. Note that if your slaves do not support ALIAS,
they will return NODATA for A/AAAA queries for such names.
+If the ALIAS target cannot be resolved during AXFR the AXFR will fail.
+To allow outgoing AXFR also if the ALIAS targets are broken set this
+setting to `ignore-errors`.
+Be warned, this will lead to inconsistent zones between Primary and
+Secondary name servers.
+
.. _setting-overload-queue-length:
``overload-queue-length``
- Default: 0 (disabled)
If this many packets are waiting for database attention, answer any new
-questions strictly from the packet cache.
+questions strictly from the packet cache. Packets not in the cache will
+be dropped, and :ref:`_stat-overload-drops` will be incremented.
.. _setting-prevent-self-notification:
Enable for upgrading record content on secondaries, or when using the API (see :doc:`upgrade notes <../upgrading>`).
Disable after record contents have been upgraded.
-This option is supported by the bind and Generic SQL backends.
+This option is supported by the bind and Generic SQL backends.
.. note::
When using a generic SQL backend, records with an unknown record type (see :doc:`../appendices/types`) can be identified with the following SQL query::
-
+
SELECT * from records where type like 'TYPE%';
.. _setting-version-string:
----------------------
- String, one of "none", "normal", "detailed"
+- Default: normal
The amount of logging the webserver must do. "none" means no useful webserver information will be logged.
When set to "normal", the webserver will log a line per request that should be familiar::
[webserver] e235780e-a5cf-415e-9326-9d33383e739e Content-Length: 49
[webserver] e235780e-a5cf-415e-9326-9d33383e739e Content-Type: text/html; charset=utf-8
[webserver] e235780e-a5cf-415e-9326-9d33383e739e Server: PowerDNS/0.0.15896.0.gaba8bab3ab
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Full body:
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Full body:
[webserver] e235780e-a5cf-415e-9326-9d33383e739e <!html><title>Not Found</title><h1>Not Found</h1>
[webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 "GET /api/v1/servers/localhost/bla HTTP/1.1" 404 196
If a PID file should be written.
+.. _setting-workaround-11804:
+
+``workaround-11804``
+--------------------
+
+- Boolean
+- Default: no
+
+Workaround for `issue #11804 (outgoing AXFR may try to overfill a chunk and fail) <https://github.com/PowerDNS/pdns/issues/11804>`_.
+
+Default of no implies the pre-4.8 behaviour of up to 100 RRs per AXFR chunk.
+
+If enabled, only a single RR will be put into each AXFR chunk, making some zones transferable when they were not.
+
.. _setting-xfr-cycle-interval:
``xfr-cycle-interval``
See the `3.X <https://doc.powerdns.com/3/authoritative/upgrading/>`__
upgrade notes if your version is older than 3.4.2.
+4.9.0 to 5.0.0/master
+--------------
+
+ixfrdist IPv6 support
+^^^^^^^^^^^^^^^^^^^^^
+
+``ixfrdist`` now binds listening sockets with `IPV6_V6ONLY set`, which means that ``[::]`` no longer accepts IPv4 connections.
+If you want to listen on both IPv4 and IPv6, you need to add a line with ``0.0.0.0`` to the ``listen`` section of your ixfrdist configuration.
+
+4.8.0 to 4.9.0
+--------------
+
+Removed options
+^^^^^^^^^^^^^^^
+
+Various settings, deprecated since 4.5.0, have been removed.
+
+* :ref:`setting-allow-unsigned-supermaster` is now :ref:`setting-allow-unsigned-autoprimary`
+* :ref:`setting-master` is now :ref:`setting-primary`
+* :ref:`setting-slave-cycle-interval` is now :ref:`setting-xfr-cycle-interval`
+* :ref:`setting-slave-renotify` is now :ref:`setting-secondary-do-renotify`
+* :ref:`setting-slave` is now :ref:`setting-secondary`
+* :ref:`setting-superslave` is now :ref:`setting-autosecondary`
+
+In :ref:`setting-lmdb-sync-mode`, the previous default ``mapasync`` is no longer a valid value.
+Due to a bug, it was interpreted as ``sync`` in previous versions.
+To avoid operational surprises, ``sync`` is the new default value.
+
+Renamed options
+^^^^^^^^^^^^^^^
+
+Bind backend
+~~~~~~~~~~~~
+
+Various experimental autoprimary settings have been renamed.
+
+* ``supermaster-config`` is now ``autoprimary-config``
+* ``supermasters`` is now ``autoprimaries``
+* ``supermaster-destdir`` is now ``autoprimary-destdir``
+
+Gsql backends
+~~~~~~~~~~~~~
+
+Various custom queries have been renamed.
+
+* ``info-all-slaves-query`` is now ``info-all-secondaries-query``
+* ``supermaster-query`` is now ``autoprimary-query``
+* ``supermaster-name-to-ips`` is now ``autoprimary-name-to-ips``
+* ``supermaster-add`` is now ``autoprimary-add``
+* ``update-master-query`` is now ``update-primary-query``
+* ``info-all-master-query`` is now ``info-all-primary-query``
+
+Also, ``get-all-domains-query`` got an extra column for a zone's catalog assignment.
+
+API changes
+~~~~~~~~~~~
+
+A long time ago (in version 3.4.2), the ``priority`` field was removed from record content in the HTTP API.
+Starting with 4.9, API calls containing a ``priority`` field are actively rejected.
+This makes it easier for users to detect they are attempting to use a very old API client.
+
any version to 4.8.x
--------------------
+Use of (RSA-)SHA1 on Red Hat Enterprise Linux 9 and derivatives
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you are using PowerDNS Authoritative Server on EL9, please read `this ticket about Red Hat's SHA1 deprecation and how it affects PowerDNS software <https://github.com/PowerDNS/pdns/issues/12890>`__.
+
LMDB backend
^^^^^^^^^^^^
Upgrading is only supported from database schema versions 3 and 4, that is, databases created/upgraded by version 4.4 and up.
-4.6.0 to 4.7.0 or master
-------------------------
+In version 4.8.0, schema version 5 is finalised.
+Databases created with -alpha1 or -beta1 work with 4.8.0.
+
+4.6.0 to 4.7.0
+--------------
Schema changes
^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~
Various settings have been renamed.
-Their old names still work in 4.5.x, but will be removed in the release after it.
+Their old names still work in 4.5.x, but will be removed in a release after it.
* :ref:`setting-allow-unsigned-supermaster` is now :ref:`setting-allow-unsigned-autoprimary`
* :ref:`setting-master` is now :ref:`setting-primary`
SUBDIRS = \
+ arc4random \
ipcrypt \
json11 \
yahttp
DIST_SUBDIRS = \
+ arc4random \
ipcrypt \
json11 \
yahttp
--- /dev/null
+*.la
+*.lo
+*.o
+Makefile
+Makefile.in
--- /dev/null
+noinst_LTLIBRARIES = libarc4random.la
+libarc4random_la_SOURCES = \
+ arc4random.c \
+ arc4random.h \
+ arc4random.hh \
+ arc4random_uniform.c \
+ bsd-getentropy.c \
+ chacha_private.h \
+ explicit_bzero.c \
+ includes.h \
+ log.h
--- /dev/null
+/* $OpenBSD: arc4random.c,v 1.58 2022/07/31 13:41:45 tb Exp $ */
+
+/*
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ * Copyright (c) 2014, Theo de Raadt <deraadt@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * ChaCha based random number generator for OpenBSD.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/crypt/arc4random.c */
+
+#include "includes.h"
+
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#ifndef HAVE_ARC4RANDOM
+
+/*
+ * Always use the getentropy implementation from bsd-getentropy.c, which
+ * will call a native getentropy if available then fall back as required.
+ * We use a different name so that OpenSSL cannot call the wrong getentropy.
+ */
+#ifdef getentropy
+# undef getentropy
+#endif
+#define getentropy(x, y) (_ssh_compat_getentropy((x), (y)))
+
+#include "log.h"
+
+#define KEYSTREAM_ONLY
+#include "chacha_private.h"
+
+#define minimum(a, b) ((a) < (b) ? (a) : (b))
+
+#if defined(__GNUC__) || defined(_MSC_VER)
+#define inline __inline
+#else /* __GNUC__ || _MSC_VER */
+#define inline
+#endif /* !__GNUC__ && !_MSC_VER */
+
+#define KEYSZ 32
+#define IVSZ 8
+#define BLOCKSZ 64
+#define RSBUFSZ (16*BLOCKSZ)
+
+#define REKEY_BASE (1024*1024) /* NB. should be a power of 2 */
+
+/* Marked MAP_INHERIT_ZERO, so zero'd out in fork children. */
+static struct _rs {
+ size_t rs_have; /* valid bytes at end of rs_buf */
+ size_t rs_count; /* bytes till reseed */
+} *rs;
+
+/* Maybe be preserved in fork children, if _rs_allocate() decides. */
+static struct _rsx {
+ chacha_ctx rs_chacha; /* chacha context for random keystream */
+ u_char rs_buf[RSBUFSZ]; /* keystream blocks */
+} *rsx;
+
+static inline int _rs_allocate(struct _rs **, struct _rsx **);
+static inline void _rs_forkdetect(void);
+#include "arc4random.h"
+
+static inline void _rs_rekey(u_char *dat, size_t datlen);
+
+static inline void
+_rs_init(u_char *buf, size_t n)
+{
+ if (n < KEYSZ + IVSZ)
+ return;
+
+ if (rs == NULL) {
+ if (_rs_allocate(&rs, &rsx) == -1)
+ _exit(1);
+ }
+
+ chacha_keysetup(&rsx->rs_chacha, buf, KEYSZ * 8);
+ chacha_ivsetup(&rsx->rs_chacha, buf + KEYSZ);
+}
+
+static void
+_rs_stir(void)
+{
+ u_char rnd[KEYSZ + IVSZ];
+ uint32_t rekey_fuzz = 0;
+
+ if (getentropy(rnd, sizeof rnd) == -1)
+ _getentropy_fail();
+
+ if (!rs)
+ _rs_init(rnd, sizeof(rnd));
+ else
+ _rs_rekey(rnd, sizeof(rnd));
+ explicit_bzero(rnd, sizeof(rnd)); /* discard source seed */
+
+ /* invalidate rs_buf */
+ rs->rs_have = 0;
+ memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf));
+
+ /* rekey interval should not be predictable */
+ chacha_encrypt_bytes(&rsx->rs_chacha, (uint8_t *)&rekey_fuzz,
+ (uint8_t *)&rekey_fuzz, sizeof(rekey_fuzz));
+ rs->rs_count = REKEY_BASE + (rekey_fuzz % REKEY_BASE);
+}
+
+static inline void
+_rs_stir_if_needed(size_t len)
+{
+ _rs_forkdetect();
+ if (!rs || rs->rs_count <= len)
+ _rs_stir();
+ if (rs->rs_count <= len)
+ rs->rs_count = 0;
+ else
+ rs->rs_count -= len;
+}
+
+static inline void
+_rs_rekey(u_char *dat, size_t datlen)
+{
+#ifndef KEYSTREAM_ONLY
+ memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf));
+#endif
+ /* fill rs_buf with the keystream */
+ chacha_encrypt_bytes(&rsx->rs_chacha, rsx->rs_buf,
+ rsx->rs_buf, sizeof(rsx->rs_buf));
+ /* mix in optional user provided data */
+ if (dat) {
+ size_t i, m;
+
+ m = minimum(datlen, KEYSZ + IVSZ);
+ for (i = 0; i < m; i++)
+ rsx->rs_buf[i] ^= dat[i];
+ }
+ /* immediately reinit for backtracking resistance */
+ _rs_init(rsx->rs_buf, KEYSZ + IVSZ);
+ memset(rsx->rs_buf, 0, KEYSZ + IVSZ);
+ rs->rs_have = sizeof(rsx->rs_buf) - KEYSZ - IVSZ;
+}
+
+static inline void
+_rs_random_buf(void *_buf, size_t n)
+{
+ u_char *buf = (u_char *)_buf;
+ u_char *keystream;
+ size_t m;
+
+ _rs_stir_if_needed(n);
+ while (n > 0) {
+ if (rs->rs_have > 0) {
+ m = minimum(n, rs->rs_have);
+ keystream = rsx->rs_buf + sizeof(rsx->rs_buf)
+ - rs->rs_have;
+ memcpy(buf, keystream, m);
+ memset(keystream, 0, m);
+ buf += m;
+ n -= m;
+ rs->rs_have -= m;
+ }
+ if (rs->rs_have == 0)
+ _rs_rekey(NULL, 0);
+ }
+}
+
+static inline void
+_rs_random_u32(uint32_t *val)
+{
+ u_char *keystream;
+
+ _rs_stir_if_needed(sizeof(*val));
+ if (rs->rs_have < sizeof(*val))
+ _rs_rekey(NULL, 0);
+ keystream = rsx->rs_buf + sizeof(rsx->rs_buf) - rs->rs_have;
+ memcpy(val, keystream, sizeof(*val));
+ memset(keystream, 0, sizeof(*val));
+ rs->rs_have -= sizeof(*val);
+}
+
+#include <pthread.h>
+static pthread_mutex_t arc4mutex = PTHREAD_MUTEX_INITIALIZER;
+
+uint32_t
+arc4random(void)
+{
+ uint32_t val;
+
+ _ARC4_LOCK();
+ _rs_random_u32(&val);
+ _ARC4_UNLOCK();
+ return val;
+}
+DEF_WEAK(arc4random);
+
+/*
+ * If we are providing arc4random, then we can provide a more efficient
+ * arc4random_buf().
+ */
+# ifndef HAVE_ARC4RANDOM_BUF
+void
+arc4random_buf(void *buf, size_t n)
+{
+ _ARC4_LOCK();
+ _rs_random_buf(buf, n);
+ _ARC4_UNLOCK();
+}
+DEF_WEAK(arc4random_buf);
+# endif /* !HAVE_ARC4RANDOM_BUF */
+#endif /* !HAVE_ARC4RANDOM */
+
+/* arc4random_buf() that uses platform arc4random() */
+#if !defined(HAVE_ARC4RANDOM_BUF) && defined(HAVE_ARC4RANDOM)
+void
+arc4random_buf(void *_buf, size_t n)
+{
+ size_t i;
+ u_int32_t r = 0;
+ char *buf = (char *)_buf;
+
+ for (i = 0; i < n; i++) {
+ if (i % 4 == 0)
+ r = arc4random();
+ buf[i] = r & 0xff;
+ r >>= 8;
+ }
+ explicit_bzero(&r, sizeof(r));
+}
+#endif /* !defined(HAVE_ARC4RANDOM_BUF) && defined(HAVE_ARC4RANDOM) */
+
--- /dev/null
+/* $OpenBSD: arc4random_linux.h,v 1.12 2019/07/11 10:37:28 inoguchi Exp $ */
+
+/*
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ * Copyright (c) 2014, Theo de Raadt <deraadt@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Stub functions for portability. From LibreSSL with some adaptations.
+ */
+
+#include <sys/mman.h>
+
+#include <signal.h>
+
+/* OpenSSH isn't multithreaded */
+#define _ARC4_LOCK() pthread_mutex_lock(&arc4mutex);
+#define _ARC4_UNLOCK() pthread_mutex_unlock(&arc4mutex);
+#define _ARC4_ATFORK(f)
+
+static inline void
+_getentropy_fail(void)
+{
+ fatal("getentropy failed");
+}
+
+static volatile sig_atomic_t _rs_forked;
+
+static inline void
+_rs_forkhandler(void)
+{
+ _rs_forked = 1;
+}
+
+static inline void
+_rs_forkdetect(void)
+{
+ static pid_t _rs_pid = 0;
+ pid_t pid = getpid();
+
+ if (_rs_pid == 0 || _rs_pid == 1 || _rs_pid != pid || _rs_forked) {
+ _rs_pid = pid;
+ _rs_forked = 0;
+ if (rs)
+ memset(rs, 0, sizeof(*rs));
+ }
+}
+
+static inline int
+_rs_allocate(struct _rs **rsp, struct _rsx **rsxp)
+{
+#if defined(MAP_ANON) && defined(MAP_PRIVATE)
+ if ((*rsp = mmap(NULL, sizeof(**rsp), PROT_READ|PROT_WRITE,
+ MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED)
+ return (-1);
+
+ if ((*rsxp = mmap(NULL, sizeof(**rsxp), PROT_READ|PROT_WRITE,
+ MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {
+ munmap(*rsp, sizeof(**rsp));
+ *rsp = NULL;
+ return (-1);
+ }
+#else
+ if ((*rsp = calloc(1, sizeof(**rsp))) == NULL)
+ return (-1);
+ if ((*rsxp = calloc(1, sizeof(**rsxp))) == NULL) {
+ free(*rsp);
+ *rsp = NULL;
+ return (-1);
+ }
+#endif
+
+ _ARC4_ATFORK(_rs_forkhandler);
+ return (0);
+}
--- /dev/null
+#pragma once
+#include <cstddef>
+#include <cinttypes>
+
+#include "config.h"
+
+extern "C"
+{
+#ifndef HAVE_ARC4RANDOM
+ uint32_t arc4random(void);
+#endif
+#ifndef HAVE_ARC4RANDOM_BUF
+ void arc4random_buf(void* buf, size_t nbytes);
+#endif
+#ifndef HAVE_ARC4RANDOM_UNIFORM
+ uint32_t arc4random_uniform(uint32_t upper_bound);
+#endif
+#ifndef HAVE_EXPLICIT_BZERO
+ void explicit_bzero(void*, size_t len);
+#endif
+}
--- /dev/null
+/* $OpenBSD: arc4random_uniform.c,v 1.3 2019/01/20 02:59:07 bcook Exp $ */
+
+/*
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/crypto/arc4random_uniform.c */
+
+#include "includes.h"
+
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+
+#ifndef HAVE_ARC4RANDOM_UNIFORM
+/*
+ * Calculate a uniformly distributed random number less than upper_bound
+ * avoiding "modulo bias".
+ *
+ * Uniformity is achieved by generating new random numbers until the one
+ * returned is outside the range [0, 2**32 % upper_bound). This
+ * guarantees the selected random number will be inside
+ * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
+ * after reduction modulo upper_bound.
+ */
+uint32_t
+arc4random_uniform(uint32_t upper_bound)
+{
+ uint32_t r, min;
+
+ if (upper_bound < 2)
+ return 0;
+
+ /* 2**32 % x == (2**32 - x) % x */
+ min = -upper_bound % upper_bound;
+
+ /*
+ * This could theoretically loop forever but each retry has
+ * p > 0.5 (worst case, usually far better) of selecting a
+ * number inside the range we need, so it should rarely need
+ * to re-roll.
+ */
+ for (;;) {
+ r = arc4random();
+ if (r >= min)
+ break;
+ }
+
+ return r % upper_bound;
+}
+#endif /* !HAVE_ARC4RANDOM_UNIFORM */
--- /dev/null
+/*
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#ifndef SSH_RANDOM_DEV
+# define SSH_RANDOM_DEV "/dev/urandom"
+#endif /* SSH_RANDOM_DEV */
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_RANDOM_H
+# include <sys/random.h>
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef WITH_OPENSSL
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#endif
+
+#include "log.h"
+
+int
+_ssh_compat_getentropy(void *s, size_t len)
+{
+#ifdef WITH_OPENSSL
+ if (RAND_bytes(s, len) <= 0)
+ fatal("Couldn't obtain random bytes (error 0x%lx)",
+ (unsigned long)ERR_get_error());
+#else
+ int fd, save_errno;
+ ssize_t r;
+ size_t o = 0;
+
+#ifdef HAVE_GETENTROPY
+ if ((r = getentropy(s, len)) == 0)
+ return 0;
+#endif /* HAVE_GETENTROPY */
+#ifdef HAVE_GETRANDOM
+ if ((r = getrandom(s, len, 0)) > 0 && (size_t)r == len)
+ return 0;
+#endif /* HAVE_GETRANDOM */
+
+ if ((fd = open(SSH_RANDOM_DEV, O_RDONLY)) == -1) {
+ save_errno = errno;
+ /* Try egd/prngd before giving up. */
+ if (seed_from_prngd(s, len) == 0)
+ return 0;
+ fatal("Couldn't open %s: %s", SSH_RANDOM_DEV,
+ strerror(save_errno));
+ }
+ while (o < len) {
+ r = read(fd, (u_char *)s + o, len - o);
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR ||
+ errno == EWOULDBLOCK)
+ continue;
+ fatal("read %s: %s", SSH_RANDOM_DEV, strerror(errno));
+ }
+ o += r;
+ }
+ close(fd);
+#endif /* WITH_OPENSSL */
+ return 0;
+}
--- /dev/null
+/* OPENBSD ORIGINAL: lib/libc/crypt/chacha_private.h */
+
+/*
+chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+*/
+
+/* $OpenBSD: chacha_private.h,v 1.3 2022/02/28 21:56:29 dtucker Exp $ */
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+typedef struct
+{
+ u32 input[16]; /* could be compressed */
+} chacha_ctx;
+
+#define U8C(v) (v##U)
+#define U32C(v) (v##U)
+
+#define U8V(v) ((u8)(v) & U8C(0xFF))
+#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
+
+#define ROTL32(v, n) \
+ (U32V((v) << (n)) | ((v) >> (32 - (n))))
+
+#define U8TO32_LITTLE(p) \
+ (((u32)((p)[0]) ) | \
+ ((u32)((p)[1]) << 8) | \
+ ((u32)((p)[2]) << 16) | \
+ ((u32)((p)[3]) << 24))
+
+#define U32TO8_LITTLE(p, v) \
+ do { \
+ (p)[0] = U8V((v) ); \
+ (p)[1] = U8V((v) >> 8); \
+ (p)[2] = U8V((v) >> 16); \
+ (p)[3] = U8V((v) >> 24); \
+ } while (0)
+
+#define ROTATE(v,c) (ROTL32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+ a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+ c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+ a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+ c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[16] = "expand 32-byte k";
+static const char tau[16] = "expand 16-byte k";
+
+static void
+chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits)
+{
+ const char *constants;
+
+ x->input[4] = U8TO32_LITTLE(k + 0);
+ x->input[5] = U8TO32_LITTLE(k + 4);
+ x->input[6] = U8TO32_LITTLE(k + 8);
+ x->input[7] = U8TO32_LITTLE(k + 12);
+ if (kbits == 256) { /* recommended */
+ k += 16;
+ constants = sigma;
+ } else { /* kbits == 128 */
+ constants = tau;
+ }
+ x->input[8] = U8TO32_LITTLE(k + 0);
+ x->input[9] = U8TO32_LITTLE(k + 4);
+ x->input[10] = U8TO32_LITTLE(k + 8);
+ x->input[11] = U8TO32_LITTLE(k + 12);
+ x->input[0] = U8TO32_LITTLE(constants + 0);
+ x->input[1] = U8TO32_LITTLE(constants + 4);
+ x->input[2] = U8TO32_LITTLE(constants + 8);
+ x->input[3] = U8TO32_LITTLE(constants + 12);
+}
+
+static void
+chacha_ivsetup(chacha_ctx *x,const u8 *iv)
+{
+ x->input[12] = 0;
+ x->input[13] = 0;
+ x->input[14] = U8TO32_LITTLE(iv + 0);
+ x->input[15] = U8TO32_LITTLE(iv + 4);
+}
+
+static void
+chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
+{
+ u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+ u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+ u8 *ctarget = NULL;
+ u8 tmp[64];
+ u_int i;
+
+ if (!bytes) return;
+
+ j0 = x->input[0];
+ j1 = x->input[1];
+ j2 = x->input[2];
+ j3 = x->input[3];
+ j4 = x->input[4];
+ j5 = x->input[5];
+ j6 = x->input[6];
+ j7 = x->input[7];
+ j8 = x->input[8];
+ j9 = x->input[9];
+ j10 = x->input[10];
+ j11 = x->input[11];
+ j12 = x->input[12];
+ j13 = x->input[13];
+ j14 = x->input[14];
+ j15 = x->input[15];
+
+ for (;;) {
+ if (bytes < 64) {
+ for (i = 0;i < bytes;++i) tmp[i] = m[i];
+ m = tmp;
+ ctarget = c;
+ c = tmp;
+ }
+ x0 = j0;
+ x1 = j1;
+ x2 = j2;
+ x3 = j3;
+ x4 = j4;
+ x5 = j5;
+ x6 = j6;
+ x7 = j7;
+ x8 = j8;
+ x9 = j9;
+ x10 = j10;
+ x11 = j11;
+ x12 = j12;
+ x13 = j13;
+ x14 = j14;
+ x15 = j15;
+ for (i = 20;i > 0;i -= 2) {
+ QUARTERROUND( x0, x4, x8,x12)
+ QUARTERROUND( x1, x5, x9,x13)
+ QUARTERROUND( x2, x6,x10,x14)
+ QUARTERROUND( x3, x7,x11,x15)
+ QUARTERROUND( x0, x5,x10,x15)
+ QUARTERROUND( x1, x6,x11,x12)
+ QUARTERROUND( x2, x7, x8,x13)
+ QUARTERROUND( x3, x4, x9,x14)
+ }
+ x0 = PLUS(x0,j0);
+ x1 = PLUS(x1,j1);
+ x2 = PLUS(x2,j2);
+ x3 = PLUS(x3,j3);
+ x4 = PLUS(x4,j4);
+ x5 = PLUS(x5,j5);
+ x6 = PLUS(x6,j6);
+ x7 = PLUS(x7,j7);
+ x8 = PLUS(x8,j8);
+ x9 = PLUS(x9,j9);
+ x10 = PLUS(x10,j10);
+ x11 = PLUS(x11,j11);
+ x12 = PLUS(x12,j12);
+ x13 = PLUS(x13,j13);
+ x14 = PLUS(x14,j14);
+ x15 = PLUS(x15,j15);
+
+#ifndef KEYSTREAM_ONLY
+ x0 = XOR(x0,U8TO32_LITTLE(m + 0));
+ x1 = XOR(x1,U8TO32_LITTLE(m + 4));
+ x2 = XOR(x2,U8TO32_LITTLE(m + 8));
+ x3 = XOR(x3,U8TO32_LITTLE(m + 12));
+ x4 = XOR(x4,U8TO32_LITTLE(m + 16));
+ x5 = XOR(x5,U8TO32_LITTLE(m + 20));
+ x6 = XOR(x6,U8TO32_LITTLE(m + 24));
+ x7 = XOR(x7,U8TO32_LITTLE(m + 28));
+ x8 = XOR(x8,U8TO32_LITTLE(m + 32));
+ x9 = XOR(x9,U8TO32_LITTLE(m + 36));
+ x10 = XOR(x10,U8TO32_LITTLE(m + 40));
+ x11 = XOR(x11,U8TO32_LITTLE(m + 44));
+ x12 = XOR(x12,U8TO32_LITTLE(m + 48));
+ x13 = XOR(x13,U8TO32_LITTLE(m + 52));
+ x14 = XOR(x14,U8TO32_LITTLE(m + 56));
+ x15 = XOR(x15,U8TO32_LITTLE(m + 60));
+#endif
+
+ j12 = PLUSONE(j12);
+ if (!j12) {
+ j13 = PLUSONE(j13);
+ /* stopping at 2^70 bytes per nonce is user's responsibility */
+ }
+
+ U32TO8_LITTLE(c + 0,x0);
+ U32TO8_LITTLE(c + 4,x1);
+ U32TO8_LITTLE(c + 8,x2);
+ U32TO8_LITTLE(c + 12,x3);
+ U32TO8_LITTLE(c + 16,x4);
+ U32TO8_LITTLE(c + 20,x5);
+ U32TO8_LITTLE(c + 24,x6);
+ U32TO8_LITTLE(c + 28,x7);
+ U32TO8_LITTLE(c + 32,x8);
+ U32TO8_LITTLE(c + 36,x9);
+ U32TO8_LITTLE(c + 40,x10);
+ U32TO8_LITTLE(c + 44,x11);
+ U32TO8_LITTLE(c + 48,x12);
+ U32TO8_LITTLE(c + 52,x13);
+ U32TO8_LITTLE(c + 56,x14);
+ U32TO8_LITTLE(c + 60,x15);
+
+ if (bytes <= 64) {
+ if (bytes < 64) {
+ for (i = 0;i < bytes;++i) ctarget[i] = c[i];
+ }
+ x->input[12] = j12;
+ x->input[13] = j13;
+ return;
+ }
+ bytes -= 64;
+ c += 64;
+#ifndef KEYSTREAM_ONLY
+ m += 64;
+#endif
+ }
+}
--- /dev/null
+/* OPENBSD ORIGINAL: lib/libc/string/explicit_bzero.c */
+/* $OpenBSD: explicit_bzero.c,v 1.1 2014/01/22 21:06:45 tedu Exp $ */
+/*
+ * Public domain.
+ * Written by Ted Unangst
+ */
+
+#include "includes.h"
+
+#include <string.h>
+
+/*
+ * explicit_bzero - don't let the compiler optimize away bzero
+ */
+
+#ifndef HAVE_EXPLICIT_BZERO
+
+#ifdef HAVE_EXPLICIT_MEMSET
+
+void
+explicit_bzero(void *p, size_t n)
+{
+ (void)explicit_memset(p, 0, n);
+}
+
+#elif defined(HAVE_MEMSET_S)
+
+void
+explicit_bzero(void *p, size_t n)
+{
+ if (n == 0)
+ return;
+ (void)memset_s(p, n, 0, n);
+}
+
+#else /* HAVE_MEMSET_S */
+
+/*
+ * Indirect bzero through a volatile pointer to hopefully avoid
+ * dead-store optimisation eliminating the call.
+ */
+static void (* volatile ssh_bzero)(void *, size_t) = bzero;
+
+void
+explicit_bzero(void *p, size_t n)
+{
+ if (n == 0)
+ return;
+ /*
+ * clang -fsanitize=memory needs to intercept memset-like functions
+ * to correctly detect memory initialisation. Make sure one is called
+ * directly since our indirection trick above successfully confuses it.
+ */
+#if defined(__has_feature)
+# if __has_feature(memory_sanitizer)
+ memset(p, 0, n);
+# endif
+#endif
+
+ ssh_bzero(p, n);
+}
+
+#endif /* HAVE_MEMSET_S */
+
+#endif /* HAVE_EXPLICIT_BZERO */
--- /dev/null
+#pragma once
+
+#include "config.h"
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#define seed_from_prngd(a, b) -1
+
+#ifndef HAVE_ARC4RANDOM
+uint32_t arc4random(void);
+#endif
+#ifndef HAVE_ARC4RANDOM_BUF
+void arc4random_buf(void *buf, size_t nbytes);
+#endif
+#ifndef HAVE_ARC4RANDOM_UNIFORM
+uint32_t arc4random_uniform(uint32_t upper_bound);
+#endif
+#ifndef HAVE_EXPLICIT_BZERO
+void explicit_bzero(void *, size_t len);
+#endif
+
+int _ssh_compat_getentropy(void *, size_t);
+
+#define DEF_WEAK(x)
--- /dev/null
+#define fatal(...) do { fprintf(stderr, __VA_ARGS__); abort(); } while (0)
out += "\\r";
} else if (ch == '\t') {
out += "\\t";
- } else if (static_cast<uint8_t>(ch) <= 0x1f || static_cast<uint8_t>(ch) >= 0x7f) {
+ } else if (static_cast<uint8_t>(ch) <= 0x1f) {
char buf[8];
snprintf(buf, sizeof buf, "\\u%04x", ch);
out += buf;
+ } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+ && static_cast<uint8_t>(value[i+2]) == 0xa8) {
+ out += "\\u2028";
+ i += 2;
+ } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+ && static_cast<uint8_t>(value[i+2]) == 0xa9) {
+ out += "\\u2029";
+ i += 2;
} else {
out += ch;
}
struct bpf_insn;
-int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
- int max_entries, int map_flags);
-int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags);
-int bpf_lookup_elem(int fd, void *key, void *value);
-int bpf_delete_elem(int fd, void *key);
-int bpf_get_next_key(int fd, void *key, void *next_key);
-
-int bpf_prog_load(enum bpf_prog_type prog_type,
- const struct bpf_insn *insns, int insn_len,
- const char *license, int kern_version);
-
-int bpf_obj_pin(int fd, const char *pathname);
-int bpf_obj_get(const char *pathname);
#define LOG_BUF_SIZE 65536
extern char bpf_log_buf[LOG_BUF_SIZE];
return (lsh->d_flags & LS_FLAG_DELETED) != 0;
}
+ uint64_t LSgetTimestamp(std::string_view val) {
+ const LSheader* lsh = LSassertFixedHeaderSize(val);
+
+ return lsh->getTimestamp();
+ }
bool s_flag_deleted{false};
}
#endif /* #ifndef DNSDIST */
-MDBDbi::MDBDbi(MDB_env* env, MDB_txn* txn, const string_view dbname, int flags)
+MDBDbi::MDBDbi(MDB_env* /* env */, MDB_txn* txn, const string_view dbname, int flags) : d_dbi(-1)
{
// A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function.
return std::string((char*)this, sizeof(*this)) + std::string(ntohs(d_numextra)*8, '\0');
}
-
+ uint64_t getTimestamp() const {
+ return _LMDB_SAFE_BSWAP64MAYBE(d_timestamp);
+ }
};
static_assert(sizeof(LSheader)==24, "LSheader size is wrong");
size_t LScheckHeaderAndGetSize(std::string_view val, size_t datasize=0);
size_t LScheckHeaderAndGetSize(const MDBOutVal *val, size_t datasize=0);
bool LSisDeleted(std::string_view val);
+ uint64_t LSgetTimestamp(std::string_view val);
extern bool s_flag_deleted;
}
namespace {
- MDBOutVal getKeyFromCombinedKey(MDBInVal combined) {
+ inline MDBOutVal getKeyFromCombinedKey(MDBInVal combined) {
if (combined.d_mdbval.mv_size < sizeof(uint32_t)) {
throw std::runtime_error("combined key too short to get ID from");
}
return ret;
}
- MDBOutVal getIDFromCombinedKey(MDBInVal combined) {
+ inline MDBOutVal getIDFromCombinedKey(MDBInVal combined) {
if (combined.d_mdbval.mv_size < sizeof(uint32_t)) {
throw std::runtime_error("combined key too short to get ID from");
}
return ret;
}
- std::string makeCombinedKey(MDBInVal key, MDBInVal val)
+ inline std::string makeCombinedKey(MDBInVal key, MDBInVal val)
{
std::string lenprefix(sizeof(uint16_t), '\0');
std::string skey((char*) key.d_mdbval.mv_data, key.d_mdbval.mv_size);
auto scombined = makeCombinedKey(keyConv(d_parent->getMember(t)), id);
MDBInVal combined(scombined);
+ // if the entry existed already, this will just update the timestamp/txid in the LS header. This is intentional, so objects and their indexes always get synced together.
txn->put(d_idx, combined, empty, flags);
}
{
public:
TypedDBI(std::shared_ptr<MDBEnv> env, string_view name)
- : d_env(env), d_name(name)
+ : d_env(std::move(env)), d_name(name)
{
d_main = d_env->openDB(name, MDB_CREATE);
// auto range = prefix_range<N>(key);
LMDBIDvec ids;
- get_multi<N>(key, ids);
+ // because we know we only want one item, pass onlyOldest=true to consistently get the same one out of a set of duplicates
+ get_multi<N>(key, ids, true);
if (ids.size() == 0) {
return 0;
};
template<int N>
- void get_multi(const typename std::tuple_element<N, tuple_t>::type::type& key, LMDBIDvec& ids)
+ void get_multi(const typename std::tuple_element<N, tuple_t>::type::type& key, LMDBIDvec& ids, bool onlyOldest=false)
{
// std::cerr<<"in get_multi"<<std::endl;
typename Parent::cursor_t cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
int rc = cursor.get(out, id, MDB_SET_RANGE);
+ uint64_t oldestts = UINT64_MAX;
+ uint32_t oldestid = 0;
+
while (rc == 0) {
auto sout = out.getNoStripHeader<std::string>(); // FIXME: this (and many others) could probably be string_view
auto thiskey = getKeyFromCombinedKey(out);
if (sthiskey == keyString) {
auto _id = getIDFromCombinedKey(out);
- ids.push_back(_id.getNoStripHeader<uint32_t>());
+ uint64_t ts = LMDBLS::LSgetTimestamp(id.getNoStripHeader<string_view>());
+ uint32_t __id = _id.getNoStripHeader<uint32_t>();
+
+ if (onlyOldest) {
+ if (ts < oldestts) {
+ oldestts = ts;
+ oldestid = __id;
+
+ ids.clear();
+ ids.push_back(oldestid);
+ }
+ } else {
+ ids.push_back(__id);
+ }
}
rc = cursor.get(out, id, MDB_NEXT);
// creating the object
// lua_newuserdata allocates memory in the internals of the lua library and returns it so we can fill it
// and that's what we do with placement-new
+ static_assert(alignof(TType) <= 8);
const auto pointerLocation = static_cast<TType*>(lua_newuserdata(state, sizeof(TType)));
new (pointerLocation) TType(std::forward<TType2>(value));
}
// creating the object
// lua_newuserdata allocates memory in the internals of the lua library and returns it so we can fill it
// and that's what we do with placement-new
+ // static_assert(alignof(TFunctionObject) <= 8); XXX trips on at least c++lib 17, see #13766
const auto functionLocation = static_cast<TFunctionObject*>(lua_newuserdata(state, sizeof(TFunctionObject)));
new (functionLocation) TFunctionObject(std::move(fn));
};
// we copy the function object onto the stack
+ static_assert(alignof(TFunctionObject) <= 8);
const auto functionObjectLocation = static_cast<TFunctionObject*>(lua_newuserdata(state, sizeof(TFunctionObject)));
new (functionObjectLocation) TFunctionObject(std::move(fn));
if (s.find("=") != std::string::npos)
keyValuePair(s, k, v);
else
- k = s;
+ k = std::move(s);
if (k == "expires") {
DateTime dt;
dt.parseCookie(v);
#include "yahttp.hpp"
+#include <limits>
+
namespace YaHTTP {
template class AsyncLoader<Request>;
if (target->headers.find(key) != target->headers.end()) {
target->headers[key] = target->headers[key] + ";" + value;
} else {
- target->headers[key] = value;
+ target->headers[key] = std::move(value);
}
}
}
throw ParseError("Unable to parse chunk size");
}
if (chunk_size == 0) { state = 3; break; } // last chunk
+ if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2)) {
+ throw ParseError("Chunk is too large");
+ }
} else {
int crlf=1;
if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline
#include "router.hpp"
namespace YaHTTP {
- typedef funcptr::tuple<int,int> TDelim;
-
// router is defined here.
YaHTTP::Router Router::router;
routes.push_back(funcptr::make_tuple(method2, url, handler, name));
};
- bool Router::route(Request *req, THandlerFunction& handler) {
- std::map<std::string, TDelim> params;
- int pos1,pos2;
- bool matched = false;
- std::string rname;
-
- // iterate routes
- for(TRouteList::iterator i = routes.begin(); !matched && i != routes.end(); i++) {
- int k1,k2,k3;
- std::string pname;
- std::string method, url;
- funcptr::tie(method, url, handler, rname) = *i;
-
- if (method.empty() == false && req->method != method) continue; // no match on method
- // see if we can't match the url
- params.clear();
- // simple matcher func
- for(k1=0, k2=0; k1 < static_cast<int>(url.size()) && k2 < static_cast<int>(req->url.path.size()); ) {
- if (url[k1] == '<') {
- pos1 = k2;
- k3 = k1+1;
+ bool Router::match(const std::string& route, const URL& requrl, std::map<std::string, TDelim> ¶ms) {
+ size_t rpos = 0;
+ size_t upos = 0;
+ size_t npos = 0;
+ size_t nstart = 0;
+ size_t nend = 0;
+ std::string pname;
+ for(; rpos < route.size() && upos < requrl.path.size(); ) {
+ if (route[rpos] == '<') {
+ nstart = upos;
+ npos = rpos+1;
// start of parameter
- while(k1 < static_cast<int>(url.size()) && url[k1] != '>') k1++;
- pname = std::string(url.begin()+k3, url.begin()+k1);
+ while(rpos < route.size() && route[rpos] != '>') {
+ rpos++;
+ }
+ pname = std::string(route.begin()+static_cast<long>(npos), route.begin()+static_cast<long>(rpos));
// then we also look it on the url
- if (pname[0]=='*') {
+ if (pname[0] == '*') {
pname = pname.substr(1);
// this matches whatever comes after it, basically end of string
- pos2 = req->url.path.size();
- if (pname != "")
- params[pname] = funcptr::tie(pos1,pos2);
- k1 = url.size();
- k2 = req->url.path.size();
+ nend = requrl.path.size();
+ if (!pname.empty()) {
+ params[pname] = funcptr::tie(nstart,nend);
+ }
+ rpos = route.size();
+ upos = requrl.path.size();
break;
} else {
- // match until url[k1]
- while(k2 < static_cast<int>(req->url.path.size()) && req->url.path[k2] != url[k1+1]) k2++;
- pos2 = k2;
- params[pname] = funcptr::tie(pos1,pos2);
+ // match until url[upos] or next / if pattern is at end
+ while (upos < requrl.path.size()) {
+ if (route[rpos+1] == '\0' && requrl.path[upos] == '/') {
+ break;
+ }
+ if (requrl.path[upos] == route[rpos+1]) {
+ break;
+ }
+ upos++;
+ }
+ nend = upos;
+ params[pname] = funcptr::tie(nstart, nend);
}
- k2--;
+ upos--;
}
- else if (url[k1] != req->url.path[k2]) {
+ else if (route[rpos] != requrl.path[upos]) {
break;
}
- k1++; k2++;
+ rpos++; upos++;
+ }
+ return route[rpos] == requrl.path[upos];
+ }
+
+ RoutingResult Router::route(Request *req, THandlerFunction& handler) {
+ std::map<std::string, TDelim> params;
+ bool matched = false;
+ bool seen = false;
+ std::string rname;
+
+ // iterate routes
+ for (auto& route: routes) {
+ std::string method;
+ std::string url;
+ funcptr::tie(method, url, handler, rname) = route;
+
+ // see if we can't match the url
+ params.clear();
+ // simple matcher func
+ matched = match(url, req->url, params);
+
+ if (matched && !method.empty() && req->method != method) {
+ // method did not match, record it though so we can return correct result
+ matched = false;
+ seen = true;
+ continue;
}
+ if (matched) {
+ break;
+ }
+ }
- // ensure.
- if (url[k1] != req->url.path[k2])
- matched = false;
- else
- matched = true;
+ if (!matched) {
+ if (seen) {
+ return RouteNoMethod;
+ }
+ // no route
+ return RouteNotFound;
}
- if (!matched) { return false; } // no route
- req->parameters.clear();
+ req->parameters.clear();
- for(std::map<std::string, TDelim>::iterator i = params.begin(); i != params.end(); i++) {
- int p1,p2;
- funcptr::tie(p1,p2) = i->second;
- std::string value(req->url.path.begin() + p1, req->url.path.begin() + p2);
+ for (const auto& param: params) {
+ int nstart = 0;
+ int nend = 0;
+ funcptr::tie(nstart, nend) = param.second;
+ std::string value(req->url.path.begin() + nstart, req->url.path.begin() + nend);
value = Utility::decodeURL(value);
- req->parameters[i->first] = value;
+ req->parameters[param.first] = std::move(value);
}
- req->routeName = rname;
+ req->routeName = std::move(rname);
- return true;
+ return RouteFound;
};
void Router::printRoutes(std::ostream &os) {
#include <utility>
namespace YaHTTP {
+ enum RoutingResult {
+ RouteFound = 1,
+ RouteNotFound = 0,
+ RouteNoMethod = -1,
+ };
+
typedef funcptr::function <void(Request* req, Response* resp)> THandlerFunction; //!< Handler function pointer
typedef funcptr::tuple<std::string, std::string, THandlerFunction, std::string> TRoute; //!< Route tuple (method, urlmask, handler, name)
typedef std::vector<TRoute> TRouteList; //!< List of routes in order of evaluation
+ typedef funcptr::tuple<int,int> TDelim;
/*! Implements simple router.
static Router router; //<! Singleton instance of Router
public:
void map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name); //<! Instance method for mapping urls
- bool route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
+ RoutingResult route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
void printRoutes(std::ostream &os); //<! Instance method for printing routes
std::pair<std::string, std::string> urlFor(const std::string &name, const strstr_map_t& arguments); //<! Instance method for generating paths
+ static bool match(const std::string& route, const URL& requrl, std::map<std::string, TDelim>& params); //<! Instance method for matching a route
/*! Map an URL.
-If method is left empty, it will match any method. Name is also optional, but needed if you want to find it for making URLs
+If method is left empty, it will match any method. Name is also optional, but needed if you want to find it for making URLs
*/
- static void Map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map(method, url, handler, name); };
- static void Get(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("GET", url, handler, name); }; //<! Helper for mapping GET
- static void Post(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("POST", url, handler, name); }; //<! Helper for mapping POST
- static void Put(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PUT", url, handler, name); }; //<! Helper for mapping PUT
- static void Patch(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PATCH", url, handler, name); }; //<! Helper for mapping PATCH
- static void Delete(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("DELETE", url, handler, name); }; //<! Helper for mapping DELETE
- static void Any(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("", url, handler, name); }; //<! Helper for mapping any method
+ static void Map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map(method, url, std::move(handler), name); };
+ static void Get(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("GET", url, std::move(handler), name); }; //<! Helper for mapping GET
+ static void Post(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("POST", url, std::move(handler), name); }; //<! Helper for mapping POST
+ static void Put(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PUT", url, std::move(handler), name); }; //<! Helper for mapping PUT
+ static void Patch(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PATCH", url, std::move(handler), name); }; //<! Helper for mapping PATCH
+ static void Delete(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("DELETE", url, std::move(handler), name); }; //<! Helper for mapping DELETE
+ static void Any(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("", url, std::move(handler), name); }; //<! Helper for mapping any method
- static bool Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path
+ static bool Match(const std::string& route, const URL& requrl, std::map<std::string, TDelim>& params) { return router.match(route, requrl, params); };
+ static RoutingResult Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path, returns RouteFound if route is found and method matches, RouteNoMethod if route is seen but method did match, and RouteNotFound if not found.
static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; //<! Prints all known routes to given output stream
static std::pair<std::string, std::string> URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; //<! Generates url from named route and arguments. Missing arguments are assumed empty
- static const TRouteList& GetRoutes() { return router.routes; } //<! Reference to route list
+ static const TRouteList& GetRoutes() { return router.routes; } //<! Reference to route list
static void Clear() { router.routes.clear(); } //<! Clear all routes
TRouteList routes; //<! Instance variable for routes
}
key = decodeURL(key);
value = decodeURL(value);
- parameter_map[key] = value;
+ parameter_map[key] = std::move(value);
if (nextpos == std::string::npos) {
// no more parameters left
break;
This repository contains several fuzzing targets that can be used with generic
fuzzing engines like AFL and libFuzzer.
-These targets are built by passing the --enable-fuzz-targets option to the
-configure, then building as usual. You can also build only these targets
-by going into the pdns/ directory and issuing a 'make fuzz_targets' command.
+These targets are built by passing the `--enable-fuzz-targets` option to the
+configure of the authoritative server and dnsdist, then building them as usual.
+You can also build only these targets manually by going into the pdns/ directory
+and issuing a `make fuzz_targets` command for the authoritative server,
+or going into the pdns/dnsdistdist and issuing a `make fuzz_targets` command for
+dnsdist.
The current targets cover:
-- the auth, dnsdist and rec packet caches (fuzz_target_packetcache and
- fuzz_target_dnsdistcache) ;
-- MOADNSParser (fuzz_target_moadnsparser) ;
-- the Proxy Protocol parser (fuzz_target_proxyprotocol) ;
-- the HTTP parser we use (YaHTTP, fuzz_target_yahttp) ;
-- ZoneParserTNG (fuzz_target_zoneparsertng).
-- Parts of the ragel-generated parser (parseRFC1035CharString in
- fuzz_target_dnslabeltext)
+- the auth and rec packet cache (`fuzz_target_packetcache`) ;
+- MOADNSParser (`fuzz_target_moadnsparser`) ;
+- the Proxy Protocol parser (`fuzz_target_proxyprotocol`) ;
+- the HTTP parser we use (YaHTTP, `fuzz_target_yahttp`) ;
+- ZoneParserTNG (`fuzz_target_zoneparsertng`).
+- Parts of the ragel-generated parser (`parseRFC1035CharString` in
+ `fuzz_target_dnslabeltext`) ;
+- the dnsdist packet cache (`fuzz_target_dnsdistcache`).
By default the targets are linked against a standalone target,
-pdns/standalone_fuzz_target_runner.cc, which does no fuzzing but makes it easy
+`standalone_fuzz_target_runner.cc`, which does no fuzzing but makes it easy
to check a given test file, or just that the fuzzing targets can be built properly.
-This behaviour can be changed via the LIB_FUZZING_ENGINE variable, for example
-by setting it to -lFuzzer, building with clang by setting CC=clang CXX=clang++
-before running the configure and adding '-fsanitize=fuzzer-no-link' to CFLAGS
-and CXXFLAGS. Doing so instructs the compiler to instrument the code for
-efficient fuzzing but not to link directly with -lFuzzer, which would make
+This behaviour can be changed via the `LIB_FUZZING_ENGINE` variable, for example
+by setting it to `-lFuzzer`, building with clang by setting `CC=clang CXX=clang++`
+before running the `configure` and adding `-fsanitize=fuzzer-no-link` to `CFLAGS`
+and `CXXFLAGS`. Doing so instructs the compiler to instrument the code for
+efficient fuzzing but not to link directly with `-lFuzzer`, which would make
the compilation tests done during the configure phase fail.
Sanitizers
----------
In order to catch the maximum of issues during fuzzing, it makes sense to
-enable the ASAN and UBSAN sanitizers via --enable-asan and --enable-ubsan
+enable the `ASAN` and `UBSAN` sanitizers via `--enable-asan` and `--enable-ubsan`
options to the configure, or to set the appropriate flags directly.
Corpus
This directory contains a few files used for continuous fuzzing
of the PowerDNS products.
-The 'corpus' directory contains three sub-directories:
-- http-raw-payloads/ contains HTTP payloads of queries, used by
- fuzz_target_yahttp ;
-- proxy-protocol-raw-packets/ contains DNS queries prefixed with a Proxy
- Protocol v2 header, used by fuzz_target_proxyprotocol ;
-- raw-dns-packets/ contains DNS queries and responses as captured on
- the wire. These are used by the fuzz_target_dnsdistcache,
- fuzz_target_moadnsparser and fuzz_target_packetcache targets ;
-- zones/ contains DNS zones, used by the fuzz_target_zoneparsertng
+The `corpus` directory contains three sub-directories:
+- `http-raw-payloads/` contains HTTP payloads of queries, used by
+ `fuzz_target_yahttp` ;
+- `proxy-protocol-raw-packets/` contains DNS queries prefixed with a Proxy
+ Protocol v2 header, used by `fuzz_target_proxyprotocol` ;
+- `raw-dns-packets/` contains DNS queries and responses as captured on
+ the wire. These are used by the `fuzz_target_dnsdistcache`,
+ `fuzz_target_moadnsparser` and `fuzz_target_packetcache` targets ;
+- `zones/` contains DNS zones, used by the `fuzz_target_zoneparsertng`
target.
When run in the OSS-Fuzz environment, the zone files from the
-regression-tests/zones/ directory are added to the ones present
-in the fuzzing/corpus/zones/ directory.
+`regression-tests/zones/` directory are added to the ones present
+in the `fuzzing/corpus/zones/` directory.
Quickly getting started (using clang 11)
----------------------------------------
-First, confgure:
+First, configure the authoritative server:
```
LIB_FUZZING_ENGINE="/usr/lib/clang/11.0.1/lib/linux/libclang_rt.fuzzer-x86_64.a" \
./configure --without-dynmodules --with-modules= --disable-lua-records --disable-ixfrdist --enable-fuzz-targets --disable-dependency-tracking --disable-silent-rules --enable-asan --enable-ubsan
```
+If you build the fuzzing targets only, you will need to issue the following commands first:
+```
+make -j2 -C ext/arc4random/
+make -j2 -C ext/yahttp/
+```
+
Then build:
```
--- /dev/null
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_compare_version.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE])
+#
+# DESCRIPTION
+#
+# This macro compares two version strings. Due to the various number of
+# minor-version numbers that can exist, and the fact that string
+# comparisons are not compatible with numeric comparisons, this is not
+# necessarily trivial to do in a autoconf script. This macro makes doing
+# these comparisons easy.
+#
+# The six basic comparisons are available, as well as checking equality
+# limited to a certain number of minor-version levels.
+#
+# The operator OP determines what type of comparison to do, and can be one
+# of:
+#
+# eq - equal (test A == B)
+# ne - not equal (test A != B)
+# le - less than or equal (test A <= B)
+# ge - greater than or equal (test A >= B)
+# lt - less than (test A < B)
+# gt - greater than (test A > B)
+#
+# Additionally, the eq and ne operator can have a number after it to limit
+# the test to that number of minor versions.
+#
+# eq0 - equal up to the length of the shorter version
+# ne0 - not equal up to the length of the shorter version
+# eqN - equal up to N sub-version levels
+# neN - not equal up to N sub-version levels
+#
+# When the condition is true, shell commands ACTION-IF-TRUE are run,
+# otherwise shell commands ACTION-IF-FALSE are run. The environment
+# variable 'ax_compare_version' is always set to either 'true' or 'false'
+# as well.
+#
+# Examples:
+#
+# AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8])
+# AX_COMPARE_VERSION([3.15],[lt],[3.15.8])
+#
+# would both be true.
+#
+# AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8])
+# AX_COMPARE_VERSION([3.15],[gt],[3.15.8])
+#
+# would both be false.
+#
+# AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8])
+#
+# would be true because it is only comparing two minor versions.
+#
+# AX_COMPARE_VERSION([3.15.7],[eq0],[3.15])
+#
+# would be true because it is only comparing the lesser number of minor
+# versions of the two values.
+#
+# Note: The characters that separate the version numbers do not matter. An
+# empty string is the same as version 0. OP is evaluated by autoconf, not
+# configure, so must be a string, not a variable.
+#
+# The author would like to acknowledge Guido Draheim whose advice about
+# the m4_case and m4_ifvaln functions make this macro only include the
+# portions necessary to perform the specific comparison specified by the
+# OP argument in the final configure script.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Tim Toolan <toolan@ele.uri.edu>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 13
+
+dnl #########################################################################
+AC_DEFUN([AX_COMPARE_VERSION], [
+ AC_REQUIRE([AC_PROG_AWK])
+
+ # Used to indicate true or false condition
+ ax_compare_version=false
+
+ # Convert the two version strings to be compared into a format that
+ # allows a simple string comparison. The end result is that a version
+ # string of the form 1.12.5-r617 will be converted to the form
+ # 0001001200050617. In other words, each number is zero padded to four
+ # digits, and non digits are removed.
+ AS_VAR_PUSHDEF([A],[ax_compare_version_A])
+ A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
+ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/[[^0-9]]//g'`
+
+ AS_VAR_PUSHDEF([B],[ax_compare_version_B])
+ B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
+ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+ -e 's/[[^0-9]]//g'`
+
+ dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary
+ dnl # then the first line is used to determine if the condition is true.
+ dnl # The sed right after the echo is to remove any indented white space.
+ m4_case(m4_tolower($2),
+ [lt],[
+ ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"`
+ ],
+ [gt],[
+ ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"`
+ ],
+ [le],[
+ ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"`
+ ],
+ [ge],[
+ ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"`
+ ],[
+ dnl Split the operator from the subversion count if present.
+ m4_bmatch(m4_substr($2,2),
+ [0],[
+ # A count of zero means use the length of the shorter version.
+ # Determine the number of characters in A and B.
+ ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'`
+ ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'`
+
+ # Set A to no more than B's length and B to no more than A's length.
+ A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"`
+ B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"`
+ ],
+ [[0-9]+],[
+ # A count greater than zero means use only that many subversions
+ A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
+ B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
+ ],
+ [.+],[
+ AC_WARNING(
+ [invalid OP numeric parameter: $2])
+ ],[])
+
+ # Pad zeros at end of numbers to make same length.
+ ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`"
+ B="$B`echo $A | sed 's/./0/g'`"
+ A="$ax_compare_version_tmp_A"
+
+ # Check for equality or inequality as necessary.
+ m4_case(m4_tolower(m4_substr($2,0,2)),
+ [eq],[
+ test "x$A" = "x$B" && ax_compare_version=true
+ ],
+ [ne],[
+ test "x$A" != "x$B" && ax_compare_version=true
+ ],[
+ AC_WARNING([invalid OP parameter: $2])
+ ])
+ ])
+
+ AS_VAR_POPDEF([A])dnl
+ AS_VAR_POPDEF([B])dnl
+
+ dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE.
+ if test "$ax_compare_version" = "true" ; then
+ m4_ifvaln([$4],[$4],[:])dnl
+ m4_ifvaln([$5],[else $5])dnl
+ fi
+]) dnl AX_COMPARE_VERSION
--- /dev/null
+AC_DEFUN([AX_CXX_CXXFS], [
+ AC_LANG_PUSH([C++])
+ old_LIBS="$LIBS"
+ dnl * Test first if it can be used without anything, then -lstdc++fs and -lc++fs
+ AC_CACHE_CHECK([for library with std::filesystem], [ax_cxx_cv_filesystem_lib], [
+ ax_cxx_cv_filesystem_lib=none
+ AC_LINK_IFELSE([AC_LANG_PROGRAM(
+ [[#include <iostream>
+ #include <filesystem>]],
+ [[std::filesystem::path path(".");
+ std::filesystem::status(path);]])],
+ [], [
+ LIBS="$LIBS -lstdc++fs"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM(
+ [[#include <iostream>
+ #include <filesystem>]],
+ [[std::filesystem::path path(".");
+ std::filesystem::status(path);]])],
+ [ax_cxx_cv_filesystem_lib=stdc++fs], [
+ LIBS="$old_LIBS -lc++fs"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM(
+ [[#include <iostream>
+ #include <filesystem>]],
+ [[std::filesystem::path path(".");
+ std::filesystem::status(path);]])],
+ [ax_cxx_cv_filesystem_lib=c++fs], [AC_MSG_ERROR([Cannot find std::filesystem library])])
+ ])])
+ LIBS="$old_LIBS"
+ ])
+ AC_LANG_POP()
+])
# I'm not sure about my test for `il' (be careful: Intel's ICC pre-defines
# the same defines as GCC's).
for i in \
+ "defined __clang__ && __clang_major__ == 18 && __clang_minor__ == 1 @ clang181" \
+ "defined __clang__ && __clang_major__ == 17 && __clang_minor__ == 0 @ clang170" \
+ "defined __clang__ && __clang_major__ == 16 && __clang_minor__ == 0 @ clang160" \
+ "defined __clang__ && __clang_major__ == 15 && __clang_minor__ == 0 @ clang150" \
"defined __clang__ && __clang_major__ == 14 && __clang_minor__ == 0 @ clang140" \
"defined __clang__ && __clang_major__ == 13 && __clang_minor__ == 0 @ clang130" \
"defined __clang__ && __clang_major__ == 12 && __clang_minor__ == 0 @ clang120" \
"defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 9 @ clang39" \
"defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 8 @ clang38" \
"defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 7 @ clang37" \
+ _BOOST_mingw_test(13, 2) \
+ _BOOST_gcc_test(13, 2) \
+ _BOOST_mingw_test(13, 1) \
+ _BOOST_gcc_test(13, 1) \
+ _BOOST_mingw_test(12, 3) \
+ _BOOST_gcc_test(12, 3) \
+ _BOOST_mingw_test(12, 2) \
+ _BOOST_gcc_test(12, 2) \
+ _BOOST_mingw_test(12, 1) \
+ _BOOST_gcc_test(12, 1) \
+ _BOOST_mingw_test(11, 4) \
+ _BOOST_gcc_test(11, 4) \
+ _BOOST_mingw_test(11, 3) \
+ _BOOST_gcc_test(11, 3) \
+ _BOOST_mingw_test(11, 2) \
+ _BOOST_gcc_test(11, 2) \
_BOOST_mingw_test(11, 1) \
_BOOST_gcc_test(11, 1) \
+ _BOOST_mingw_test(10, 5) \
+ _BOOST_gcc_test(10, 5) \
+ _BOOST_mingw_test(10, 4) \
+ _BOOST_gcc_test(10, 4) \
_BOOST_mingw_test(10, 3) \
_BOOST_gcc_test(10, 3) \
_BOOST_mingw_test(10, 2) \
_BOOST_gcc_test(10, 2) \
_BOOST_mingw_test(10, 1) \
_BOOST_gcc_test(10, 1) \
+ _BOOST_mingw_test(9, 5) \
+ _BOOST_gcc_test(9, 5) \
+ _BOOST_mingw_test(9, 4) \
+ _BOOST_gcc_test(9, 4) \
_BOOST_mingw_test(9, 3) \
_BOOST_gcc_test(9, 3) \
_BOOST_mingw_test(9, 2) \
LIBS="$LIBCRYPTO_LIBS $LIBS"
CPPFLAGS="$LIBCRYPTO_INCLUDES $CPPFLAGS"
AC_LINK_IFELSE(
- [AC_LANG_PROGRAM([#include <openssl/crypto.h>], [ERR_load_CRYPTO_strings()])],
+ [AC_LANG_PROGRAM([#include <openssl/bn.h>], [BN_new()])],
[
AC_MSG_RESULT([yes])
AC_CHECK_FUNCS([RAND_bytes RAND_pseudo_bytes CRYPTO_memcmp OPENSSL_init_crypto EVP_MD_CTX_new EVP_MD_CTX_free RSA_get0_key])
- AC_CHECK_DECL(EVP_PKEY_CTX_set1_scrypt_salt, [AC_DEFINE([HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT], [1], [Define to 1 if you have EVP_PKEY_CTX_set1_scrypt_salt])], [], [#include <openssl/kdf.h>])
+ # you might be wondering why the stdarg.h and stddef.h includes,
+ # in which case please have a look at https://github.com/PowerDNS/pdns/issues/12926
+ # and weep, yelling at Red Hat
+ AC_CHECK_DECL(EVP_PKEY_CTX_set1_scrypt_salt,
+ [AC_DEFINE([HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT], [1], [Define to 1 if you have EVP_PKEY_CTX_set1_scrypt_salt])],
+ [],
+ [#include <stdarg.h>
+ #include <stddef.h>
+ #include <openssl/kdf.h>])
$1
], [
AC_MSG_RESULT([no])
AC_DEFUN([PDNS_CHECK_SECURE_MEMSET], [
- AC_CHECK_FUNCS([explicit_bzero explicit_memset])
+ AC_CHECK_FUNCS([explicit_bzero explicit_memset memset_s])
])
[enable_coverage=no]
)
AC_MSG_RESULT([$enable_coverage])
- AS_IF([test "x$enable_coverage" != "xno"], [
+
+ AS_IF([test "x$enable_coverage" = "xclang"], [
+ dnl let's see if the clang++ specific format is supported,
+ dnl as it has a much lower overhead and is more accurate,
+ dnl see https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
+ gl_COMPILER_OPTION_IF([-fprofile-instr-generate -fcoverage-mapping], [
+ CFLAGS="$CFLAGS -DCOVERAGE -DCLANG_COVERAGE -fprofile-instr-generate -fcoverage-mapping"
+ CXXFLAGS="$CXXFLAGS -DCOVERAGE -DCLANG_COVERAGE -fprofile-instr-generate -fcoverage-mapping"
+ ], [
+ AC_MSG_ERROR([$CXX does not support gathering coverage data in the clang format])
+ ])
+ ])
+
+ AS_IF([test "x$enable_coverage" = "xyes"], [
gl_COMPILER_OPTION_IF([-fprofile-arcs -ftest-coverage], [
- CXXFLAGS="$CXXFLAGS -U_FORTIFY_SOURCE -g -O0 -fprofile-arcs -ftest-coverage"
+ CFLAGS="$CFLAGS -DCOVERAGE --coverage"
+ CXXFLAGS="$CXXFLAGS -DCOVERAGE --coverage"
+ LDFLAGS="$LDFLAGS --coverage"
], [
AC_MSG_ERROR([$CXX does not support gathering coverage data])
])
AC_DEFUN([PDNS_WITH_NET_SNMP], [
AC_MSG_CHECKING([if we need to link in Net SNMP])
AC_ARG_WITH([net-snmp],
- AS_HELP_STRING([--with-net-snmp],[enable net snmp support @<:@default=auto@:>@]),
+ AS_HELP_STRING([--with-net-snmp],[enable net snmp support @<:@default=no@:>@]),
[with_net_snmp=$withval],
- [with_net_snmp=auto],
+ [with_net_snmp=no],
)
AC_MSG_RESULT([$with_net_snmp])
binddnssec.cc
libbindbackend_la_LDFLAGS = -module -avoid-version
-
-# for bindparser.h/hh
-.hh.h:
- cp $< $@
int Bind2Backend::s_first = 1;
bool Bind2Backend::s_ignore_broken_records = false;
-std::mutex Bind2Backend::s_supermaster_config_lock; // protects writes to config file
+std::mutex Bind2Backend::s_autosecondary_config_lock; // protects writes to config file
std::mutex Bind2Backend::s_startup_lock;
string Bind2Backend::s_binddirectory;
fd = -1;
*d_of << "; Written by PowerDNS, don't edit!" << endl;
- *d_of << "; Zone '" << bbd.d_name << "' retrieved from master " << endl
- << "; at " << nowTime() << endl; // insert master info here again
+ *d_of << "; Zone '" << bbd.d_name << "' retrieved from primary " << endl
+ << "; at " << nowTime() << endl; // insert primary info here again
return true;
}
throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
}
- shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+ shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
string content = drc->getZoneRepresentation();
// SOA needs stripping too! XXX FIXME - also, this should not be here I think
return true;
}
-void Bind2Backend::getUpdatedMasters(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
+void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
{
vector<DomainInfo> consider;
{
auto state = s_state.read_lock();
for (const auto& i : *state) {
- if (i.d_kind != DomainInfo::Master && this->alsoNotify.empty() && i.d_also_notify.empty())
+ if (i.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && i.d_also_notify.empty())
continue;
DomainInfo di;
di.last_check = i.d_lastcheck;
di.notified_serial = i.d_lastnotified;
di.backend = this;
- di.kind = DomainInfo::Master;
+ di.kind = DomainInfo::Primary;
consider.push_back(std::move(di));
}
}
di.zone = i.d_name;
di.last_check = i.d_lastcheck;
di.kind = i.d_kind;
- di.masters = i.d_masters;
+ di.primaries = i.d_primaries;
di.backend = this;
domains->push_back(std::move(di));
};
}
}
-void Bind2Backend::getUnfreshSlaveInfos(vector<DomainInfo>* unfreshDomains)
+void Bind2Backend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
{
vector<DomainInfo> domains;
{
auto state = s_state.read_lock();
domains.reserve(state->size());
for (const auto& i : *state) {
- if (i.d_kind != DomainInfo::Slave)
+ if (i.d_kind != DomainInfo::Secondary)
continue;
DomainInfo sd;
sd.id = i.d_id;
sd.zone = i.d_name;
- sd.masters = i.d_masters;
+ sd.primaries = i.d_primaries;
sd.last_check = i.d_lastcheck;
sd.backend = this;
- sd.kind = DomainInfo::Slave;
+ sd.kind = DomainInfo::Secondary;
domains.push_back(std::move(sd));
}
}
catch (...) {
}
sd.serial = soadata.serial;
+ // coverity[store_truncates_time_t]
if (sd.last_check + soadata.refresh < (unsigned int)time(nullptr))
unfreshDomains->push_back(std::move(sd));
}
di.id = bbd.d_id;
di.zone = domain;
- di.masters = bbd.d_masters;
+ di.primaries = bbd.d_primaries;
di.last_check = bbd.d_lastcheck;
di.backend = this;
di.kind = bbd.d_kind;
bbd->d_status = "parsed into memory at " + nowTime();
bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
bbd->d_nsec3zone = nsec3zone;
- bbd->d_nsec3param = ns3pr;
+ bbd->d_nsec3param = std::move(ns3pr);
}
/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
ret << "\t On-disk file: " << info.d_filename << " (" << info.d_ctime << ")" << std::endl;
ret << "\t Kind: ";
switch (info.d_kind) {
- case DomainInfo::Master:
- ret << "Master";
+ case DomainInfo::Primary:
+ ret << "Primary";
break;
- case DomainInfo::Slave:
- ret << "Slave";
+ case DomainInfo::Secondary:
+ ret << "Secondary";
break;
default:
ret << "Native";
}
ret << std::endl;
- ret << "\t Masters: " << std::endl;
- for (const auto& master : info.d_masters) {
- ret << "\t\t - " << master.toStringWithPort() << std::endl;
+ ret << "\t Primaries: " << std::endl;
+ for (const auto& primary : info.d_primaries) {
+ ret << "\t\t - " << primary.toStringWithPort() << std::endl;
}
ret << "\t Also Notify: " << std::endl;
for (const auto& also : info.d_also_notify) {
}
}
-void Bind2Backend::loadConfig(string* status)
+void Bind2Backend::loadConfig(string* status) // NOLINT(readability-function-cognitive-complexity) 13379 https://github.com/PowerDNS/pdns/issues/13379 Habbie: zone2sql.cc, bindbackend2.cc: reduce complexity
{
static int domain_id = 1;
if (domain.type.empty()) {
g_log << Logger::Notice << d_logprefix << " Zone '" << domain.name << "' has no type specified, assuming 'native'" << endl;
}
- if (domain.type != "master" && domain.type != "slave" && domain.type != "native" && !domain.type.empty()) {
+ if (domain.type != "primary" && domain.type != "secondary" && domain.type != "native" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because type '" << domain.type << "' is invalid" << endl;
rejected++;
continue;
// overwrite what we knew about the domain
bbd.d_name = domain.name;
bool filenameChanged = (bbd.d_filename != domain.filename);
- bool addressesChanged = (bbd.d_masters != domain.masters || bbd.d_also_notify != domain.alsoNotify);
+ bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
bbd.d_filename = domain.filename;
- bbd.d_masters = domain.masters;
+ bbd.d_primaries = domain.primaries;
bbd.d_also_notify = domain.alsoNotify;
DomainInfo::DomainKind kind = DomainInfo::Native;
- if (domain.type == "master")
- kind = DomainInfo::Master;
- if (domain.type == "slave")
- kind = DomainInfo::Slave;
+ if (domain.type == "primary" || domain.type == "master") {
+ kind = DomainInfo::Primary;
+ }
+ if (domain.type == "secondary" || domain.type == "slave") {
+ kind = DomainInfo::Secondary;
+ }
bool kindChanged = (bbd.d_kind != kind);
bbd.d_kind = kind;
catch (std::system_error& ae) {
ostringstream msg;
if (ae.code().value() == ENOENT && isNew && domain.type == "slave")
- msg << " error at " + nowTime() << " no file found for new slave domain '" << domain.name << "'. Has not been AXFR'd yet";
+ msg << " error at " + nowTime() << " no file found for new secondary domain '" << domain.name << "'. Has not been AXFR'd yet";
else
msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
if (!bbd.d_loaded) {
d_handle.reset();
- throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.d_filename + "' not loaded (file missing, corrupt or master dead)"); // fsck
+ throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.d_filename + "' not loaded (file missing, corrupt or primary dead)"); // fsck
}
d_handle.d_records = bbd.d_records.get();
bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
{
- if (getArg("supermaster-config").empty())
+ if (getArg("autoprimary-config").empty())
return false;
- ifstream c_if(getArg("supermasters"), std::ios::in);
+ ifstream c_if(getArg("autoprimaries"), std::ios::in);
if (!c_if) {
- g_log << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
+ g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
return false;
}
return true;
}
-bool Bind2Backend::superMasterBackend(const string& ip, const DNSName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** db)
+bool Bind2Backend::autoPrimaryBackend(const string& ip, const DNSName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** db)
{
// Check whether we have a configfile available.
- if (getArg("supermaster-config").empty())
+ if (getArg("autoprimary-config").empty())
return false;
- ifstream c_if(getArg("supermasters").c_str(), std::ios::in); // this was nocreate?
+ ifstream c_if(getArg("autoprimaries").c_str(), std::ios::in); // this was nocreate?
if (!c_if) {
- g_log << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
+ g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
return false;
}
if (sip != ip) // ip not found in authorization list - reject
return false;
- // ip authorized as supermaster - accept
+ // ip authorized as autoprimary - accept
*db = this;
if (saccount.length() > 0)
*account = saccount.c_str();
return bbd;
}
-bool Bind2Backend::createSlaveDomain(const string& ip, const DNSName& domain, const string& /* nameserver */, const string& account)
+bool Bind2Backend::createSecondaryDomain(const string& ip, const DNSName& domain, const string& /* nameserver */, const string& account)
{
- string filename = getArg("supermaster-destdir") + '/' + domain.toStringNoDot();
+ string filename = getArg("autoprimary-destdir") + '/' + domain.toStringNoDot();
g_log << Logger::Warning << d_logprefix
<< " Writing bind config zone statement for superslave zone '" << domain
- << "' from supermaster " << ip << endl;
+ << "' from autoprimary " << ip << endl;
{
- std::lock_guard<std::mutex> l2(s_supermaster_config_lock);
+ std::lock_guard<std::mutex> l2(s_autosecondary_config_lock);
- ofstream c_of(getArg("supermaster-config").c_str(), std::ios::app);
+ ofstream c_of(getArg("autoprimary-config").c_str(), std::ios::app);
if (!c_of) {
- g_log << Logger::Error << "Unable to open supermaster configfile for append: " << stringerror() << endl;
- throw DBException("Unable to open supermaster configfile for append: " + stringerror());
+ g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << stringerror() << endl;
+ throw DBException("Unable to open autoprimary configfile for append: " + stringerror());
}
c_of << endl;
- c_of << "# Superslave zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
+ c_of << "# AutoSecondary zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
c_of << "zone \"" << domain.toStringNoDot() << "\" {" << endl;
- c_of << "\ttype slave;" << endl;
+ c_of << "\ttype secondary;" << endl;
c_of << "\tfile \"" << filename << "\";" << endl;
- c_of << "\tmasters { " << ip << "; };" << endl;
+ c_of << "\tprimaries { " << ip << "; };" << endl;
c_of << "};" << endl;
c_of.close();
}
BB2DomainInfo bbd = createDomainEntry(domain, filename);
- bbd.d_kind = DomainInfo::Slave;
- bbd.d_masters.push_back(ComboAddress(ip, 53));
+ bbd.d_kind = DomainInfo::Secondary;
+ bbd.d_primaries.push_back(ComboAddress(ip, 53));
bbd.setCtime();
safePutBBDomainInfo(bbd);
return true;
}
-bool Bind2Backend::searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result)
+bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
{
SimpleMatch sm(pattern, true);
static bool mustlog = ::arg().mustDo("query-logging");
shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
- for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < static_cast<vector<DNSResourceRecord>::size_type>(maxResults) && ri != rhandle->end(); ri++) {
+ for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < maxResults && ri != rhandle->end(); ri++) {
DNSName name = ri->qname.empty() ? i.d_name : (ri->qname + i.d_name);
if (sm.match(name) || sm.match(ri->content)) {
DNSResourceRecord r;
- r.qname = name;
+ r.qname = std::move(name);
r.domain_id = i.d_id;
r.content = ri->content;
r.qtype = ri->qtype;
declare(suffix, "ignore-broken-records", "Ignore records that are out-of-bound for the zone.", "no");
declare(suffix, "config", "Location of named.conf", "");
declare(suffix, "check-interval", "Interval for zonefile changes", "0");
- declare(suffix, "supermaster-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
- declare(suffix, "supermasters", "List of IP-addresses of supermasters", "");
- declare(suffix, "supermaster-destdir", "Destination directory for newly added slave zones", ::arg()["config-dir"]);
+ declare(suffix, "autoprimary-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
+ declare(suffix, "autoprimaries", "List of IP-addresses of autoprimaries", "");
+ declare(suffix, "autoprimary-destdir", "Destination directory for newly added secondary zones", ::arg()["config-dir"]);
declare(suffix, "dnssec-db", "Filename to store & access our DNSSEC metadatabase, empty for none", "");
declare(suffix, "dnssec-db-journal-mode", "SQLite3 journal mode", "WAL");
declare(suffix, "hybrid", "Store DNSSEC metadata in other backend", "no");
class LookButDontTouch
{
public:
- LookButDontTouch()
- {
- }
+ LookButDontTouch() = default;
LookButDontTouch(shared_ptr<T>&& records) :
d_records(std::move(records))
{
}
DNSName d_name; //!< actual name of the domain
- DomainInfo::DomainKind d_kind; //!< the kind of domain
+ DomainInfo::DomainKind d_kind{DomainInfo::Native}; //!< the kind of domain
string d_filename; //!< full absolute filename of the zone on disk
string d_status; //!< message describing status of a domain, for human consumption
- vector<ComboAddress> d_masters; //!< IP address of the master of this domain
+ vector<ComboAddress> d_primaries; //!< IP address of the primary of this domain
set<string> d_also_notify; //!< IP list of hosts to also notify
LookButDontTouch<recordstorage_t> d_records; //!< the actual records belonging to this domain
time_t d_ctime{0}; //!< last known ctime of the file on disk
time_t d_lastcheck{0}; //!< last time domain was checked for freshness
- uint32_t d_lastnotified{0}; //!< Last serial number we notified our slaves of
- unsigned int d_id; //!< internal id of the domain
+ uint32_t d_lastnotified{0}; //!< Last serial number we notified our secondaries of
+ unsigned int d_id{0}; //!< internal id of the domain
mutable bool d_checknow; //!< if this domain has been flagged for a check
- bool d_loaded; //!< if a domain is loaded
+ bool d_loaded{false}; //!< if a domain is loaded
bool d_wasRejectedLastReload{false}; //!< if the domain was rejected during Bind2Backend::queueReloadAndStore
bool d_nsec3zone{false};
NSEC3PARAMRecordContent d_nsec3param;
private:
time_t getCtime();
- time_t d_checkinterval;
+ time_t d_checkinterval{0};
};
class SSQLite3;
{
public:
Bind2Backend(const string& suffix = "", bool loadZones = true);
- ~Bind2Backend();
- void getUnfreshSlaveInfos(vector<DomainInfo>* unfreshDomains) override;
- void getUpdatedMasters(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+ ~Bind2Backend() override;
+ void getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains) override;
+ void getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial = true) override;
time_t getCtime(const string& fname);
// DNSSEC
bool commitTransaction() override;
bool abortTransaction() override;
void alsoNotifies(const DNSName& domain, set<string>* ips) override;
- bool searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result) override;
+ bool searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result) override;
// the DNSSEC related (getDomainMetadata has broader uses too)
bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta) override;
// for autoprimary support
bool autoPrimariesList(std::vector<AutoPrimary>& primaries) override;
- bool superMasterBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** db) override;
- static std::mutex s_supermaster_config_lock;
- bool createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
+ bool autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** db) override;
+ static std::mutex s_autosecondary_config_lock;
+ bool createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
private:
void setupDNSSEC();
handle();
+ handle(const handle&) = delete;
+ handle& operator=(const handle&) = delete; // don't go copying this
+
shared_ptr<const recordstorage_t> d_records;
recordstorage_t::index<UnorderedNameTag>::type::const_iterator d_iter, d_end_iter;
DNSName qname;
DNSName domain;
- int id;
+ int id{-1};
QType qtype;
- bool d_list;
- bool mustlog;
+ bool d_list{false};
+ bool mustlog{false};
private:
bool get_normal(DNSResourceRecord&);
bool get_list(DNSResourceRecord&);
-
- void operator=(const handle&); // don't go copying this
- handle(const handle&);
};
unique_ptr<SSqlStatement> d_getAllDomainMetadataQuery_stmt;
void Bind2Backend::setupDNSSEC()
{
- if (!getArg("dnssec-db").empty())
+ if (!getArg("dnssec-db").empty()) {
throw runtime_error("bind-dnssec-db requires building PowerDNS with SQLite3");
+ }
}
bool Bind2Backend::doesDNSSEC()
return d_hybrid;
}
-bool Bind2Backend::getNSEC3PARAM(const DNSName& name, NSEC3PARAMRecordContent* ns3p)
+bool Bind2Backend::getNSEC3PARAM(const DNSName& /* name */, NSEC3PARAMRecordContent* /* ns3p */)
{
return false;
}
-bool Bind2Backend::getNSEC3PARAMuncached(const DNSName& name, NSEC3PARAMRecordContent* ns3p)
+bool Bind2Backend::getNSEC3PARAMuncached(const DNSName& /* name */, NSEC3PARAMRecordContent* /* ns3p */)
{
return false;
}
-bool Bind2Backend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
+bool Bind2Backend::getAllDomainMetadata(const DNSName& /* name */, std::map<std::string, std::vector<std::string>>& /* meta */)
{
return false;
}
-bool Bind2Backend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
+bool Bind2Backend::getDomainMetadata(const DNSName& /* name */, const std::string& /* kind */, std::vector<std::string>& /* meta */)
{
return false;
}
-bool Bind2Backend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
+bool Bind2Backend::setDomainMetadata(const DNSName& /* name */, const std::string& /* kind */, const std::vector<std::string>& /* meta */)
{
return false;
}
-bool Bind2Backend::getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
+bool Bind2Backend::getDomainKeys(const DNSName& /* name */, std::vector<KeyData>& /* keys */)
{
return false;
}
-bool Bind2Backend::removeDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::removeDomainKey(const DNSName& /* name */, unsigned int /* id */)
{
return false;
}
-bool Bind2Backend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
+bool Bind2Backend::addDomainKey(const DNSName& /* name */, const KeyData& /* key */, int64_t& /* id */)
{
return false;
}
-bool Bind2Backend::activateDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::activateDomainKey(const DNSName& /* name */, unsigned int /* id */)
{
return false;
}
-bool Bind2Backend::deactivateDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::deactivateDomainKey(const DNSName& /* name */, unsigned int /* id */)
{
return false;
}
-bool Bind2Backend::publishDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::publishDomainKey(const DNSName& /* name */, unsigned int /* id */)
{
return false;
}
-bool Bind2Backend::unpublishDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::unpublishDomainKey(const DNSName& /* name */, unsigned int /* id */)
{
return false;
}
-bool Bind2Backend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
+bool Bind2Backend::getTSIGKey(const DNSName& /* name */, DNSName& /* algorithm */, string& /* content */)
{
return false;
}
-bool Bind2Backend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
+bool Bind2Backend::setTSIGKey(const DNSName& /* name */, const DNSName& /* algorithm */, const string& /* content */)
{
return false;
}
-bool Bind2Backend::deleteTSIGKey(const DNSName& name)
+bool Bind2Backend::deleteTSIGKey(const DNSName& /* name */)
{
return false;
}
-bool Bind2Backend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
+bool Bind2Backend::getTSIGKeys(std::vector<struct TSIGKey>& /* keys */)
{
return false;
}
void Bind2Backend::setupStatements()
{
- return;
}
void Bind2Backend::freeStatements()
{
- return;
}
#else
if (getArg("dnssec-db").empty() || d_hybrid)
return;
try {
- d_dnssecdb = shared_ptr<SSQLite3>(new SSQLite3(getArg("dnssec-db"), getArg("dnssec-db-journal-mode")));
+ d_dnssecdb = std::make_shared<SSQLite3>(getArg("dnssec-db"), getArg("dnssec-db-journal-mode"));
setupStatements();
}
catch (SSqlException& se) {
static int maxNSEC3Iterations = ::arg().asNum("max-nsec3-iterations");
if (ns3p) {
- auto tmp = std::dynamic_pointer_cast<NSEC3PARAMRecordContent>(DNSRecordContent::mastermake(QType::NSEC3PARAM, 1, value));
+ auto tmp = std::dynamic_pointer_cast<NSEC3PARAMRecordContent>(DNSRecordContent::make(QType::NSEC3PARAM, 1, value));
*ns3p = *tmp;
if (ns3p->d_iterations > maxNSEC3Iterations) {
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <cstdint>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <boost/algorithm/string/replace.hpp>
#include <boost/format.hpp>
#include <fstream>
+#include <filesystem>
+#include <utility>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
#include <yaml-cpp/yaml.h>
+#pragma GCC diagnostic pop
ReadWriteLock GeoIPBackend::s_state_lock;
struct GeoIPDNSResourceRecord : DNSResourceRecord
{
- int weight;
- bool has_weight;
+ int weight{};
+ bool has_weight{};
};
struct GeoIPService
struct GeoIPDomain
{
- int id;
+ std::uint32_t id{};
DNSName domain;
- int ttl;
+ int ttl{};
map<DNSName, GeoIPService> services;
map<DNSName, vector<GeoIPDNSResourceRecord>> records;
vector<string> mapping_lookup_formats;
GeoIPBackend::GeoIPBackend(const string& suffix)
{
- WriteLock wl(&s_state_lock);
- d_dnssec = false;
+ WriteLock writeLock(&s_state_lock);
setArgPrefix("geoip" + suffix);
- if (getArg("dnssec-keydir").empty() == false) {
- DIR* d = opendir(getArg("dnssec-keydir").c_str());
- if (d == nullptr) {
+ if (!getArg("dnssec-keydir").empty()) {
+ auto dirHandle = std::unique_ptr<DIR, decltype(&closedir)>(opendir(getArg("dnssec-keydir").c_str()), closedir);
+ if (!dirHandle) {
throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
}
d_dnssec = true;
- closedir(d);
}
if (s_rc == 0) { // first instance gets to open everything
initialize();
// infinite recursion.
static bool validateMappingLookupFormats(const vector<string>& formats)
{
- string::size_type cur, last;
+ string::size_type cur = 0;
+ string::size_type last = 0;
+
for (const auto& lookupFormat : formats) {
last = 0;
while ((cur = lookupFormat.find("%", last)) != string::npos) {
- if (!lookupFormat.compare(cur, 3, "%mp")) {
+ if (lookupFormat.compare(cur, 3, "%mp") == 0) {
return false;
}
- else if (!lookupFormat.compare(cur, 2, "%%")) { // Ensure escaped % is also accepted
+
+ if (lookupFormat.compare(cur, 2, "%%") == 0) { // Ensure escaped % is also accepted
last = cur + 2;
continue;
}
return true;
}
-void GeoIPBackend::initialize()
+static vector<GeoIPDNSResourceRecord> makeDNSResourceRecord(GeoIPDomain& dom, DNSName name)
{
- YAML::Node config;
- vector<GeoIPDomain> tmp_domains;
-
- s_geoip_files.clear(); // reset pointers
+ GeoIPDNSResourceRecord resourceRecord;
+ resourceRecord.domain_id = static_cast<int>(dom.id);
+ resourceRecord.ttl = dom.ttl;
+ resourceRecord.qname = std::move(name);
+ resourceRecord.qtype = QType(0); // empty non terminal
+ resourceRecord.content = "";
+ resourceRecord.auth = true;
+ resourceRecord.weight = 100;
+ resourceRecord.has_weight = false;
+ vector<GeoIPDNSResourceRecord> rrs;
+ rrs.push_back(resourceRecord);
+ return rrs;
+}
- if (getArg("database-files").empty() == false) {
- vector<string> files;
- stringtok(files, getArg("database-files"), " ,\t\r\n");
- for (auto const& file : files) {
- s_geoip_files.push_back(GeoIPInterface::makeInterface(file));
+void GeoIPBackend::setupNetmasks(const YAML::Node& domain, GeoIPDomain& dom)
+{
+ for (auto service = domain["services"].begin(); service != domain["services"].end(); service++) {
+ unsigned int netmask4 = 0;
+ unsigned int netmask6 = 0;
+ DNSName serviceName{service->first.as<string>()};
+ NetmaskTree<vector<string>> netmaskTree;
+
+ // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
+ if (service->second.IsMap()) {
+ for (auto net = service->second.begin(); net != service->second.end(); net++) {
+ vector<string> value;
+ if (net->second.IsSequence()) {
+ value = net->second.as<vector<string>>();
+ }
+ else {
+ value.push_back(net->second.as<string>());
+ }
+ if (net->first.as<string>() == "default") {
+ netmaskTree.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
+ netmaskTree.insert(Netmask("::/0")).second.swap(value);
+ }
+ else {
+ Netmask netmask{net->first.as<string>()};
+ netmaskTree.insert(netmask).second.swap(value);
+ if (netmask.isIPv6() && netmask6 < netmask.getBits()) {
+ netmask6 = netmask.getBits();
+ }
+ if (!netmask.isIPv6() && netmask4 < netmask.getBits()) {
+ netmask4 = netmask.getBits();
+ }
+ }
+ }
+ }
+ else {
+ vector<string> value;
+ if (service->second.IsSequence()) {
+ value = service->second.as<vector<string>>();
+ }
+ else {
+ value.push_back(service->second.as<string>());
+ }
+ netmaskTree.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
+ netmaskTree.insert(Netmask("::/0")).second.swap(value);
}
- }
- if (s_geoip_files.empty())
- g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;
+ // Allow per domain override of mapping_lookup_formats and custom_mapping.
+ // If not defined, the global values will be used.
+ if (YAML::Node formats = domain["mapping_lookup_formats"]) {
+ auto mapping_lookup_formats = formats.as<vector<string>>();
+ if (!validateMappingLookupFormats(mapping_lookup_formats)) {
+ throw PDNSException(string("%mp is not allowed in mapping lookup formats of domain ") + dom.domain.toLogString());
+ }
- if (!getArg("zones-file").empty()) {
- try {
- config = YAML::LoadFile(getArg("zones-file"));
+ dom.mapping_lookup_formats = std::move(mapping_lookup_formats);
}
- catch (YAML::Exception& ex) {
- throw PDNSException(string("Cannot read config file ") + ex.msg);
+ else {
+ dom.mapping_lookup_formats = d_global_mapping_lookup_formats;
+ }
+ if (YAML::Node mapping = domain["custom_mapping"]) {
+ dom.custom_mapping = mapping.as<map<std::string, std::string>>();
+ }
+ else {
+ dom.custom_mapping = d_global_custom_mapping;
}
- }
- // Global lookup formats and mapping will be used
- // if none defined at the domain level.
- vector<string> global_mapping_lookup_formats;
- map<std::string, std::string> global_custom_mapping;
- if (YAML::Node formats = config["mapping_lookup_formats"]) {
- global_mapping_lookup_formats = formats.as<vector<string>>();
- if (!validateMappingLookupFormats(global_mapping_lookup_formats))
- throw PDNSException(string("%mp is not allowed in mapping lookup"));
- }
- if (YAML::Node mapping = config["custom_mapping"]) {
- global_custom_mapping = mapping.as<map<std::string, std::string>>();
+ dom.services[serviceName].netmask4 = netmask4;
+ dom.services[serviceName].netmask6 = netmask6;
+ dom.services[serviceName].masks.swap(netmaskTree);
}
+}
- for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
- const auto& domain = *_domain;
- GeoIPDomain dom;
- dom.id = tmp_domains.size();
+bool GeoIPBackend::loadDomain(const YAML::Node& domain, std::uint32_t domainID, GeoIPDomain& dom)
+{
+ try {
+ dom.id = domainID;
dom.domain = DNSName(domain["domain"].as<string>());
dom.ttl = domain["ttl"].as<int>();
- for (YAML::const_iterator recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
+ for (auto recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
DNSName qname = DNSName(recs->first.as<string>());
vector<GeoIPDNSResourceRecord> rrs;
- for (YAML::const_iterator item = recs->second.begin(); item != recs->second.end(); item++) {
- YAML::const_iterator rec = item->begin();
+ for (auto item = recs->second.begin(); item != recs->second.end(); item++) {
+ auto rec = item->begin();
GeoIPDNSResourceRecord rr;
- rr.domain_id = dom.id;
+ rr.domain_id = static_cast<int>(dom.id);
rr.ttl = dom.ttl;
rr.qname = qname;
if (rec->first.IsNull()) {
}
else if (rec->second.IsMap()) {
for (YAML::const_iterator iter = rec->second.begin(); iter != rec->second.end(); iter++) {
- string attr = iter->first.as<string>();
+ auto attr = iter->first.as<string>();
if (attr == "content") {
- string content = iter->second.as<string>();
- rr.content = content;
+ auto content = iter->second.as<string>();
+ rr.content = std::move(content);
}
else if (attr == "weight") {
rr.weight = iter->second.as<int>();
}
}
else {
- string content = rec->second.as<string>();
- rr.content = content;
+ auto content = rec->second.as<string>();
+ rr.content = std::move(content);
rr.weight = 100;
}
- rr.auth = 1;
+ rr.auth = true;
rrs.push_back(rr);
}
std::swap(dom.records[qname], rrs);
}
- for (YAML::const_iterator service = domain["services"].begin(); service != domain["services"].end(); service++) {
- unsigned int netmask4 = 0, netmask6 = 0;
- DNSName srvName{service->first.as<string>()};
- NetmaskTree<vector<string>> nmt;
-
- // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
- if (service->second.IsMap()) {
- for (YAML::const_iterator net = service->second.begin(); net != service->second.end(); net++) {
- vector<string> value;
- if (net->second.IsSequence()) {
- value = net->second.as<vector<string>>();
- }
- else {
- value.push_back(net->second.as<string>());
- }
- if (net->first.as<string>() == "default") {
- nmt.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
- nmt.insert(Netmask("::/0")).second.swap(value);
- }
- else {
- Netmask nm{net->first.as<string>()};
- nmt.insert(nm).second.swap(value);
- if (nm.isIPv6() == true && netmask6 < nm.getBits())
- netmask6 = nm.getBits();
- if (nm.isIPv6() == false && netmask4 < nm.getBits())
- netmask4 = nm.getBits();
- }
- }
- }
- else {
- vector<string> value;
- if (service->second.IsSequence()) {
- value = service->second.as<vector<string>>();
- }
- else {
- value.push_back(service->second.as<string>());
- }
- nmt.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
- nmt.insert(Netmask("::/0")).second.swap(value);
- }
-
- // Allow per domain override of mapping_lookup_formats and custom_mapping.
- // If not defined, the global values will be used.
- if (YAML::Node formats = domain["mapping_lookup_formats"]) {
- vector<string> mapping_lookup_formats = formats.as<vector<string>>();
- if (!validateMappingLookupFormats(mapping_lookup_formats))
- throw PDNSException(string("%mp is not allowed in mapping lookup formats of domain ") + dom.domain.toLogString());
-
- dom.mapping_lookup_formats = mapping_lookup_formats;
- }
- else {
- dom.mapping_lookup_formats = global_mapping_lookup_formats;
- }
- if (YAML::Node mapping = domain["custom_mapping"]) {
- dom.custom_mapping = mapping.as<map<std::string, std::string>>();
- }
- else {
- dom.custom_mapping = global_custom_mapping;
- }
-
- dom.services[srvName].netmask4 = netmask4;
- dom.services[srvName].netmask6 = netmask6;
- dom.services[srvName].masks.swap(nmt);
- }
+ setupNetmasks(domain, dom);
// rectify the zone, first static records
for (auto& item : dom.records) {
// ensure we have parent in records
DNSName name = item.first;
while (name.chopOff() && name.isPartOf(dom.domain)) {
- if (dom.records.find(name) == dom.records.end() && !dom.services.count(name)) { // don't ENT out a service!
- GeoIPDNSResourceRecord rr;
- vector<GeoIPDNSResourceRecord> rrs;
- rr.domain_id = dom.id;
- rr.ttl = dom.ttl;
- rr.qname = name;
- rr.qtype = QType(0); // empty non terminal
- rr.content = "";
- rr.auth = 1;
- rr.weight = 100;
- rr.has_weight = false;
- rrs.push_back(rr);
+ if (dom.records.find(name) == dom.records.end() && (dom.services.count(name) == 0U)) { // don't ENT out a service!
+ auto rrs = makeDNSResourceRecord(dom, name);
std::swap(dom.records[name], rrs);
}
}
DNSName name = item.first;
while (name.chopOff() && name.isPartOf(dom.domain)) {
if (dom.records.find(name) == dom.records.end()) {
- GeoIPDNSResourceRecord rr;
- vector<GeoIPDNSResourceRecord> rrs;
- rr.domain_id = dom.id;
- rr.ttl = dom.ttl;
- rr.qname = name;
- rr.qtype = QType(0);
- rr.content = "";
- rr.auth = 1;
- rr.weight = 100;
- rr.has_weight = false;
- rrs.push_back(rr);
+ auto rrs = makeDNSResourceRecord(dom, name);
std::swap(dom.records[name], rrs);
}
}
map<uint16_t, GeoIPDNSResourceRecord*> lasts;
bool has_weight = false;
// first we look for used weight
- for (const auto& rr : item.second) {
- weights[rr.qtype.getCode()] += rr.weight;
- if (rr.has_weight)
+ for (const auto& resourceRecord : item.second) {
+ weights[resourceRecord.qtype.getCode()] += static_cast<float>(resourceRecord.weight);
+ if (resourceRecord.has_weight) {
has_weight = true;
+ }
}
if (has_weight) {
// put them back as probabilities and values..
- for (auto& rr : item.second) {
- uint16_t rr_type = rr.qtype.getCode();
- rr.weight = static_cast<int>((static_cast<float>(rr.weight) / weights[rr_type]) * 1000.0);
- sums[rr_type] += rr.weight;
- rr.has_weight = has_weight;
- lasts[rr_type] = &rr;
+ for (auto& resourceRecord : item.second) {
+ uint16_t rr_type = resourceRecord.qtype.getCode();
+ resourceRecord.weight = static_cast<int>((static_cast<float>(resourceRecord.weight) / weights[rr_type]) * 1000.0);
+ sums[rr_type] += static_cast<float>(resourceRecord.weight);
+ resourceRecord.has_weight = has_weight;
+ lasts[rr_type] = &resourceRecord;
}
// remove rounding gap
for (auto& x : lasts) {
float sum = sums[x.first];
- if (sum < 1000)
+ if (sum < 1000) {
x.second->weight += (1000 - sum);
+ }
}
}
}
+ }
+ catch (std::exception& ex) {
+ g_log << Logger::Error << ex.what() << endl;
+ return false;
+ }
+ catch (PDNSException& ex) {
+ g_log << Logger::Error << ex.reason << endl;
+ return false;
+ }
+ return true;
+}
- tmp_domains.push_back(std::move(dom));
+void GeoIPBackend::loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains)
+{
+ vector<std::filesystem::path> paths;
+ for (const std::filesystem::path& p : std::filesystem::directory_iterator(std::filesystem::path(dir))) {
+ if (std::filesystem::is_regular_file(p) && p.has_extension() && (p.extension() == ".yaml" || p.extension() == ".yml")) {
+ paths.push_back(p);
+ }
+ }
+ std::sort(paths.begin(), paths.end());
+ for (const auto& p : paths) {
+ try {
+ GeoIPDomain dom;
+ const auto& zoneRoot = YAML::LoadFile(p.string());
+ // expect zone key
+ const auto& zone = zoneRoot["zone"];
+ if (loadDomain(zone, domains.size(), dom)) {
+ domains.push_back(dom);
+ }
+ }
+ catch (std::exception& ex) {
+ g_log << Logger::Warning << "Cannot load zone from " << p << ": " << ex.what() << endl;
+ }
+ }
+}
+
+void GeoIPBackend::initialize()
+{
+ YAML::Node config;
+ vector<GeoIPDomain> tmp_domains;
+
+ s_geoip_files.clear(); // reset pointers
+
+ if (getArg("database-files").empty() == false) {
+ vector<string> files;
+ stringtok(files, getArg("database-files"), " ,\t\r\n");
+ for (auto const& file : files) {
+ s_geoip_files.push_back(GeoIPInterface::makeInterface(file));
+ }
+ }
+
+ if (s_geoip_files.empty()) {
+ g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;
+ }
+
+ if (!getArg("zones-file").empty()) {
+ try {
+ config = YAML::LoadFile(getArg("zones-file"));
+ }
+ catch (YAML::Exception& ex) {
+ throw PDNSException(string("Cannot read config file ") + ex.msg);
+ }
+ }
+
+ // Global lookup formats and mapping will be used
+ // if none defined at the domain level.
+ if (YAML::Node formats = config["mapping_lookup_formats"]) {
+ d_global_mapping_lookup_formats = formats.as<vector<string>>();
+ if (!validateMappingLookupFormats(d_global_mapping_lookup_formats)) {
+ throw PDNSException(string("%mp is not allowed in mapping lookup"));
+ }
+ }
+ if (YAML::Node mapping = config["custom_mapping"]) {
+ d_global_custom_mapping = mapping.as<map<std::string, std::string>>();
+ }
+
+ for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
+ GeoIPDomain dom;
+ if (loadDomain(*_domain, tmp_domains.size(), dom)) {
+ tmp_domains.push_back(std::move(dom));
+ }
+ }
+
+ if (YAML::Node domain_dir = config["zones_dir"]) {
+ loadDomainsFromDirectory(domain_dir.as<string>(), tmp_domains);
}
s_domains.clear();
GeoIPBackend::~GeoIPBackend()
{
try {
- WriteLock wl(&s_state_lock);
+ WriteLock writeLock(&s_state_lock);
s_rc--;
if (s_rc == 0) { // last instance gets to cleanup
s_geoip_files.clear();
break;
}
- if (!found || val.empty() || val == "--")
+ if (!found || val.empty() || val == "--") {
continue; // try next database
- ret = val;
+ }
+ ret = std::move(val);
std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
break;
}
#pragma once
#include "pdns/namespaces.hh"
+#include <cstdint>
#include <vector>
#include <map>
#include <string>
class GeoIPInterface;
+namespace YAML
+{
+class Node;
+};
+
struct GeoIPDomain;
struct GeoIPNetmask
{
public:
GeoIPBackend(const std::string& suffix = "");
- ~GeoIPBackend();
+ ~GeoIPBackend() override;
void lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p = nullptr) override;
bool list(const DNSName& /* target */, int /* domain_id */, bool /* include_disabled */ = false) override { return false; } // not supported
void initialize();
string format2str(string format, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom);
- bool d_dnssec;
+ bool d_dnssec{};
bool hasDNSSECkey(const DNSName& name);
bool lookup_static(const GeoIPDomain& dom, const DNSName& search, const QType& qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask& gl);
+ void setupNetmasks(const YAML::Node& domain, GeoIPDomain& dom);
+ bool loadDomain(const YAML::Node& domain, std::uint32_t domainID, GeoIPDomain& dom);
+ void loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains);
vector<DNSResourceRecord> d_result;
vector<GeoIPInterface> d_files;
+ std::vector<std::string> d_global_mapping_lookup_formats;
+ std::map<std::string, std::string> d_global_custom_mapping;
};
return false;
}
- ~GeoIPInterfaceDAT() {}
+ ~GeoIPInterfaceDAT() override = default;
private:
unsigned int d_db_type;
#else
-unique_ptr<GeoIPInterface> GeoIPInterface::makeDATInterface(const string& fname, const map<string, string>& opts)
+unique_ptr<GeoIPInterface> GeoIPInterface::makeDATInterface([[maybe_unused]] const string& fname, [[maybe_unused]] const map<string, string>& opts)
{
throw PDNSException("libGeoIP support not compiled in");
}
return true;
}
- ~GeoIPInterfaceMMDB() { MMDB_close(&d_s); };
+ ~GeoIPInterfaceMMDB() override { MMDB_close(&d_s); };
private:
MMDB_s d_s;
#else
-unique_ptr<GeoIPInterface> GeoIPInterface::makeMMDBInterface(const string& fname, const map<string, string>& opts)
+unique_ptr<GeoIPInterface> GeoIPInterface::makeMMDBInterface([[maybe_unused]] const string& fname, [[maybe_unused]] const map<string, string>& opts)
{
throw PDNSException("libmaxminddb support not compiled in");
}
boost::optional<int>& alt, boost::optional<int>& prec)
= 0;
- virtual ~GeoIPInterface() {}
+ virtual ~GeoIPInterface() = default;
static unique_ptr<GeoIPInterface> makeInterface(const string& dbStr);
void gMySQLBackend::reconnect()
{
- setDB(new SMySQL(getArg("dbname"),
- getArg("host"),
- getArgAsNum("port"),
- getArg("socket"),
- getArg("user"),
- getArg("password"),
- getArg("group"),
- mustDo("innodb-read-committed"),
- getArgAsNum("timeout"),
- mustDo("thread-cleanup"),
- mustDo("ssl")));
+ setDB(std::unique_ptr<SSql>(new SMySQL(getArg("dbname"),
+ getArg("host"),
+ getArgAsNum("port"),
+ getArg("socket"),
+ getArg("user"),
+ getArg("password"),
+ getArg("group"),
+ mustDo("innodb-read-committed"),
+ getArgAsNum("timeout"),
+ mustDo("thread-cleanup"))));
allocateStatements();
}
declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?");
- declare(suffix, "info-all-slaves-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
- declare(suffix, "supermaster-query", "", "select account from supermasters where ip=? and nameserver=?");
- declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
- declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
+ declare(suffix, "info-all-secondaries-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
+ declare(suffix, "autoprimary-query", "", "select account from supermasters where ip=? and nameserver=?");
+ declare(suffix, "autoprimary-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
+ declare(suffix, "autoprimary-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = ? and nameserver = ?");
declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and disabled=0");
declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and type=? and disabled=0");
- declare(suffix, "update-master-query", "", "update domains set master=? where name=?");
+ declare(suffix, "update-primary-query", "", "update domains set master=? where name=?");
declare(suffix, "update-kind-query", "", "update domains set type=? where name=?");
declare(suffix, "update-options-query", "", "update domains set options=? where name=?");
declare(suffix, "update-catalog-query", "", "update domains set catalog=? where name=?");
declare(suffix, "update-account-query", "", "update domains set account=? where name=?");
declare(suffix, "update-serial-query", "", "update domains set notified_serial=? where id=?");
declare(suffix, "update-lastcheck-query", "", "update domains set last_check=? where id=?");
- declare(suffix, "info-all-master-query", "", "select d.id, d.name, d.type, d.notified_serial,d.options, d.catalog,r.content from records r join domains d on r.domain_id=d.id and r.name=d.name where r.type='SOA' and r.disabled=0 and d.type in ('MASTER', 'PRODUCER')");
+ declare(suffix, "info-all-primary-query", "", "select d.id, d.name, d.type, d.notified_serial,d.options, d.catalog,r.content from records r join domains d on r.domain_id=d.id and r.name=d.name where r.type='SOA' and r.disabled=0 and d.type in ('MASTER', 'PRODUCER')");
declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0");
declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=?");
declare(suffix, "delete-domain-query", "", "delete from domains where name=?");
declare(suffix, "delete-tsig-key-query", "", "delete from tsigkeys where name=?");
declare(suffix, "get-tsig-keys-query", "", "select name,algorithm, secret from tsigkeys");
- declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ?");
+ declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ?");
declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=?");
declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?)");
}
}
- SSqlStatement* bind(const string& /* name */, bool value)
+ SSqlStatement* bind(const string& /* name */, bool value) override
{
prepareStatement();
if (d_paridx >= d_parnum) {
d_paridx++;
return this;
}
- SSqlStatement* bind(const string& name, int value)
+ SSqlStatement* bind(const string& name, int value) override
{
return bind(name, (long)value);
}
- SSqlStatement* bind(const string& name, uint32_t value)
+ SSqlStatement* bind(const string& name, uint32_t value) override
{
return bind(name, (unsigned long)value);
}
- SSqlStatement* bind(const string& /* name */, long value)
+ SSqlStatement* bind(const string& /* name */, long value) override
{
prepareStatement();
if (d_paridx >= d_parnum) {
d_paridx++;
return this;
}
- SSqlStatement* bind(const string& /* name */, unsigned long value)
+ SSqlStatement* bind(const string& /* name */, unsigned long value) override
{
prepareStatement();
if (d_paridx >= d_parnum) {
d_paridx++;
return this;
}
- SSqlStatement* bind(const string& /* name */, long long value)
+ SSqlStatement* bind(const string& /* name */, long long value) override
{
prepareStatement();
if (d_paridx >= d_parnum) {
d_paridx++;
return this;
}
- SSqlStatement* bind(const string& /* name */, unsigned long long value)
+ SSqlStatement* bind(const string& /* name */, unsigned long long value) override
{
prepareStatement();
if (d_paridx >= d_parnum) {
d_paridx++;
return this;
}
- SSqlStatement* bind(const string& /* name */, const std::string& value)
+ SSqlStatement* bind(const string& /* name */, const std::string& value) override
{
prepareStatement();
if (d_paridx >= d_parnum) {
d_paridx++;
return this;
}
- SSqlStatement* bindNull(const string& /* name */)
+ SSqlStatement* bindNull(const string& /* name */) override
{
prepareStatement();
if (d_paridx >= d_parnum) {
return this;
}
- SSqlStatement* execute()
+ SSqlStatement* execute() override
{
prepareStatement();
return this;
}
- bool hasNextRow()
+ bool hasNextRow() override
{
if (d_dolog && d_residx == d_resnum) {
g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us total to last row" << endl;
return d_residx < d_resnum;
}
- SSqlStatement* nextRow(row_t& row)
+ SSqlStatement* nextRow(row_t& row) override
{
int err;
row.clear();
return this;
}
- SSqlStatement* getResult(result_t& result)
+ SSqlStatement* getResult(result_t& result) override
{
result.clear();
result.reserve(d_resnum);
return this;
}
- SSqlStatement* reset()
+ SSqlStatement* reset() override
{
if (!d_stmt)
return this;
return this;
}
- const std::string& getQuery() { return d_query; }
+ const std::string& getQuery() override { return d_query; }
- ~SMySQLStatement()
+ ~SMySQLStatement() override
{
releaseStatement();
}
do {
-#if MYSQL_VERSION_ID >= 50013
- my_bool set_reconnect = 0;
- mysql_options(&d_db, MYSQL_OPT_RECONNECT, &set_reconnect);
-#endif
-
#if MYSQL_VERSION_ID >= 50100
if (d_timeout) {
mysql_options(&d_db, MYSQL_OPT_READ_TIMEOUT, &d_timeout);
mysql_options(&d_db, MYSQL_OPT_WRITE_TIMEOUT, &d_timeout);
+ mysql_options(&d_db, MYSQL_OPT_CONNECT_TIMEOUT, &d_timeout);
}
#endif
d_database.empty() ? nullptr : d_database.c_str(),
d_port,
d_msocket.empty() ? nullptr : d_msocket.c_str(),
- (d_clientSSL ? CLIENT_SSL : 0) | CLIENT_MULTI_RESULTS)) {
+ CLIENT_MULTI_RESULTS)) {
if (retry == 0)
throw sPerrorException("Unable to connect to database");
}
SMySQL::SMySQL(string database, string host, uint16_t port, string msocket, string user,
- string password, string group, bool setIsolation, unsigned int timeout, bool threadCleanup, bool clientSSL) :
- d_database(std::move(database)), d_host(std::move(host)), d_msocket(std::move(msocket)), d_user(std::move(user)), d_password(std::move(password)), d_group(std::move(group)), d_timeout(timeout), d_port(port), d_setIsolation(setIsolation), d_threadCleanup(threadCleanup), d_clientSSL(clientSSL)
+ string password, string group, bool setIsolation, unsigned int timeout, bool threadCleanup) :
+ d_database(std::move(database)), d_host(std::move(host)), d_msocket(std::move(msocket)), d_user(std::move(user)), d_password(std::move(password)), d_group(std::move(group)), d_timeout(timeout), d_port(port), d_setIsolation(setIsolation), d_threadCleanup(threadCleanup)
{
connect();
}
string msocket = "", string user = "",
string password = "", string group = "",
bool setIsolation = false, unsigned int timeout = 10,
- bool threadCleanup = false, bool clientSSL = false);
+ bool threadCleanup = false);
- ~SMySQL();
+ ~SMySQL() override;
SSqlException sPerrorException(const string& reason) override;
void setLog(bool state) override;
uint16_t d_port;
bool d_setIsolation;
bool d_threadCleanup;
- bool d_clientSSL;
};
GSQLBackend(mode, suffix)
{
try {
- setDB(new SODBC(getArg("datasource"), getArg("username"), getArg("password")));
+ setDB(std::unique_ptr<SSql>(new SODBC(getArg("datasource"), getArg("username"), getArg("password"))));
}
catch (SSqlException& e) {
g_log << Logger::Error << mode << " Connection failed: " << e.txtReason() << std::endl;
declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?");
- declare(suffix, "info-all-slaves-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
- declare(suffix, "supermaster-query", "", "select account from supermasters where ip=? and nameserver=?");
- declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
- declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
+ declare(suffix, "info-all-secondaries-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
+ declare(suffix, "autoprimary-query", "", "select account from supermasters where ip=? and nameserver=?");
+ declare(suffix, "autoprimary-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
+ declare(suffix, "autoprimary-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = ? and nameserver = ?");
declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and disabled=0");
declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and type=? and disabled=0");
- declare(suffix, "update-master-query", "", "update domains set master=? where name=?");
+ declare(suffix, "update-primary-query", "", "update domains set master=? where name=?");
declare(suffix, "update-kind-query", "", "update domains set type=? where name=?");
declare(suffix, "update-options-query", "", "update domains set options=? where name=?");
declare(suffix, "update-catalog-query", "", "update domains set catalog=? where name=?");
declare(suffix, "update-account-query", "", "update domains set account=? where name=?");
declare(suffix, "update-serial-query", "", "update domains set notified_serial=? where id=?");
declare(suffix, "update-lastcheck-query", "", "update domains set last_check=? where id=?");
- declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
+ declare(suffix, "info-all-primary-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0");
declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=?");
declare(suffix, "delete-domain-query", "", "delete from domains where name=?");
declare(suffix, "delete-tsig-key-query", "", "delete from tsigkeys where name=?");
declare(suffix, "get-tsig-keys-query", "", "select name,algorithm, secret from tsigkeys");
- declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR records.disabled=?");
+ declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR records.disabled=?");
declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=?");
declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?)");
return this;
}
- SSqlStatement* bind(const string& name, bool value)
+ SSqlStatement* bind(const string& name, bool value) override
{
prepareStatement();
return bind(name, (uint32_t)value);
}
- SSqlStatement* bind(const string& name, long value)
+ SSqlStatement* bind(const string& name, long value) override
{
prepareStatement();
return bind(name, (unsigned long)value);
}
- SSqlStatement* bind(const string& name, int value)
+ SSqlStatement* bind(const string& name, int value) override
{
prepareStatement();
return bind(name, (uint32_t)value);
}
- SSqlStatement* bind(const string& name, long long value)
+ SSqlStatement* bind(const string& name, long long value) override
{
prepareStatement();
return bind(name, (unsigned long long)value);
}
- SSqlStatement* bind(const string& name, uint32_t value)
+ SSqlStatement* bind(const string& name, uint32_t value) override
{
prepareStatement();
ODBCParam p;
return bind(name, p);
}
- SSqlStatement* bind(const string& name, unsigned long value)
+ SSqlStatement* bind(const string& name, unsigned long value) override
{
prepareStatement();
ODBCParam p;
return bind(name, p);
}
- SSqlStatement* bind(const string& name, unsigned long long value)
+ SSqlStatement* bind(const string& name, unsigned long long value) override
{
prepareStatement();
ODBCParam p;
return bind(name, p);
}
- SSqlStatement* bind(const string& name, const std::string& value)
+ SSqlStatement* bind(const string& name, const std::string& value) override
{
// cerr<<"asked to bind string "<<value<<endl;
return bind(name, p);
}
- SSqlStatement* bindNull(const string& name)
+ SSqlStatement* bindNull(const string& name) override
{
if (d_req_bind.size() > (d_parnum + 1))
throw SSqlException("Trying to bind too many parameters.");
return bind(name, p);
}
- SSqlStatement* execute()
+ SSqlStatement* execute() override
{
prepareStatement();
SQLRETURN result;
return this;
}
- bool hasNextRow()
+ bool hasNextRow() override
{
// cerr<<"hasNextRow d_result="<<d_result<<endl;
return d_result != SQL_NO_DATA;
}
- SSqlStatement* nextRow(row_t& row);
+ SSqlStatement* nextRow(row_t& row) override;
- SSqlStatement* getResult(result_t& result)
+ SSqlStatement* getResult(result_t& result) override
{
result.clear();
// if (d_res == NULL) return this;
return this;
}
- SSqlStatement* reset()
+ SSqlStatement* reset() override
{
SQLCloseCursor(d_statement); // hack, this probably violates some state transitions
d_paridx = 0;
return this;
}
- const std::string& getQuery() { return d_query; }
+ const std::string& getQuery() override { return d_query; }
- ~SODBCStatement()
+ ~SODBCStatement() override
{
releaseStatement();
}
}
// Destructor.
-SODBC::~SODBC(void)
+SODBC::~SODBC()
{
// Disconnect from database and free all used resources.
// SQLFreeHandle( SQL_HANDLE_STMT, m_statement );
const std::string& password);
//! Destructor.
- virtual ~SODBC(void);
+ ~SODBC() override;
//! Sets the logging state.
void setLog(bool state) override;
GSQLBackend(mode, suffix)
{
try {
- setDB(new SPgSQL(getArg("dbname"),
- getArg("host"),
- getArg("port"),
- getArg("user"),
- getArg("password"),
- getArg("extra-connection-parameters"),
- mustDo("prepared-statements")));
+ setDB(std::unique_ptr<SSql>(new SPgSQL(getArg("dbname"),
+ getArg("host"),
+ getArg("port"),
+ getArg("user"),
+ getArg("password"),
+ getArg("extra-connection-parameters"),
+ mustDo("prepared-statements"))));
}
catch (SSqlException& e) {
declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=$1");
- declare(suffix, "info-all-slaves-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
- declare(suffix, "supermaster-query", "", "select account from supermasters where ip=$1 and nameserver=$2");
- declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=$1 and account=$2");
- declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values ($1,$2,$3)");
+ declare(suffix, "info-all-secondaries-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
+ declare(suffix, "autoprimary-query", "", "select account from supermasters where ip=$1 and nameserver=$2");
+ declare(suffix, "autoprimary-name-to-ips", "", "select ip,account from supermasters where nameserver=$1 and account=$2");
+ declare(suffix, "autoprimary-add", "", "insert into supermasters (ip, nameserver, account) values ($1,$2,$3)");
declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = $1 and nameserver = $2");
declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=$1 where domain_id=$2 and name=$3 and disabled=false");
declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=$1 where domain_id=$2 and name=$3 and type=$4 and disabled=false");
- declare(suffix, "update-master-query", "", "update domains set master=$1 where name=$2");
+ declare(suffix, "update-primary-query", "", "update domains set master=$1 where name=$2");
declare(suffix, "update-kind-query", "", "update domains set type=$1 where name=$2");
declare(suffix, "update-options-query", "", "update domains set options=$1 where name=$2");
declare(suffix, "update-catalog-query", "", "update domains set catalog=$1 where name=$2");
declare(suffix, "update-account-query", "", "update domains set account=$1 where name=$2");
declare(suffix, "update-serial-query", "", "update domains set notified_serial=$1 where id=$2");
declare(suffix, "update-lastcheck-query", "", "update domains set last_check=$1 where id=$2");
- declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=false and domains.type in ('MASTER', 'PRODUCER')");
+ declare(suffix, "info-all-primary-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=false and domains.type in ('MASTER', 'PRODUCER')");
declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=$1 and records.type='SOA' and records.disabled=false");
declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=$1");
declare(suffix, "delete-domain-query", "", "delete from domains where name=$1");
declare(suffix, "delete-tsig-key-query", "", "delete from tsigkeys where name=$1");
declare(suffix, "get-tsig-keys-query", "", "select name,algorithm, secret from tsigkeys");
- declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=false OR $1");
+ declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=false OR $1");
declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=$1");
declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES ($1, $2, $3, $4, $5, $6)");
d_nstatement = nstatement;
}
- SSqlStatement* bind(const string& name, bool value) { return bind(name, string(value ? "t" : "f")); }
- SSqlStatement* bind(const string& name, int value) { return bind(name, std::to_string(value)); }
- SSqlStatement* bind(const string& name, uint32_t value) { return bind(name, std::to_string(value)); }
- SSqlStatement* bind(const string& name, long value) { return bind(name, std::to_string(value)); }
- SSqlStatement* bind(const string& name, unsigned long value) { return bind(name, std::to_string(value)); }
- SSqlStatement* bind(const string& name, long long value) { return bind(name, std::to_string(value)); }
- SSqlStatement* bind(const string& name, unsigned long long value) { return bind(name, std::to_string(value)); }
- SSqlStatement* bind(const string& /* name */, const std::string& value)
+ SSqlStatement* bind(const string& name, bool value) override { return bind(name, string(value ? "t" : "f")); }
+ SSqlStatement* bind(const string& name, int value) override { return bind(name, std::to_string(value)); }
+ SSqlStatement* bind(const string& name, uint32_t value) override { return bind(name, std::to_string(value)); }
+ SSqlStatement* bind(const string& name, long value) override { return bind(name, std::to_string(value)); }
+ SSqlStatement* bind(const string& name, unsigned long value) override { return bind(name, std::to_string(value)); }
+ SSqlStatement* bind(const string& name, long long value) override { return bind(name, std::to_string(value)); }
+ SSqlStatement* bind(const string& name, unsigned long long value) override { return bind(name, std::to_string(value)); }
+ SSqlStatement* bind(const string& /* name */, const std::string& value) override
{
prepareStatement();
allocate();
d_paridx++;
return this;
}
- SSqlStatement* bindNull(const string& /* name */)
+ SSqlStatement* bindNull(const string& /* name */) override
{
prepareStatement();
d_paridx++;
return this;
} // these are set null in allocate()
- SSqlStatement* execute()
+ SSqlStatement* execute() override
{
prepareStatement();
if (d_dolog) {
}
}
- bool hasNextRow()
+ bool hasNextRow() override
{
if (d_dolog && d_residx == d_resnum) {
g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiff() << " us total to last row" << endl;
return d_residx < d_resnum;
}
- SSqlStatement* nextRow(row_t& row)
+ SSqlStatement* nextRow(row_t& row) override
{
int i;
row.clear();
return this;
}
- SSqlStatement* getResult(result_t& result)
+ SSqlStatement* getResult(result_t& result) override
{
result.clear();
if (d_res == nullptr)
return this;
}
- SSqlStatement* reset()
+ SSqlStatement* reset() override
{
int i;
if (d_res) {
return this;
}
- const std::string& getQuery() { return d_query; }
+ const std::string& getQuery() override { return d_query; }
- ~SPgSQLStatement()
+ ~SPgSQLStatement() override
{
releaseStatement();
}
const string& user = "", const string& password = "",
const string& extra_connection_parameters = "", const bool use_prepared = true);
- ~SPgSQL();
+ ~SPgSQL() override;
SSqlException sPerrorException(const string& reason) override;
void setLog(bool state) override;
GSQLBackend(mode, suffix)
{
try {
- SSQLite3* ptr = new SSQLite3(getArg("database"), getArg("pragma-journal-mode"));
- setDB(ptr);
- allocateStatements();
+ auto ptr = std::unique_ptr<SSql>(new SSQLite3(getArg("database"), getArg("pragma-journal-mode")));
if (!getArg("pragma-synchronous").empty()) {
ptr->execute("PRAGMA synchronous=" + getArg("pragma-synchronous"));
}
if (mustDo("pragma-foreign-keys")) {
ptr->execute("PRAGMA foreign_keys = 1");
}
+ setDB(std::move(ptr));
+ allocateStatements();
}
catch (SSqlException& e) {
g_log << Logger::Error << mode << ": connection failed: " << e.txtReason() << std::endl;
declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=:domain");
- declare(suffix, "info-all-slaves-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
- declare(suffix, "supermaster-query", "", "select account from supermasters where ip=:ip and nameserver=:nameserver");
- declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=:nameserver and account=:account");
- declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (:ip,:nameserver,:account)");
+ declare(suffix, "info-all-secondaries-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
+ declare(suffix, "autoprimary-query", "", "select account from supermasters where ip=:ip and nameserver=:nameserver");
+ declare(suffix, "autoprimary-name-to-ips", "", "select ip,account from supermasters where nameserver=:nameserver and account=:account");
+ declare(suffix, "autoprimary-add", "", "insert into supermasters (ip, nameserver, account) values (:ip,:nameserver,:account)");
declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = :ip and nameserver = :nameserver");
declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
- declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check,notified_serial) values(:type, :domain, :masters, :account, null, null)");
+ declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check,notified_serial) values(:type, :domain, :primaries, :account, null, null)");
declare(suffix, "insert-record-query", "", "insert into records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (:content,:ttl,:priority,:qtype,:domain_id,:disabled,:qname,:ordername,:auth)");
declare(suffix, "insert-empty-non-terminal-order-query", "insert empty non-terminal in zone", "insert into records (type,domain_id,disabled,name,ordername,auth,ttl,prio,content) values (null,:domain_id,0,:qname,:ordername,:auth,null,null,null)");
declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=:auth where domain_id=:domain_id and name=:qname and disabled=0");
declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=:auth where domain_id=:domain_id and name=:qname and type=:qtype and disabled=0");
- declare(suffix, "update-master-query", "", "update domains set master=:master where name=:domain");
+ declare(suffix, "update-primary-query", "", "update domains set master=:master where name=:domain");
declare(suffix, "update-kind-query", "", "update domains set type=:kind where name=:domain");
declare(suffix, "update-options-query", "", "update domains set options=:options where name=:domain");
declare(suffix, "update-catalog-query", "", "update domains set catalog=:catalog where name=:domain");
declare(suffix, "update-account-query", "", "update domains set account=:account where name=:domain");
declare(suffix, "update-serial-query", "", "update domains set notified_serial=:serial where id=:domain_id");
declare(suffix, "update-lastcheck-query", "", "update domains set last_check=:last_check where id=:domain_id");
- declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
+ declare(suffix, "info-all-primary-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=:catalog and records.type='SOA' and records.disabled=0");
declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=:catalog");
declare(suffix, "delete-domain-query", "", "delete from domains where name=:domain");
declare(suffix, "delete-tsig-key-query", "", "delete from tsigkeys where name=:key_name");
declare(suffix, "get-tsig-keys-query", "", "select name,algorithm, secret from tsigkeys");
- declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR :include_disabled");
+ declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR :include_disabled");
declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=:domain_id");
declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (:domain_id, :qname, :qtype, :modified_at, :account, :content)");
ldapauthenticator.hh ldapauthenticator_p.hh ldapauthenticator.cc \
ldapbackend.cc ldapbackend.hh \
ldaputils.hh ldaputils.cc \
- master.cc \
native.cc \
powerldap.cc powerldap.hh \
+ primary.cc \
utils.hh
libldapbackend_la_LDFLAGS = -module -avoid-version
-ldapbackend.lo master.lo native.lo powerldap.lo ldaputils.lo ldapauthenticator.lo
+ldapbackend.lo native.lo powerldap.lo primary.lo ldaputils.lo ldapauthenticator.lo
class LdapAuthenticator
{
public:
- virtual ~LdapAuthenticator() {}
+ virtual ~LdapAuthenticator() = default;
virtual bool authenticate(LDAP* connection) = 0;
virtual std::string getError() const = 0;
};
public:
LdapSimpleAuthenticator(const std::string& dn, const std::string& pw, int timeout);
- virtual bool authenticate(LDAP* conn);
- virtual std::string getError() const;
+ bool authenticate(LDAP* conn) override;
+ std::string getError() const override;
};
class LdapGssapiAuthenticator : public LdapAuthenticator
public:
LdapGssapiAuthenticator(const std::string& keytab, const std::string& credsCache, int timeout);
- ~LdapGssapiAuthenticator();
- virtual bool authenticate(LDAP* conn);
- virtual std::string getError() const;
+ ~LdapGssapiAuthenticator() override;
+ bool authenticate(LDAP* conn) override;
+ std::string getError() const override;
};
public:
LdapBackend(const string& suffix = "");
- ~LdapBackend();
+ ~LdapBackend() override;
// Native backend
bool list(const DNSName& target, int domain_id, bool include_disabled = false) override;
bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial = true) override;
- // Master backend
- void getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+ // Primary backend
+ void getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
void setNotified(uint32_t id, uint32_t serial) override;
};
if (result.count("PdnsDomainMaster") && !result["PdnsDomainMaster"].empty()) {
for (const auto& m : result["PdnsDomainMaster"])
- di.masters.emplace_back(m, 53);
+ di.primaries.emplace_back(m, 53);
}
if (result.count("PdnsDomainType") && !result["PdnsDomainType"].empty()) {
string kind = result["PdnsDomainType"][0];
if (kind == "master")
- di.kind = DomainInfo::Master;
+ di.kind = DomainInfo::Primary;
else if (kind == "slave")
- di.kind = DomainInfo::Slave;
+ di.kind = DomainInfo::Secondary;
else
di.kind = DomainInfo::Native;
}
throw LDAPException("Starting LDAP search: " + getError(rc));
}
- return SearchResult::Ptr(new SearchResult(msgid, d_ld));
+ return std::make_unique<SearchResult>(msgid, d_ld);
}
/**
int d_msgid;
bool d_finished;
- SearchResult(const SearchResult& other);
- SearchResult& operator=(const SearchResult& other);
-
public:
typedef std::unique_ptr<SearchResult> Ptr;
+ SearchResult(const SearchResult& other) = delete;
+ SearchResult& operator=(const SearchResult& other) = delete;
+
SearchResult(int msgid, LDAP* ld);
~SearchResult();
#include "ldapbackend.hh"
#include <cstdlib>
-void LdapBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+void LdapBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
{
string filter;
PowerLDAP::SearchResult::Ptr search;
NULL};
try {
- // First get all domains on which we are master.
+ // First get all domains on which we are primary.
filter = strbind(":target:", "&(SOARecord=*)(PdnsDomainId=*)", getArg("filter-axfr"));
search = d_pldap->search(getArg("basedn"), LDAP_SCOPE_SUBTREE, filter, attronly);
}
catch (LDAPNoConnection& lnc) {
g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
if (reconnect())
- this->getUpdatedMasters(domains, catalogs, catalogHashes);
+ this->getUpdatedPrimaries(domains, catalogs, catalogHashes);
else
throw PDNSException("Failed to reconnect to LDAP server");
}
../../ext/lmdb-safe/lmdb-typed.hh ../../ext/lmdb-safe/lmdb-typed.cc \
lmdbbackend.cc lmdbbackend.hh
liblmdbbackend_la_LDFLAGS = -module -avoid-version $(BOOST_SERIALIZATION_LDFLAGS)
-liblmdbbackend_la_LIBADD = $(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS)
+liblmdbbackend_la_LIBADD = $(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS) $(SYSTEMD_LIBS)
-$(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS)
+$(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS) $(SYSTEMD_LIBS)
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "ext/lmdb-safe/lmdb-safe.hh"
#include <lmdb.h>
+#include <stdexcept>
#include <utility>
#ifdef HAVE_CONFIG_H
#include "config.h"
#include <boost/iostreams/device/back_inserter.hpp>
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
#include <stdio.h>
#include <unistd.h>
if ((rc = mdb_env_open(env, filename.c_str(), MDB_NOSUBDIR | MDB_RDONLY, 0600)) != 0) {
if (rc == ENOENT) {
// we don't have a database yet! report schema 0, with 0 shards
- return std::make_pair(0, 0);
+ return {0u, 0u};
}
mdb_env_close(env);
throw std::runtime_error("mdb_env_open failed");
// we pretend this means 5
mdb_txn_abort(txn);
mdb_env_close(env);
- return std::make_pair(5, 0);
+ return {5u, 0u};
}
mdb_txn_abort(txn);
mdb_env_close(env);
// we pretend this means 5
mdb_txn_abort(txn);
mdb_env_close(env);
- return std::make_pair(5, 0);
+ return {5u, 0u};
}
throw std::runtime_error("mdb_get pdns.schemaversion failed");
mdb_txn_abort(txn);
mdb_env_close(env);
- return std::make_pair(schemaversion, shards);
+ return {schemaversion, shards};
}
namespace
throw std::runtime_error("mdb_txn_begin failed");
}
+#ifdef HAVE_SYSTEMD
+ /* A schema migration may take a long time. Extend the startup service timeout to 1 day,
+ * but only if this is beyond the original maximum time of TimeoutStartSec=.
+ */
+ sd_notify(0, "EXTEND_TIMEOUT_USEC=86400000000");
+#endif
+
std::cerr << "migrating shards" << std::endl;
for (uint32_t i = 0; i < shards; i++) {
string shardfile = filename + "-" + std::to_string(i);
int index = 0;
- for (const std::string& dbname : {"domains", "keydata", "tsig", "metadata"}) {
+ for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
std::cerr << "migrating " << dbname << std::endl;
std::string tdbname = dbname + "_v5";
index = 0;
- for (const std::string& dbname : {"domains", "keydata", "tsig", "metadata"}) {
+ for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
std::string fdbname = dbname + "_0";
std::cerr << "migrating " << dbname << std::endl;
std::string tdbname = dbname + "_v5_0";
std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
- for (const std::string& keyname : {"schemaversion", "shards"}) {
+ for (const std::string keyname : {"schemaversion", "shards"}) {
cerr << "migrating pdns." << keyname << endl;
key.mv_data = (char*)keyname.c_str();
}
}
- for (const std::string& keyname : {"uuid"}) {
+ for (const std::string keyname : {"uuid"}) {
cerr << "migrating pdns." << keyname << endl;
key.mv_data = (char*)keyname.c_str();
d_asyncFlag = MDB_NOSYNC;
else if (syncMode == "nometasync")
d_asyncFlag = MDB_NOMETASYNC;
- else if (syncMode == "mapasync")
- d_asyncFlag = MDB_MAPASYNC;
else if (syncMode.empty() || syncMode == "sync")
d_asyncFlag = 0;
else
}
LMDBLS::s_flag_deleted = mustDo("flag-deleted");
+ d_handle_dups = false;
+
+ if (mustDo("lightning-stream")) {
+ d_random_ids = true;
+ d_handle_dups = true;
+ LMDBLS::s_flag_deleted = true;
+
+ if (atoi(getArg("shards").c_str()) != 1) {
+ throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
+ }
+ }
bool opened = false;
MDBOutVal shards;
if (!txn->get(pdnsdbi, "shards", shards)) {
s_shards = shards.get<uint32_t>();
+
+ if (mustDo("lightning-stream") && s_shards != 1) {
+ throw std::runtime_error(std::string("running with Lightning Stream support enabled requires a database with exactly 1 shard"));
+ }
+
if (s_shards != atoi(getArg("shards").c_str())) {
g_log << Logger::Warning << "Note: configured number of lmdb shards (" << atoi(getArg("shards").c_str()) << ") is different from on-disk (" << s_shards << "). Using on-disk shard number" << endl;
}
ar& g.zone;
ar& g.last_check;
ar& g.account;
- ar& g.masters;
+ ar& g.primaries;
ar& g.id;
ar& g.notified_serial;
ar& g.kind;
ar& g.zone;
ar& g.last_check;
ar& g.account;
- ar& g.masters;
+ ar& g.primaries;
ar& g.id;
ar& g.notified_serial;
ar& g.kind;
static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
{
- auto drc = DNSRecordContent::mastermake(qtype, QClass::IN, content);
+ auto drc = DNSRecordContent::make(qtype, QClass::IN, content);
return drc->serialize(domain, false);
}
if (!rrset.empty()) {
vector<LMDBResourceRecord> adjustedRRSet;
- for (auto rr : rrset) {
+ for (const auto& rr : rrset) {
LMDBResourceRecord lrr(rr);
lrr.content = serializeContent(lrr.qtype.getCode(), lrr.qname, lrr.content);
lrr.qname.makeUsRelative(di.zone);
return true;
}
+bool LMDBBackend::replaceComments([[maybe_unused]] const uint32_t domain_id, [[maybe_unused]] const DNSName& qname, [[maybe_unused]] const QType& qt, const vector<Comment>& comments)
+{
+ // if the vector is empty, good, that's what we do here (LMDB does not store comments)
+ // if it's not, report failure
+ return comments.empty();
+}
+
// tempting to templatize these two functions but the pain is not worth it
std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTransaction(uint32_t id)
{
return ret;
}
-std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(uint32_t id, std::shared_ptr<LMDBBackend::RecordsRWTransaction> rwtxn)
+std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(uint32_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn)
{
auto& shard = d_trecords[id % s_shards];
if (!shard.env) {
abortTransaction();
- uint32_t id;
+ LMDBIDvec idvec;
- { // get domain id
+ if (!d_handle_dups) {
+ // get domain id
auto txn = d_tdomains->getROTransaction();
DomainInfo di;
- id = txn.get<0>(domain, di);
+ idvec.push_back(txn.get<0>(domain, di));
}
+ else {
+ // this transaction used to be RO.
+ // it is now RW to narrow a race window between PowerDNS and Lightning Stream
+ // FIXME: turn the entire delete, including this ID scan, into one RW transaction
+ // when doing that, first do a short RO check to see if we actually have anything to delete
+ auto txn = d_tdomains->getRWTransaction();
+
+ txn.get_multi<0>(domain, idvec);
+ }
+
+ for (auto id : idvec) {
- startTransaction(domain, id);
+ startTransaction(domain, id);
- { // Remove metadata
- auto txn = d_tmeta->getRWTransaction();
- LMDBIDvec ids;
+ { // Remove metadata
+ auto txn = d_tmeta->getRWTransaction();
+ LMDBIDvec ids;
- txn.get_multi<0>(domain, ids);
+ txn.get_multi<0>(domain, ids);
- for (auto& _id : ids) {
- txn.del(_id);
+ for (auto& _id : ids) {
+ txn.del(_id);
+ }
+
+ txn.commit();
}
- txn.commit();
- }
+ { // Remove cryptokeys
+ auto txn = d_tkdb->getRWTransaction();
+ LMDBIDvec ids;
+ txn.get_multi<0>(domain, ids);
- { // Remove cryptokeys
- auto txn = d_tkdb->getRWTransaction();
- LMDBIDvec ids;
- txn.get_multi<0>(domain, ids);
+ for (auto _id : ids) {
+ txn.del(_id);
+ }
- for (auto _id : ids) {
- txn.del(_id);
+ txn.commit();
}
+ // Remove records
+ commitTransaction();
+
+ // Remove zone
+ auto txn = d_tdomains->getRWTransaction();
+ txn.del(id);
txn.commit();
}
- // Remove records
- commitTransaction();
startTransaction(transactionDomain, transactionDomainId);
- // Remove zone
- auto txn = d_tdomains->getRWTransaction();
- txn.del(id);
- txn.commit();
-
return true;
}
return true;
}
-int LMDBBackend::genChangeDomain(const DNSName& domain, std::function<void(DomainInfo&)> func)
+int LMDBBackend::genChangeDomain(const DNSName& domain, const std::function<void(DomainInfo&)>& func)
{
auto txn = d_tdomains->getRWTransaction();
return true;
}
-int LMDBBackend::genChangeDomain(uint32_t id, std::function<void(DomainInfo&)> func)
+int LMDBBackend::genChangeDomain(uint32_t id, const std::function<void(DomainInfo&)>& func)
{
DomainInfo di;
});
}
-bool LMDBBackend::setMasters(const DNSName& domain, const vector<ComboAddress>& masters)
+bool LMDBBackend::setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries)
{
- return genChangeDomain(domain, [&masters](DomainInfo& di) {
- di.masters = masters;
+ return genChangeDomain(domain, [&primaries](DomainInfo& di) {
+ di.primaries = primaries;
});
}
-bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& masters, const string& account)
+bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
{
DomainInfo di;
di.zone = domain;
di.kind = kind;
- di.masters = masters;
+ di.primaries = primaries;
di.account = account;
txn.put(di, 0, d_random_ids);
return true;
}
+void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
+{
+ auto txn = d_tdomains->getROTransaction();
+ if (d_handle_dups) {
+ map<DNSName, DomainInfo> zonemap;
+ set<DNSName> dups;
+
+ for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
+ DomainInfo di = *iter;
+ di.id = iter.getID();
+ di.backend = this;
+
+ if (!zonemap.emplace(di.zone, di).second) {
+ dups.insert(di.zone);
+ }
+ }
+
+ for (const auto& zone : dups) {
+ DomainInfo di;
+
+ // this get grabs the oldest item if there are duplicates
+ di.id = txn.get<0>(zone, di);
+
+ if (di.id == 0) {
+ // .get actually found nothing for us
+ continue;
+ }
+
+ di.backend = this;
+ zonemap[di.zone] = di;
+ }
+
+ for (auto& [k, v] : zonemap) {
+ if (allow(v)) {
+ domains->push_back(std::move(v));
+ }
+ }
+ }
+ else {
+ for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
+ DomainInfo di = *iter;
+ di.id = iter.getID();
+ di.backend = this;
+
+ if (allow(di)) {
+ domains->push_back(di);
+ }
+ }
+ }
+}
+
void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool /* doSerial */, bool include_disabled)
{
domains->clear();
- auto txn = d_tdomains->getROTransaction();
- for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
- // cerr<<"iter"<<endl;
- DomainInfo di = *iter;
- di.id = iter.getID();
+ getAllDomainsFiltered(domains, [this, include_disabled](DomainInfo& di) {
if (!getSerial(di) && !include_disabled) {
- continue;
+ return false;
}
- di.backend = this;
- domains->push_back(di);
- }
+ return true;
+ });
}
-void LMDBBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
{
uint32_t serial;
time_t now = time(0);
LMDBResourceRecord lrr;
soatimes st;
- auto txn = d_tdomains->getROTransaction();
- for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
- if (!iter->isSecondaryType()) {
- continue;
+ getAllDomainsFiltered(domains, [this, &lrr, &st, &now, &serial](DomainInfo& di) {
+ if (!di.isSecondaryType()) {
+ return false;
}
- auto txn2 = getRecordsROTransaction(iter.getID());
+ auto txn2 = getRecordsROTransaction(di.id);
compoundOrdername co;
MDBOutVal val;
- if (!txn2->txn->get(txn2->db->dbi, co(iter.getID(), g_rootdnsname, QType::SOA), val)) {
+ if (!txn2->txn->get(txn2->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
serFromString(val.get<string_view>(), lrr);
memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
- if ((time_t)(iter->last_check + ntohl(st.refresh)) > now) { // still fresh
- continue;
+ if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
+ return false;
}
serial = ntohl(st.serial);
}
serial = 0;
}
- DomainInfo di(*iter);
- di.id = iter.getID();
- di.serial = serial;
- di.backend = this;
-
- domains->emplace_back(di);
- }
+ return true;
+ });
}
void LMDBBackend::setStale(uint32_t domain_id)
});
}
-void LMDBBackend::getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
{
- DomainInfo di;
CatalogInfo ci;
- auto txn = d_tdomains->getROTransaction();
- for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
-
- if (!iter->isPrimaryType()) {
- continue;
+ getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes, &ci](DomainInfo& di) {
+ if (!di.isPrimaryType()) {
+ return false;
}
- if (iter->kind == DomainInfo::Producer) {
- catalogs.insert(iter->zone);
- catalogHashes[iter->zone].process("\0");
- continue; // Producer fresness check is performed elsewhere
+ if (di.kind == DomainInfo::Producer) {
+ catalogs.insert(di.zone);
+ catalogHashes[di.zone].process("\0");
+ return false; // Producer fresness check is performed elsewhere
}
- di = *iter;
- di.id = iter.getID();
-
- if (!iter->catalog.empty()) {
- ci.fromJson(iter->options, CatalogInfo::CatalogType::Producer);
+ if (!di.catalog.empty()) {
+ ci.fromJson(di.options, CatalogInfo::CatalogType::Producer);
ci.updateHash(catalogHashes, di);
}
if (getSerial(di) && di.serial != di.notified_serial) {
di.backend = this;
- updatedDomains.emplace_back(di);
+ return true;
}
- }
+
+ return false;
+ });
}
void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial)
});
}
+class getCatalogMembersReturnFalseException : std::runtime_error
+{
+public:
+ getCatalogMembersReturnFalseException() :
+ std::runtime_error("getCatalogMembers should return false") {}
+};
+
bool LMDBBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
{
- auto txn = d_tdomains->getROTransaction();
- for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
- if ((type == CatalogInfo::CatalogType::Producer && iter->kind != DomainInfo::Master) || (type == CatalogInfo::CatalogType::Consumer && iter->kind != DomainInfo::Slave) || iter->catalog != catalog) {
- continue;
- }
+ vector<DomainInfo> scratch;
+
+ try {
+ getAllDomainsFiltered(&scratch, [&catalog, &members, &type](DomainInfo& di) {
+ if ((type == CatalogInfo::CatalogType::Producer && di.kind != DomainInfo::Primary) || (type == CatalogInfo::CatalogType::Consumer && di.kind != DomainInfo::Secondary) || di.catalog != catalog) {
+ return false;
+ }
+
+ CatalogInfo ci;
+ ci.d_id = di.id;
+ ci.d_zone = di.zone;
+ ci.d_primaries = di.primaries;
+ try {
+ ci.fromJson(di.options, type);
+ }
+ catch (const std::runtime_error& e) {
+ g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << di.options << "' for zone '" << di.zone << "' is no valid JSON: " << e.what() << endl;
+ members.clear();
+ throw getCatalogMembersReturnFalseException();
+ }
+ members.emplace_back(ci);
- CatalogInfo ci;
- ci.d_id = iter->id;
- ci.d_zone = iter->zone;
- ci.d_primaries = iter->masters;
- try {
- ci.fromJson(iter->options, type);
- }
- catch (const std::runtime_error& e) {
- g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << iter->options << "' for zone '" << iter->zone << "' is no valid JSON: " << e.what() << endl;
- members.clear();
return false;
- }
- members.emplace_back(ci);
+ });
+ }
+ catch (const getCatalogMembersReturnFalseException& e) {
+ return false;
}
return true;
}
serFromString(val.get<StringView>(), lrrs);
bool changed = false;
vector<LMDBResourceRecord> newRRs;
- for (auto lrr : lrrs) {
+ for (auto& lrr : lrrs) {
lrr.qtype = co.getQType(key.getNoStripHeader<StringView>());
if (!needNSEC3 && qtype != QType::ANY) {
needNSEC3 = (lrr.ordername && QType(qtype) != lrr.qtype);
lrr.ordername = hasOrderName;
changed = true;
}
- newRRs.push_back(lrr);
+ newRRs.push_back(std::move(lrr));
}
if (changed) {
cursor.put(key, serToString(newRRs));
string LMDBBackend::directBackendCmd(const string& query)
{
- if (query == "info") {
- ostringstream ret;
+ ostringstream ret, usage;
+
+ usage << "info show some information about the database" << endl;
+ usage << "index check domains check zone<>ID indexes" << endl;
+ usage << "index refresh domains <ID> refresh index for zone with this ID" << endl;
+ usage << "index refresh-all domains refresh index for all zones with disconnected indexes" << endl;
+ vector<string> argv;
+ stringtok(argv, query);
+
+ if (argv.empty()) {
+ return usage.str();
+ }
+ string& cmd = argv[0];
+
+ if (cmd == "help") {
+ return usage.str();
+ }
+
+ if (cmd == "info") {
ret << "shards: " << s_shards << endl;
ret << "schemaversion: " << SCHEMAVERSION << endl;
return ret.str();
}
- else {
- return "unknown lmdbbackend command\n";
+
+ if (cmd == "index") {
+ if (argv.size() < 2) {
+ return "need an index subcommand\n";
+ }
+
+ string& subcmd = argv[1];
+
+ if (subcmd == "check" || subcmd == "refresh-all") {
+ bool refresh = false;
+
+ if (subcmd == "refresh-all") {
+ refresh = true;
+ }
+
+ if (argv.size() < 3) {
+ return "need an index name\n";
+ }
+
+ if (argv[2] != "domains") {
+ return "can only check the domains index\n";
+ }
+
+ vector<uint32_t> refreshQueue;
+
+ {
+ auto txn = d_tdomains->getROTransaction();
+
+ for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
+ DomainInfo di = *iter;
+
+ auto id = iter.getID();
+
+ LMDBIDvec ids;
+ txn.get_multi<0>(di.zone, ids);
+
+ if (ids.size() != 1) {
+ ret << "ID->zone index has " << id << "->" << di.zone << ", ";
+
+ if (ids.empty()) {
+ ret << "zone->ID index has no entry for " << di.zone << endl;
+ if (refresh) {
+ refreshQueue.push_back(id);
+ }
+ else {
+ ret << " suggested remedy: index refresh domains " << id << endl;
+ }
+ }
+ else {
+ // ids.size() > 1
+ ret << "zone->ID index has multiple entries for " << di.zone << ": ";
+ for (auto id_ : ids) {
+ ret << id_ << " ";
+ }
+ ret << endl;
+ }
+ }
+ }
+ }
+
+ if (refresh) {
+ for (const auto& id : refreshQueue) {
+ if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
+ ret << "refreshed " << id << endl;
+ }
+ else {
+ ret << "failed to refresh " << id << endl;
+ }
+ }
+ }
+ return ret.str();
+ }
+ if (subcmd == "refresh") {
+ // index refresh domains 12345
+ if (argv.size() < 4) {
+ return "usage: index refresh domains <ID>\n";
+ }
+
+ if (argv[2] != "domains") {
+ return "can only refresh in the domains index\n";
+ }
+
+ uint32_t id = 0;
+
+ try {
+ id = pdns::checked_stoi<uint32_t>(argv[3]);
+ }
+ catch (const std::out_of_range& e) {
+ return "ID out of range\n";
+ }
+
+ if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
+ ret << "refreshed" << endl;
+ }
+ else {
+ ret << "failed" << endl;
+ }
+ return ret.str();
+ }
}
+
+ return "unknown lmdbbackend command\n";
}
class LMDBFactory : public BackendFactory
void declareArguments(const string& suffix = "") override
{
declare(suffix, "filename", "Filename for lmdb", "./pdns.lmdb");
- declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, mapasync, sync", "mapasync");
+ declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, sync", "sync");
// there just is no room for more on 32 bit
declare(suffix, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 4) ? "2" : "64");
declare(suffix, "schema-version", "Maximum allowed schema version to run on this DB. If a lower version is found, auto update is performed", std::to_string(SCHEMAVERSION));
declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
declare(suffix, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
+ declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
}
DNSBackend* make(const string& suffix = "") override
{
bool list(const DNSName& target, int id, bool include_disabled) override;
bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getserial = true) override;
- bool createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& masters, const string& account) override;
+ bool createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account) override;
bool startTransaction(const DNSName& domain, int domain_id = -1) override;
bool commitTransaction() override;
bool feedEnts(int domain_id, map<DNSName, bool>& nonterm) override;
bool feedEnts3(int domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow) override;
bool replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset) override;
+ bool replaceComments(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<Comment>& comments) override;
void getAllDomains(vector<DomainInfo>* domains, bool doSerial, bool include_disabled) override;
void lookup(const QType& type, const DNSName& qdomain, int zoneId, DNSPacket* p = nullptr) override;
bool get(DNSZoneRecord& dzr) override;
// secondary support
- void getUnfreshSlaveInfos(vector<DomainInfo>* domains) override;
+ void getUnfreshSecondaryInfos(vector<DomainInfo>* domains) override;
void setStale(uint32_t domain_id) override;
void setFresh(uint32_t domain_id) override;
// primary support
- void getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+ void getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
void setNotified(uint32_t id, uint32_t serial) override;
// catalog zones
bool setOptions(const DNSName& domain, const std::string& options) override;
bool setCatalog(const DNSName& domain, const DNSName& options) override;
- bool setMasters(const DNSName& domain, const vector<ComboAddress>& masters) override;
+ bool setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries) override;
bool setKind(const DNSName& domain, const DomainInfo::DomainKind kind) override;
bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta) override;
bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override
bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after) override;
- virtual bool getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after) override;
+ bool getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after) override;
bool updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype = QType::ANY) override;
class LMDBResourceRecord : public DNSResourceRecord
{
public:
- LMDBResourceRecord() {}
+ LMDBResourceRecord() = default;
LMDBResourceRecord(const DNSResourceRecord& rr) :
DNSResourceRecord(rr), ordername(false) {}
shared_ptr<RecordsROTransaction> d_rotxn; // for lookup and list
shared_ptr<RecordsRWTransaction> d_rwtxn; // for feedrecord within begin/aborttransaction
std::shared_ptr<RecordsRWTransaction> getRecordsRWTransaction(uint32_t id);
- std::shared_ptr<RecordsROTransaction> getRecordsROTransaction(uint32_t id, std::shared_ptr<LMDBBackend::RecordsRWTransaction> rwtxn = nullptr);
- int genChangeDomain(const DNSName& domain, std::function<void(DomainInfo&)> func);
- int genChangeDomain(uint32_t id, std::function<void(DomainInfo&)> func);
+ std::shared_ptr<RecordsROTransaction> getRecordsROTransaction(uint32_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn = nullptr);
+ int genChangeDomain(const DNSName& domain, const std::function<void(DomainInfo&)>& func);
+ int genChangeDomain(uint32_t id, const std::function<void(DomainInfo&)>& func);
void deleteDomainRecords(RecordsRWTransaction& txn, uint32_t domain_id, uint16_t qtype = QType::ANY);
+ void getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow);
+
bool getSerial(DomainInfo& di);
bool upgradeToSchemav3();
uint32_t d_transactiondomainid;
bool d_dolog;
bool d_random_ids;
+ bool d_handle_dups;
DTime d_dtime; // used only for logging
};
loadFile(getArg("filename"));
}
- ~Lua2BackendAPIv2();
+ ~Lua2BackendAPIv2() override;
#define logCall(func, var) \
{ \
} \
}
- virtual void postPrepareContext() override
+ void postPrepareContext() override
{
AuthLua4::postPrepareContext();
}
- virtual void postLoad() override
+ void postLoad() override
{
f_lookup = d_lw->readVariable<boost::optional<lookup_call_t>>("dns_lookup").get_value_or(0);
f_list = d_lw->readVariable<boost::optional<list_call_t>>("dns_list").get_value_or(0);
else if (item.first == "last_check")
di.last_check = static_cast<time_t>(boost::get<long>(item.second));
else if (item.first == "masters")
- for (const auto& master : boost::get<vector<string>>(item.second))
- di.masters.push_back(ComboAddress(master, 53));
+ for (const auto& primary : boost::get<vector<string>>(item.second))
+ di.primaries.push_back(ComboAddress(primary, 53));
else if (item.first == "id")
di.id = static_cast<int>(boost::get<long>(item.second));
else if (item.first == "notified_serial")
private:
std::list<DNSResourceRecord> d_result;
- bool d_debug_log;
- bool d_dnssec;
+ bool d_debug_log{false};
+ bool d_dnssec{false};
lookup_call_t f_lookup;
list_call_t f_list;
if (eolPos != received.size() - 1) {
/* we have some data remaining after the first '\n', let's keep it for later */
- d_remaining.append(received, eolPos + 1, received.size() - eolPos - 1);
+ d_remaining = std::string(received, eolPos + 1, received.size() - eolPos - 1);
}
received.resize(eolPos);
if (connect(d_fd, (struct sockaddr*)&remote, sizeof(remote)) < 0)
unixDie("Unable to connect to remote '" + path + "' using UNIX domain socket");
- d_fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(d_fd, "r"), fclose);
+ d_fp = pdns::UniqueFilePtr(fdopen(d_fd, "r"));
}
void UnixRemote::send(const string& line)
#include <stdio.h>
#include <string>
+#include "pdns/misc.hh"
#include "pdns/namespaces.hh"
class CoRemote
{
public:
- virtual ~CoRemote() {}
+ virtual ~CoRemote() = default;
virtual void sendReceive(const string& send, string& receive) = 0;
virtual void receive(string& rcv) = 0;
virtual void send(const string& send) = 0;
{
public:
CoProcess(const string& command, int timeout = 0, int infd = 0, int outfd = 1);
- ~CoProcess();
+ ~CoProcess() override;
void sendReceive(const string& send, string& receive) override;
void receive(string& rcv) override;
void send(const string& send) override;
private:
int d_fd;
- std::unique_ptr<FILE, int (*)(FILE*)> d_fp{nullptr, fclose};
+ pdns::UniqueFilePtr d_fp{nullptr};
};
bool isUnixSocket(const string& fname);
// I think
}
-CoWrapper::~CoWrapper()
-{
-}
+CoWrapper::~CoWrapper() = default;
void CoWrapper::launch()
{
{
public:
PipeBackend(const string& suffix = "");
- ~PipeBackend();
+ ~PipeBackend() override;
void lookup(const QType&, const DNSName& qdomain, int zoneId, DNSPacket* p = nullptr) override;
bool list(const DNSName& target, int domain_id, bool include_disabled = false) override;
bool get(DNSResourceRecord& r) override;
AM_LDFLAGS = $(THREADFLAGS)
JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
+ARC4RANDOM_LIBS = $(top_builddir)/ext/arc4random/libarc4random.la
EXTRA_DIST = \
OBJECTFILES \
../../pdns/base32.cc \
../../pdns/base64.cc \
../../pdns/dns.hh ../../pdns/dns.cc \
- ../../pdns/dns_random_urandom.cc \
../../pdns/dnsbackend.hh ../../pdns/dnsbackend.cc \
../../pdns/dnslabeltext.cc \
../../pdns/dnsname.cc ../../pdns/dnsname.hh \
test-remotebackend-keys.hh \
test-remotebackend.cc
-remotebackend_http_test_LDADD = libtestremotebackend.la
+remotebackend_http_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
remotebackend_json_test_SOURCES = \
test-remotebackend-json.cc \
test-remotebackend-keys.hh \
test-remotebackend.cc
-remotebackend_json_test_LDADD = libtestremotebackend.la
+remotebackend_json_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
remotebackend_pipe_test_SOURCES = \
test-remotebackend-keys.hh \
test-remotebackend-pipe.cc \
test-remotebackend.cc
-remotebackend_pipe_test_LDADD = libtestremotebackend.la
+remotebackend_pipe_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
remotebackend_post_test_SOURCES = \
test-remotebackend-keys.hh \
test-remotebackend-post.cc \
test-remotebackend.cc
-remotebackend_post_test_LDADD = libtestremotebackend.la
+remotebackend_post_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
remotebackend_unix_test_SOURCES = \
test-remotebackend-keys.hh \
test-remotebackend-unix.cc \
test-remotebackend.cc
-remotebackend_unix_test_LDADD = libtestremotebackend.la
+remotebackend_unix_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
remotebackend_zeromq_test_SOURCES = \
test-remotebackend-keys.hh \
test-remotebackend-zeromq.cc \
test-remotebackend.cc
-remotebackend_zeromq_test_LDADD = libtestremotebackend.la
+remotebackend_zeromq_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
}
}
-HTTPConnector::~HTTPConnector() {}
+HTTPConnector::~HTTPConnector() = default;
void HTTPConnector::addUrlComponent(const Json& parameters, const string& element, std::stringstream& ss)
{
std::string sparam;
- if (parameters[element] != Json())
+ if (parameters[element] != Json()) {
ss << "/" << YaHTTP::Utility::encodeURL(asString(parameters[element]), false);
+ }
}
-std::string HTTPConnector::buildMemberListArgs(std::string prefix, const Json& args)
+std::string HTTPConnector::buildMemberListArgs(const std::string& prefix, const Json& args)
{
std::stringstream stream;
stream << prefix << "[" << YaHTTP::Utility::encodeURL(pair.first, false) << "]=";
}
else {
- stream << prefix << "[" << YaHTTP::Utility::encodeURL(pair.first, false) << "]=" << YaHTTP::Utility::encodeURL(this->asString(pair.second), false);
+ stream << prefix << "[" << YaHTTP::Utility::encodeURL(pair.first, false) << "]=" << YaHTTP::Utility::encodeURL(HTTPConnector::asString(pair.second), false);
}
stream << "&";
}
else if (method == "createSlaveDomain") {
addUrlComponent(parameters, "ip", ss);
addUrlComponent(parameters, "domain", ss);
- if (parameters["account"].is_null() == false && parameters["account"].is_string()) {
+ if (!parameters["account"].is_null() && parameters["account"].is_string()) {
req.POST()["account"] = parameters["account"].string_value();
}
req.preparePost();
req.body = out;
}
else {
- std::stringstream url, content;
+ std::stringstream url;
+ std::stringstream content;
// call url/method.suffix
url << d_url << "/" << input["method"].string_value() << d_url_suffix;
req.setup("POST", url.str());
int HTTPConnector::send_message(const Json& input)
{
- int rv, ec, fd;
+ int rv = 0;
+ int ec = 0;
+ int fd = 0;
std::vector<std::string> members;
std::string method;
// perform request
YaHTTP::Request req;
- if (d_post)
+ if (d_post) {
post_requestbuilder(input, req);
- else
+ }
+ else {
restful_requestbuilder(input["method"].string_value(), input["parameters"], req);
+ }
rv = -1;
req.headers["connection"] = "Keep-Alive"; // see if we can streamline requests (not needed, strictly speaking)
}
}
- if (rv == 1)
+ if (rv == 1) {
return rv;
+ }
this->d_socket.reset();
// connect using tcp
- struct addrinfo *gAddr, *gAddrPtr, hints;
+ struct addrinfo* gAddr = nullptr;
+ struct addrinfo* gAddrPtr = nullptr;
+ struct addrinfo hints
+ {
+ };
std::string sPort = std::to_string(d_port);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
// try to connect to each address.
gAddrPtr = gAddr;
- while (gAddrPtr) {
+ while (gAddrPtr != nullptr) {
try {
d_socket = std::make_unique<Socket>(gAddrPtr->ai_family, gAddrPtr->ai_socktype, gAddrPtr->ai_protocol);
d_addr.setSockaddr(gAddrPtr->ai_addr, gAddrPtr->ai_addrlen);
g_log << Logger::Error << "While writing to HTTP endpoint " << d_addr.toStringWithPort() << ": exception caught" << std::endl;
}
- if (rv > -1)
+ if (rv > -1) {
break;
+ }
d_socket.reset();
gAddrPtr = gAddrPtr->ai_next;
}
YaHTTP::AsyncResponseLoader arl;
YaHTTP::Response resp;
- if (d_socket == nullptr)
+ if (d_socket == nullptr) {
return -1; // cannot receive :(
+ }
char buffer[4096];
int rd = -1;
- time_t t0;
+ time_t t0 = 0;
arl.initialize(&resp);
try {
- t0 = time((time_t*)NULL);
- while (arl.ready() == false && (labs(time((time_t*)NULL) - t0) <= timeout)) {
+ t0 = time((time_t*)nullptr);
+ while (!arl.ready() && (labs(time((time_t*)nullptr) - t0) <= timeout)) {
rd = d_socket->readWithTimeout(buffer, sizeof(buffer), timeout);
- if (rd == 0)
+ if (rd == 0) {
throw NetworkError("EOF while reading");
- if (rd < 0)
+ }
+ if (rd < 0) {
throw NetworkError(std::string(strerror(rd)));
+ }
arl.feed(std::string(buffer, rd));
}
// timeout occurred.
- if (arl.ready() == false)
+ if (!arl.ready()) {
throw NetworkError("timeout");
+ }
}
catch (NetworkError& ne) {
d_socket.reset();
int rv = -1;
std::string err;
output = Json::parse(resp.body, err);
- if (output != nullptr)
+ if (output != nullptr) {
return resp.body.size();
+ }
g_log << Logger::Error << "Cannot parse JSON reply: " << err << endl;
return rv;
PipeConnector::~PipeConnector()
{
- int status;
+ int status = 0;
// just in case...
- if (d_pid == -1)
+ if (d_pid == -1) {
return;
+ }
- if (!waitpid(d_pid, &status, WNOHANG)) {
+ if (waitpid(d_pid, &status, WNOHANG) == 0) {
kill(d_pid, 9);
waitpid(d_pid, &status, 0);
}
- if (d_fd1[1]) {
+ if (d_fd1[1] != 0) {
close(d_fd1[1]);
}
}
void PipeConnector::launch()
{
// no relaunch
- if (d_pid > 0 && checkStatus())
+ if (d_pid > 0 && checkStatus()) {
return;
+ }
std::vector<std::string> v;
split(v, command, boost::is_any_of(" "));
std::vector<const char*> argv(v.size() + 1);
- argv[v.size()] = 0;
+ argv[v.size()] = nullptr;
- for (size_t n = 0; n < v.size(); n++)
+ for (size_t n = 0; n < v.size(); n++) {
argv[n] = v[n].c_str();
+ }
signal(SIGPIPE, SIG_IGN);
- if (access(argv[0], X_OK)) // check before fork so we can throw
+ if (access(argv[0], X_OK) != 0) { // check before fork so we can throw
throw PDNSException("Command '" + string(argv[0]) + "' cannot be executed: " + stringerror());
+ }
- if (pipe(d_fd1) < 0 || pipe(d_fd2) < 0)
+ if (pipe(d_fd1) < 0 || pipe(d_fd2) < 0) {
throw PDNSException("Unable to open pipe for coprocess: " + string(strerror(errno)));
+ }
- if ((d_pid = fork()) < 0)
+ if ((d_pid = fork()) < 0) {
throw PDNSException("Unable to fork for coprocess: " + stringerror());
- else if (d_pid > 0) { // parent speaking
+ }
+ if (d_pid > 0) { // parent speaking
close(d_fd1[0]);
setCloseOnExec(d_fd1[1]);
close(d_fd2[1]);
setCloseOnExec(d_fd2[0]);
- if (!(d_fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(d_fd2[0], "r"), fclose)))
+ if (!(d_fp = pdns::UniqueFilePtr(fdopen(d_fd2[0], "r")))) {
throw PDNSException("Unable to associate a file pointer with pipe: " + stringerror());
- if (d_timeout)
- setbuf(d_fp.get(), 0); // no buffering please, confuses poll
+ }
+ if (d_timeout != 0) {
+ setbuf(d_fp.get(), nullptr); // no buffering please, confuses poll
+ }
}
- else if (!d_pid) { // child
+ else if (d_pid == 0) { // child
signal(SIGCHLD, SIG_DFL); // silence a warning from perl
close(d_fd1[1]);
close(d_fd2[0]);
// stdin & stdout are now connected, fire up our coprocess!
- if (execv(argv[0], const_cast<char* const*>(argv.data())) < 0) // now what
+ if (execv(argv[0], const_cast<char* const*>(argv.data())) < 0) { // now what
exit(123);
+ }
/* not a lot we can do here. We shouldn't return because that will leave a forked process around.
no way to log this either - only thing we can do is make sure that our parent catches this soonest! */
this->send(msg);
msg = nullptr;
- if (this->recv(msg) == false) {
+ if (!this->recv(msg)) {
g_log << Logger::Error << "Failed to initialize coprocess" << std::endl;
}
}
line.append(1, '\n');
unsigned int sent = 0;
- int bytes;
+ int bytes = 0;
// writen routine - socket may not accept al data in one go
while (sent < line.size()) {
bytes = write(d_fd1[1], line.c_str() + sent, line.length() - sent);
- if (bytes < 0)
+ if (bytes < 0) {
throw PDNSException("Writing to coprocess failed: " + std::string(strerror(errno)));
+ }
sent += bytes;
}
while (1) {
receive.clear();
- if (d_timeout) {
+ if (d_timeout != 0) {
int ret = waitForData(fileno(d_fp.get()), 0, d_timeout * 1000);
- if (ret < 0)
+ if (ret < 0) {
throw PDNSException("Error waiting on data from coprocess: " + stringerror());
- if (!ret)
+ }
+ if (ret == 0) {
throw PDNSException("Timeout waiting for data from coprocess");
+ }
}
- if (!stringfgets(d_fp.get(), receive))
+ if (!stringfgets(d_fp.get(), receive)) {
throw PDNSException("Child closed pipe");
+ }
s_output.append(receive);
// see if it can be parsed
output = Json::parse(s_output, err);
- if (output != nullptr)
+ if (output != nullptr) {
return s_output.size();
+ }
}
return 0;
}
-bool PipeConnector::checkStatus()
+bool PipeConnector::checkStatus() const
{
- int status;
+ int status = 0;
int ret = waitpid(d_pid, &status, WNOHANG);
- if (ret < 0)
+ if (ret < 0) {
throw PDNSException("Unable to ascertain status of coprocess " + std::to_string(d_pid) + " from " + std::to_string(getpid()) + ": " + string(strerror(errno)));
- else if (ret) {
+ }
+ if (ret != 0) {
if (WIFEXITED(status)) {
int exitStatus = WEXITSTATUS(status);
throw PDNSException("Coprocess exited with code " + std::to_string(exitStatus));
int sig = WTERMSIG(status);
string reason = "CoProcess died on receiving signal " + std::to_string(sig);
#ifdef WCOREDUMP
- if (WCOREDUMP(status))
+ if (WCOREDUMP(status)) {
reason += ". Dumped core";
+ }
#endif
throw PDNSException(reason);
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <limits>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
if (value["result"] == Json()) {
throw PDNSException("No 'result' field in response from remote process");
}
- else if (value["result"].is_bool() && boolFromJson(value, "result", false) == false) {
+ if (value["result"].is_bool() && !boolFromJson(value, "result", false)) {
retval = false;
}
for (const auto& message : value["log"].array_items()) {
this->d_connstr = getArg("connection-string");
this->d_dnssec = mustDo("dnssec");
- this->d_index = -1;
- this->d_trxid = 0;
build();
}
-RemoteBackend::~RemoteBackend() {}
+RemoteBackend::~RemoteBackend() = default;
bool RemoteBackend::send(Json& value)
{
std::map<std::string, std::string> options;
// connstr is of format "type:options"
- size_t pos;
- pos = d_connstr.find_first_of(":");
- if (pos == std::string::npos)
+ size_t pos = 0;
+ pos = d_connstr.find_first_of(':');
+ if (pos == std::string::npos) {
throw PDNSException("Invalid connection string: malformed");
+ }
type = d_connstr.substr(0, pos);
opts = d_connstr.substr(pos + 1);
// find out some options and parse them while we're at it
for (const auto& opt : parts) {
- std::string key, val;
+ std::string key;
+ std::string val;
// make sure there is something else than air in the option...
- if (opt.find_first_not_of(" ") == std::string::npos)
+ if (opt.find_first_not_of(" ") == std::string::npos) {
continue;
+ }
// split it on '='. if not found, we treat it as "yes"
pos = opt.find_first_of("=");
key = opt.substr(0, pos);
val = opt.substr(pos + 1);
}
- options[key] = val;
+ options[key] = std::move(val);
}
// connectors know what they are doing
*/
void RemoteBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p)
{
- if (d_index != -1)
+ if (d_index != -1) {
throw PDNSException("Attempt to lookup while one running");
+ }
string localIP = "0.0.0.0";
string remoteIP = "0.0.0.0";
string realRemote = "0.0.0.0/0";
- if (pkt_p) {
+ if (pkt_p != nullptr) {
localIP = pkt_p->getLocal().toString();
realRemote = pkt_p->getRealRemote().toString();
remoteIP = pkt_p->getInnerRemote().toString();
{"method", "lookup"},
{"parameters", Json::object{{"qtype", qtype.toString()}, {"qname", qdomain.toString()}, {"remote", remoteIP}, {"local", localIP}, {"real-remote", realRemote}, {"zone-id", zoneId}}}};
- if (this->send(query) == false || this->recv(d_result) == false) {
+ if (!this->send(query) || !this->recv(d_result)) {
return;
}
// OK. we have result parameters in result. do not process empty result.
- if (d_result["result"].is_array() == false || d_result["result"].array_items().size() < 1)
+ if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
return;
+ }
d_index = 0;
}
bool RemoteBackend::list(const DNSName& target, int domain_id, bool include_disabled)
{
- if (d_index != -1)
+ if (d_index != -1) {
throw PDNSException("Attempt to lookup while one running");
+ }
Json query = Json::object{
{"method", "list"},
{"parameters", Json::object{{"zonename", target.toString()}, {"domain_id", domain_id}, {"include_disabled", include_disabled}}}};
- if (this->send(query) == false || this->recv(d_result) == false)
+ if (!this->send(query) || !this->recv(d_result)) {
return false;
- if (d_result["result"].is_array() == false || d_result["result"].array_items().size() < 1)
+ }
+ if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
return false;
+ }
d_index = 0;
return true;
bool RemoteBackend::get(DNSResourceRecord& rr)
{
- if (d_index == -1)
+ if (d_index == -1) {
return false;
+ }
rr.qtype = stringFromJson(d_result["result"][d_index], "qtype");
rr.qname = DNSName(stringFromJson(d_result["result"][d_index], "qname"));
rr.content = stringFromJson(d_result["result"][d_index], "content");
rr.ttl = d_result["result"][d_index]["ttl"].int_value();
rr.domain_id = intFromJson(d_result["result"][d_index], "domain_id", -1);
- if (d_dnssec)
- rr.auth = intFromJson(d_result["result"][d_index], "auth", 1);
- else
- rr.auth = 1;
+ if (d_dnssec) {
+ rr.auth = (intFromJson(d_result["result"][d_index], "auth", 1) != 0);
+ }
+ else {
+ rr.auth = true;
+ }
rr.scopeMask = d_result["result"][d_index]["scopeMask"].int_value();
d_index++;
bool RemoteBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "getBeforeAndAfterNamesAbsolute"},
{"parameters", Json::object{{"id", Json(static_cast<double>(id))}, {"qname", qname.toString()}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return false;
+ }
unhashed = DNSName(stringFromJson(answer["result"], "unhashed"));
before.clear();
after.clear();
- if (answer["result"]["before"] != Json())
+ if (answer["result"]["before"] != Json()) {
before = DNSName(stringFromJson(answer["result"], "before"));
- if (answer["result"]["after"] != Json())
+ }
+ if (answer["result"]["after"] != Json()) {
after = DNSName(stringFromJson(answer["result"], "after"));
+ }
return true;
}
{"method", "getAllDomainMetadata"},
{"parameters", Json::object{{"name", name.toString()}}}};
- if (this->send(query) == false)
+ if (!this->send(query)) {
return false;
+ }
meta.clear();
Json answer;
// not mandatory to implement
- if (this->recv(answer) == false)
+ if (!this->recv(answer)) {
return true;
+ }
for (const auto& pair : answer["result"].object_items()) {
if (pair.second.is_array()) {
- for (const auto& val : pair.second.array_items())
+ for (const auto& val : pair.second.array_items()) {
meta[pair.first].push_back(asString(val));
+ }
}
else {
meta[pair.first].push_back(asString(pair.second));
{"method", "getDomainMetadata"},
{"parameters", Json::object{{"name", name.toString()}, {"kind", kind}}}};
- if (this->send(query) == false)
+ if (!this->send(query)) {
return false;
+ }
meta.clear();
Json answer;
// not mandatory to implement
- if (this->recv(answer) == false)
+ if (!this->recv(answer)) {
return true;
+ }
if (answer["result"].is_array()) {
- for (const auto& row : answer["result"].array_items())
+ for (const auto& row : answer["result"].array_items()) {
meta.push_back(row.string_value());
+ }
}
else if (answer["result"].is_string()) {
meta.push_back(answer["result"].string_value());
{"parameters", Json::object{{"name", name.toString()}, {"kind", kind}, {"value", meta}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return false;
+ }
return boolFromJson(answer, "result", false);
}
bool RemoteBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "getDomainKeys"},
{"parameters", Json::object{{"name", name.toString()}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return false;
+ }
keys.clear();
bool RemoteBackend::removeDomainKey(const DNSName& name, unsigned int id)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "removeDomainKey"},
{"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
-
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "addDomainKey"},
{"parameters", Json::object{{"name", name.toString()}, {"key", Json::object{{"flags", static_cast<int>(key.flags)}, {"active", key.active}, {"published", key.published}, {"content", key.content}}}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return false;
+ }
id = answer["result"].int_value();
return id >= 0;
bool RemoteBackend::activateDomainKey(const DNSName& name, unsigned int id)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "activateDomainKey"},
{"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
-
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "deactivateDomainKey"},
{"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
-
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::publishDomainKey(const DNSName& name, unsigned int id)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "publishDomainKey"},
{"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
-
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "unpublishDomainKey"},
{"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
-
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::doesDNSSEC()
bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, std::string& content)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "getTSIGKey"},
{"parameters", Json::object{{"name", name.toString()}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return false;
+ }
algorithm = DNSName(stringFromJson(answer["result"], "algorithm"));
content = stringFromJson(answer["result"], "content");
bool RemoteBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const std::string& content)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "setTSIGKey"},
{"parameters", Json::object{{"name", name.toString()}, {"algorithm", algorithm.toString()}, {"content", content}}}};
Json answer;
- if (connector->send(query) == false || connector->recv(answer) == false)
- return false;
-
- return true;
+ return connector->send(query) && connector->recv(answer);
}
bool RemoteBackend::deleteTSIGKey(const DNSName& name)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "deleteTSIGKey"},
{"parameters", Json::object{{"name", name.toString()}}}};
Json answer;
- if (connector->send(query) == false || connector->recv(answer) == false)
- return false;
-
- return true;
+ return connector->send(query) && connector->recv(answer);
}
bool RemoteBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
{
// no point doing dnssec if it's not supported
- if (d_dnssec == false)
+ if (!d_dnssec) {
return false;
+ }
Json query = Json::object{
{"method", "getTSIGKeys"},
{"parameters", Json::object{}}};
Json answer;
- if (connector->send(query) == false || connector->recv(answer) == false)
+ if (!connector->send(query) || !connector->recv(answer)) {
return false;
+ }
for (const auto& jsonKey : answer["result"].array_items()) {
struct TSIGKey key;
{
di.id = intFromJson(obj, "id", -1);
di.zone = DNSName(stringFromJson(obj, "zone"));
- for (const auto& master : obj["masters"].array_items())
- di.masters.push_back(ComboAddress(master.string_value(), 53));
+ for (const auto& primary : obj["masters"].array_items()) {
+ di.primaries.emplace_back(primary.string_value(), 53);
+ }
di.notified_serial = static_cast<unsigned int>(doubleFromJson(obj, "notified_serial", 0));
di.serial = static_cast<unsigned int>(obj["serial"].number_value());
di.last_check = static_cast<time_t>(obj["last_check"].number_value());
- string kind = "";
+ string kind;
if (obj["kind"].is_string()) {
kind = stringFromJson(obj, "kind");
}
if (kind == "master") {
- di.kind = DomainInfo::Master;
+ di.kind = DomainInfo::Primary;
}
else if (kind == "slave") {
- di.kind = DomainInfo::Slave;
+ di.kind = DomainInfo::Secondary;
}
else {
di.kind = DomainInfo::Native;
bool RemoteBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */)
{
- if (domain.empty())
+ if (domain.empty()) {
return false;
+ }
+
Json query = Json::object{
{"method", "getDomainInfo"},
{"parameters", Json::object{{"name", domain.toString()}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return false;
+ }
this->parseDomainInfo(answer["result"], di);
return true;
{"parameters", Json::object{{"id", static_cast<double>(id)}, {"serial", static_cast<double>(serial)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false) {
+ if (!this->send(query) || !this->recv(answer)) {
g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setNotified(" << id << "," << serial << ")" << endl;
}
}
-bool RemoteBackend::superMasterBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
+bool RemoteBackend::autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
{
Json::array rrset;
{"method", "superMasterBackend"},
{"parameters", Json::object{{"ip", ip}, {"domain", domain.toString()}, {"nsset", rrset}}}};
- *ddb = 0;
+ *ddb = nullptr;
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return false;
+ }
// we are the backend
*ddb = this;
return true;
}
-bool RemoteBackend::createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
+bool RemoteBackend::createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
{
Json query = Json::object{
{"method", "createSlaveDomain"},
}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qtype, const vector<DNSResourceRecord>& rrset)
{"parameters", Json::object{{"domain_id", static_cast<double>(domain_id)}, {"qname", qname.toString()}, {"qtype", qtype.toString()}, {"trxid", static_cast<double>(d_trxid)}, {"rrset", json_rrset}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
-
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
- return true; // XXX FIXME this API should not return 'true' I think -ahu
+ return this->send(query) && this->recv(answer); // XXX FIXME this API should not return 'true' I think -ahu
}
bool RemoteBackend::feedEnts(int domain_id, map<DNSName, bool>& nonterm)
{
Json::array nts;
- for (const auto& t : nonterm)
+ for (const auto& t : nonterm) {
nts.push_back(Json::object{
{"nonterm", t.first.toString()},
{"auth", t.second}});
+ }
Json query = Json::object{
{"method", "feedEnts"},
};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::feedEnts3(int domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
{
Json::array nts;
- for (const auto& t : nonterm)
+ for (const auto& t : nonterm) {
nts.push_back(Json::object{
{"nonterm", t.first.toString()},
{"auth", t.second}});
+ }
Json query = Json::object{
{"method", "feedEnts3"},
};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::startTransaction(const DNSName& domain, int domain_id)
{
- this->d_trxid = time((time_t*)NULL);
+ this->d_trxid = time((time_t*)nullptr);
Json query = Json::object{
{"method", "startTransaction"},
{"parameters", Json::object{{"domain", domain.toString()}, {"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false) {
+ if (!this->send(query) || !this->recv(answer)) {
d_trxid = -1;
return false;
}
return true;
}
+
bool RemoteBackend::commitTransaction()
{
- if (d_trxid == -1)
+ if (d_trxid == -1) {
return false;
+ }
Json query = Json::object{
{"method", "commitTransaction"},
d_trxid = -1;
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
- return true;
+ return this->send(query) && this->recv(answer);
}
bool RemoteBackend::abortTransaction()
{
- if (d_trxid == -1)
+ if (d_trxid == -1) {
return false;
+ }
Json query = Json::object{
{"method", "abortTransaction"},
d_trxid = -1;
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
- return false;
- return true;
+ return this->send(query) && this->recv(answer);
}
string RemoteBackend::directBackendCmd(const string& querystr)
{"parameters", Json::object{{"query", querystr}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return "backend command failed";
+ }
return asString(answer["result"]);
}
-bool RemoteBackend::searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result)
+bool RemoteBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
{
+ const auto intMax = static_cast<decltype(maxResults)>(std::numeric_limits<int>::max());
+ if (maxResults > intMax) {
+ throw std::out_of_range("Remote backend: length of list of result (" + std::to_string(maxResults) + ") is larger than what the JSON library supports for serialization (" + std::to_string(intMax) + ")");
+ }
+
Json query = Json::object{
{"method", "searchRecords"},
- {"parameters", Json::object{{"pattern", pattern}, {"maxResults", maxResults}}}};
+ {"parameters", Json::object{{"pattern", pattern}, {"maxResults", static_cast<int>(maxResults)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return false;
+ }
- if (answer["result"].is_array() == false)
+ if (!answer["result"].is_array()) {
return false;
+ }
for (const auto& row : answer["result"].array_items()) {
DNSResourceRecord rr;
rr.content = stringFromJson(row, "content");
rr.ttl = row["ttl"].int_value();
rr.domain_id = intFromJson(row, "domain_id", -1);
- if (d_dnssec)
- rr.auth = intFromJson(row, "auth", 1);
- else
+ if (d_dnssec) {
+ rr.auth = (intFromJson(row, "auth", 1) != 0);
+ }
+ else {
rr.auth = 1;
+ }
rr.scopeMask = row["scopeMask"].int_value();
result.push_back(rr);
}
return true;
}
-bool RemoteBackend::searchComments(const string& /* pattern */, int /* maxResults */, vector<Comment>& /* result */)
+bool RemoteBackend::searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
{
// FIXME: Implement Comment API
return false;
{"parameters", Json::object{{"include_disabled", include_disabled}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return;
+ }
- if (answer["result"].is_array() == false)
+ if (!answer["result"].is_array()) {
return;
+ }
for (const auto& row : answer["result"].array_items()) {
DomainInfo di;
}
}
-void RemoteBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
+void RemoteBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
{
Json query = Json::object{
{"method", "getUpdatedMasters"},
};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return;
+ }
- if (answer["result"].is_array() == false)
+ if (!answer["result"].is_array()) {
return;
+ }
for (const auto& row : answer["result"].array_items()) {
DomainInfo di;
}
}
-void RemoteBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+void RemoteBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
{
Json query = Json::object{
{"method", "getUnfreshSlaveInfos"},
};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false)
+ if (!this->send(query) || !this->recv(answer)) {
return;
+ }
- if (answer["result"].is_array() == false)
+ if (!answer["result"].is_array()) {
return;
+ }
for (const auto& row : answer["result"].array_items()) {
DomainInfo di;
{"parameters", Json::object{{"id", static_cast<double>(domain_id)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false) {
+ if (!this->send(query) || !this->recv(answer)) {
g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setStale(" << domain_id << ")" << endl;
}
}
{"parameters", Json::object{{"id", static_cast<double>(domain_id)}}}};
Json answer;
- if (this->send(query) == false || this->recv(answer) == false) {
+ if (!this->send(query) || !this->recv(answer)) {
g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setFresh(" << domain_id << ")" << endl;
}
}
}
catch (...) {
g_log << Logger::Error << kBackendId << " Unable to instantiate a remotebackend!" << endl;
- return 0;
+ return nullptr;
};
}
class Connector
{
public:
- virtual ~Connector(){};
+ virtual ~Connector() = default;
bool send(Json& value);
bool recv(Json& value);
virtual int send_message(const Json& input) = 0;
virtual int recv_message(Json& output) = 0;
protected:
- string asString(const Json& value)
+ static string asString(const Json& value)
{
- if (value.is_number())
+ if (value.is_number()) {
return std::to_string(value.int_value());
- if (value.is_bool())
+ }
+ if (value.is_bool()) {
return (value.bool_value() ? "1" : "0");
- if (value.is_string())
+ }
+ if (value.is_string()) {
return value.string_value();
+ }
throw JsonException("Json value not convertible to String");
};
};
{
public:
UnixsocketConnector(std::map<std::string, std::string> options);
- virtual ~UnixsocketConnector();
- virtual int send_message(const Json& input);
- virtual int recv_message(Json& output);
+ ~UnixsocketConnector() override;
+ int send_message(const Json& input) override;
+ int recv_message(Json& output) override;
private:
ssize_t read(std::string& data);
{
public:
HTTPConnector(std::map<std::string, std::string> options);
- ~HTTPConnector();
+ ~HTTPConnector() override;
- virtual int send_message(const Json& input);
- virtual int recv_message(Json& output);
+ int send_message(const Json& input) override;
+ int recv_message(Json& output) override;
private:
std::string d_url;
bool d_post_json;
void restful_requestbuilder(const std::string& method, const Json& parameters, YaHTTP::Request& req);
void post_requestbuilder(const Json& input, YaHTTP::Request& req);
- void addUrlComponent(const Json& parameters, const string& element, std::stringstream& ss);
- std::string buildMemberListArgs(std::string prefix, const Json& args);
+ static void addUrlComponent(const Json& parameters, const string& element, std::stringstream& ss);
+ static std::string buildMemberListArgs(const std::string& prefix, const Json& args);
std::unique_ptr<Socket> d_socket;
ComboAddress d_addr;
std::string d_host;
{
public:
ZeroMQConnector(std::map<std::string, std::string> options);
- virtual ~ZeroMQConnector();
- virtual int send_message(const Json& input);
- virtual int recv_message(Json& output);
+ ~ZeroMQConnector() override;
+ int send_message(const Json& input) override;
+ int recv_message(Json& output) override;
private:
void connect();
std::string d_endpoint;
int d_timeout;
- int d_timespent;
+ int d_timespent{0};
std::map<std::string, std::string> d_options;
std::unique_ptr<void, int (*)(void*)> d_ctx;
std::unique_ptr<void, int (*)(void*)> d_sock;
{
public:
PipeConnector(std::map<std::string, std::string> options);
- ~PipeConnector();
+ ~PipeConnector() override;
- virtual int send_message(const Json& input);
- virtual int recv_message(Json& output);
+ int send_message(const Json& input) override;
+ int recv_message(Json& output) override;
private:
void launch();
- bool checkStatus();
+ [[nodiscard]] bool checkStatus() const;
std::string command;
std::map<std::string, std::string> options;
- int d_fd1[2], d_fd2[2];
+ int d_fd1[2]{}, d_fd2[2]{};
int d_pid;
int d_timeout;
- std::unique_ptr<FILE, int (*)(FILE*)> d_fp{nullptr, fclose};
+ pdns::UniqueFilePtr d_fp{nullptr};
};
class RemoteBackend : public DNSBackend
{
public:
RemoteBackend(const std::string& suffix = "");
- ~RemoteBackend();
+ ~RemoteBackend() override;
void lookup(const QType& qtype, const DNSName& qdomain, int zoneId = -1, DNSPacket* pkt_p = nullptr) override;
bool get(DNSResourceRecord& rr) override;
bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial = true) override;
void setNotified(uint32_t id, uint32_t serial) override;
bool doesDNSSEC() override;
- bool superMasterBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb) override;
- bool createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
+ bool autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb) override;
+ bool createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
bool replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset) override;
bool feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool ordernameIsNSEC3 = false) override;
bool feedEnts(int domain_id, map<DNSName, bool>& nonterm) override;
bool deleteTSIGKey(const DNSName& name) override;
bool getTSIGKeys(std::vector<struct TSIGKey>& keys) override;
string directBackendCmd(const string& querystr) override;
- bool searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result) override;
- bool searchComments(const string& pattern, int maxResults, vector<Comment>& result) override;
+ bool searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result) override;
+ bool searchComments(const string& pattern, size_t maxResults, vector<Comment>& result) override;
void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled) override;
- void getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
- void getUnfreshSlaveInfos(vector<DomainInfo>* domains) override;
+ void getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+ void getUnfreshSecondaryInfos(vector<DomainInfo>* domains) override;
void setStale(uint32_t domain_id) override;
void setFresh(uint32_t domain_id) override;
std::unique_ptr<Connector> connector;
bool d_dnssec;
Json d_result;
- int d_index;
- int64_t d_trxid;
+ int d_index{-1};
+ int64_t d_trxid{0};
std::string d_connstr;
bool send(Json& value);
bool recv(Json& value);
- void makeErrorAndThrow(Json& value);
+ static void makeErrorAndThrow(Json& value);
- string asString(const Json& value)
+ static string asString(const Json& value)
{
- if (value.is_number())
+ if (value.is_number()) {
return std::to_string(value.int_value());
- if (value.is_bool())
+ }
+ if (value.is_bool()) {
return (value.bool_value() ? "1" : "0");
- if (value.is_string())
+ }
+ if (value.is_string()) {
return value.string_value();
+ }
throw JsonException("Json value not convertible to String");
};
- bool asBool(const Json& value)
+ static bool asBool(const Json& value)
{
- if (value.is_bool())
+ if (value.is_bool()) {
return value.bool_value();
+ }
try {
string val = asString(value);
- if (val == "0")
+ if (val == "0") {
return false;
- if (val == "1")
+ }
+ if (val == "1") {
return true;
+ }
}
catch (const JsonException&) {
};
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <memory>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
RemoteLoader();
};
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_MAIN
#define BOOST_TEST_MODULE unit
{
RemotebackendSetup()
{
- be = 0;
+ backendUnderTest = nullptr;
try {
// setup minimum arguments
::arg().set("module-dir") = "./.libs";
// then get us a instance of it
::arg().set("remote-connection-string") = "http:url=http://localhost:62434/dns";
::arg().set("remote-dnssec") = "yes";
- be = BackendMakers().all()[0];
+ backendUnderTest = std::move(BackendMakers().all()[0]);
}
catch (PDNSException& ex) {
BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
};
}
- ~RemotebackendSetup() {}
+ ~RemotebackendSetup() = default;
};
BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
RemoteLoader();
};
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_MAIN
#define BOOST_TEST_MODULE unit
{
RemotebackendSetup()
{
- be = 0;
+ backendUnderTest = nullptr;
try {
// setup minimum arguments
::arg().set("module-dir") = "./.libs";
// then get us a instance of it
::arg().set("remote-connection-string") = "http:url=http://localhost:62434/dns/endpoint.json,post=1,post_json=1";
::arg().set("remote-dnssec") = "yes";
- be = BackendMakers().all()[0];
+ backendUnderTest = std::move(BackendMakers().all()[0]);
}
catch (PDNSException& ex) {
BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
};
}
- ~RemotebackendSetup() {}
+ ~RemotebackendSetup() = default;
};
BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <string>
+#include <pdns/dnsbackend.hh>
+
DNSBackend::KeyData k1 = {std::string("Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\nModulus: qpe9fxlN4dBT38cLPWtqljZhcJjbqRprj9XsYmf2/uFu4kA5sHYrlQY7H9lpzGJPRfOAfxShBpKs1AVaVInfJQ==\nPublicExponent: AQAB\nPrivateExponent: Ad3YogzXvVDLsWuAfioY571QlolbdTbzVlhLEMLD6dSRx+xcZgw6c27ak2HAH00iSKTvqK3AyeaK8Eqy/oJ5QQ==\nPrime1: wo8LZrdU2y0xLGCeLhwziQDDtTMi18NEIwlx8tUPnhs=\nPrime2: 4HcuFqgo7NOiXFvN+V2PT+QaIt2+oi6D2m8/qtTDS78=\nExponent1: GUdCoPbi9JM7l1t6Ud1iKMPLqchaF5SMTs0UXAuous8=\nExponent2: nzgKqimX9f1corTAEw0pddrwKyEtcu8ZuhzFhZCsAxM=\nCoefficient: YGNxbulf5GTNiIu0oNKmAF0khNtx9layjOPEI0R4/RY="), 1, 257, true, true};
DNSBackend::KeyData k2 = {std::string("Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\nModulus: tY2TAMgL/whZdSbn2aci4wcMqohO24KQAaq5RlTRwQ33M8FYdW5fZ3DMdMsSLQUkjGnKJPKEdN3Qd4Z5b18f+w==\nPublicExponent: AQAB\nPrivateExponent: BB6xibPNPrBV0PUp3CQq0OdFpk9v9EZ2NiBFrA7osG5mGIZICqgOx/zlHiHKmX4OLmL28oU7jPKgogeuONXJQQ==\nPrime1: yjxe/iHQ4IBWpvCmuGqhxApWF+DY9LADIP7bM3Ejf3M=\nPrime2: 5dGWTyYEQRBVK74q1a64iXgaNuYm1pbClvvZ6ccCq1k=\nExponent1: TwM5RebmWeAqerzJFoIqw5IaQugJO8hM4KZR9A4/BTs=\nExponent2: bpV2HSmu3Fvuj7jWxbFoDIXlH0uJnrI2eg4/4hSnvSk=\nCoefficient: e2uDDWN2zXwYa2P6VQBWQ4mR1ZZjFEtO/+YqOJZun1Y="), 2, 256, true, true};
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_MAIN
#define BOOST_TEST_MODULE unit
RemoteLoader();
};
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
struct RemotebackendSetup
{
RemotebackendSetup()
{
- be = 0;
+ backendUnderTest = nullptr;
try {
// setup minimum arguments
::arg().set("module-dir") = "./.libs";
// then get us a instance of it
::arg().set("remote-connection-string") = "pipe:command=unittest_pipe.rb";
::arg().set("remote-dnssec") = "yes";
- be = BackendMakers().all()[0];
+ backendUnderTest = std::move(BackendMakers().all()[0]);
// load few record types to help out
SOARecordContent::report();
NSRecordContent::report();
BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
};
}
- ~RemotebackendSetup() {}
+ ~RemotebackendSetup() = default;
};
BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
RemoteLoader();
};
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_MAIN
#define BOOST_TEST_MODULE unit
{
RemotebackendSetup()
{
- be = 0;
+ backendUnderTest = nullptr;
try {
// setup minimum arguments
::arg().set("module-dir") = "./.libs";
// then get us a instance of it
::arg().set("remote-connection-string") = "http:url=http://localhost:62434/dns,post=1";
::arg().set("remote-dnssec") = "yes";
- be = BackendMakers().all()[0];
+ backendUnderTest = std::move(BackendMakers().all()[0]);
}
catch (PDNSException& ex) {
BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
};
}
- ~RemotebackendSetup() {}
+ ~RemotebackendSetup() = default;
};
BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_MAIN
#define BOOST_TEST_MODULE unit
RemoteLoader();
};
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
struct RemotebackendSetup
{
RemotebackendSetup()
{
- be = 0;
+ backendUnderTest = nullptr;
try {
// setup minimum arguments
::arg().set("module-dir") = "./.libs";
// then get us a instance of it
::arg().set("remote-connection-string") = "unix:path=/tmp/remotebackend.sock";
::arg().set("remote-dnssec") = "yes";
- be = BackendMakers().all()[0];
+ backendUnderTest = std::move(BackendMakers().all()[0]);
// load few record types to help out
SOARecordContent::report();
NSRecordContent::report();
BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
};
}
- ~RemotebackendSetup() {}
+ ~RemotebackendSetup() = default;
};
BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_MAIN
#define BOOST_TEST_MODULE unit
RemoteLoader();
};
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
#ifdef REMOTEBACKEND_ZEROMQ
#include <boost/test/unit_test.hpp>
{
RemotebackendSetup()
{
- be = 0;
+ backendUnderTest = nullptr;
try {
// setup minimum arguments
::arg().set("module-dir") = "./.libs";
// then get us a instance of it
::arg().set("remote-connection-string") = "zeromq:endpoint=ipc:///tmp/remotebackend.0";
::arg().set("remote-dnssec") = "yes";
- be = BackendMakers().all()[0];
+ backendUnderTest = std::move(BackendMakers().all()[0]);
// load few record types to help out
SOARecordContent::report();
NSRecordContent::report();
BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
};
}
- ~RemotebackendSetup() {}
+ ~RemotebackendSetup() = default;
};
BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "test-remotebackend-keys.hh"
-extern DNSBackend* be;
+extern std::unique_ptr<DNSBackend> backendUnderTest;
BOOST_AUTO_TEST_SUITE(test_remotebackend_so)
BOOST_AUTO_TEST_CASE(test_method_lookup)
{
BOOST_TEST_MESSAGE("Testing lookup method");
- DNSResourceRecord rr;
- be->lookup(QType(QType::SOA), DNSName("unit.test."));
+ DNSResourceRecord resourceRecord;
+ backendUnderTest->lookup(QType(QType::SOA), DNSName("unit.test."));
// then try to get()
- BOOST_CHECK(be->get(rr)); // and this should be TRUE.
+ BOOST_CHECK(backendUnderTest->get(resourceRecord)); // and this should be TRUE.
// then we check rr contains what we expect
- BOOST_CHECK_EQUAL(rr.qname.toString(), "unit.test.");
- BOOST_CHECK_MESSAGE(rr.qtype == QType::SOA, "returned qtype was not SOA");
- BOOST_CHECK_EQUAL(rr.content, "ns.unit.test. hostmaster.unit.test. 1 2 3 4 5");
- BOOST_CHECK_EQUAL(rr.ttl, 300);
+ BOOST_CHECK_EQUAL(resourceRecord.qname.toString(), "unit.test.");
+ BOOST_CHECK_MESSAGE(resourceRecord.qtype == QType::SOA, "returned qtype was not SOA");
+ BOOST_CHECK_EQUAL(resourceRecord.content, "ns.unit.test. hostmaster.unit.test. 1 2 3 4 5");
+ BOOST_CHECK_EQUAL(resourceRecord.ttl, 300);
}
BOOST_AUTO_TEST_CASE(test_method_lookup_empty)
{
BOOST_TEST_MESSAGE("Testing lookup method with empty result");
- DNSResourceRecord rr;
- be->lookup(QType(QType::SOA), DNSName("empty.unit.test."));
+ DNSResourceRecord resourceRecord;
+ backendUnderTest->lookup(QType(QType::SOA), DNSName("empty.unit.test."));
// then try to get()
- BOOST_CHECK(!be->get(rr)); // and this should be FALSE
+ BOOST_CHECK(!backendUnderTest->get(resourceRecord)); // and this should be FALSE
}
BOOST_AUTO_TEST_CASE(test_method_list)
{
int record_count = 0;
- DNSResourceRecord rr;
+ DNSResourceRecord resourceRecord;
BOOST_TEST_MESSAGE("Testing list method");
- be->list(DNSName("unit.test."), -1);
- while (be->get(rr))
+ backendUnderTest->list(DNSName("unit.test."), -1);
+ while (backendUnderTest->get(resourceRecord)) {
record_count++;
+ }
BOOST_CHECK_EQUAL(record_count, 5); // number of records our test domain has
}
BOOST_AUTO_TEST_CASE(test_method_doesDNSSEC)
{
BOOST_TEST_MESSAGE("Testing doesDNSSEC method");
- BOOST_CHECK(be->doesDNSSEC()); // should be true
+ BOOST_CHECK(backendUnderTest->doesDNSSEC()); // should be true
}
BOOST_AUTO_TEST_CASE(test_method_setDomainMetadata)
{
std::vector<std::string> meta;
- meta.push_back("VALUE");
+ meta.emplace_back("VALUE");
BOOST_TEST_MESSAGE("Testing setDomainMetadata method");
- BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."), "TEST", meta));
+ BOOST_CHECK(backendUnderTest->setDomainMetadata(DNSName("unit.test."), "TEST", meta));
}
BOOST_AUTO_TEST_CASE(test_method_alsoNotifies)
{
- BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."), "ALSO-NOTIFY", {"192.0.2.1"}));
+ BOOST_CHECK(backendUnderTest->setDomainMetadata(DNSName("unit.test."), "ALSO-NOTIFY", {"192.0.2.1"}));
std::set<std::string> alsoNotifies;
BOOST_TEST_MESSAGE("Testing alsoNotifies method");
- be->alsoNotifies(DNSName("unit.test."), &alsoNotifies);
+ backendUnderTest->alsoNotifies(DNSName("unit.test."), &alsoNotifies);
BOOST_CHECK_EQUAL(alsoNotifies.size(), 1);
- if (alsoNotifies.size() > 0)
+ if (!alsoNotifies.empty()) {
BOOST_CHECK_EQUAL(alsoNotifies.count("192.0.2.1"), 1);
- BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."), "ALSO-NOTIFY", std::vector<std::string>()));
+ }
+ BOOST_CHECK(backendUnderTest->setDomainMetadata(DNSName("unit.test."), "ALSO-NOTIFY", std::vector<std::string>()));
}
BOOST_AUTO_TEST_CASE(test_method_getDomainMetadata)
{
std::vector<std::string> meta;
BOOST_TEST_MESSAGE("Testing getDomainMetadata method");
- be->getDomainMetadata(DNSName("unit.test."), "TEST", meta);
+ backendUnderTest->getDomainMetadata(DNSName("unit.test."), "TEST", meta);
BOOST_CHECK_EQUAL(meta.size(), 1);
// in case we got more than one value, which would be unexpected
// but not fatal
- if (meta.size() > 0)
+ if (!meta.empty()) {
BOOST_CHECK_EQUAL(meta[0], "VALUE");
+ }
}
BOOST_AUTO_TEST_CASE(test_method_getAllDomainMetadata)
{
std::map<std::string, std::vector<std::string>> meta;
BOOST_TEST_MESSAGE("Testing getAllDomainMetadata method");
- be->getAllDomainMetadata(DNSName("unit.test."), meta);
+ backendUnderTest->getAllDomainMetadata(DNSName("unit.test."), meta);
BOOST_CHECK_EQUAL(meta.size(), 1);
// in case we got more than one value, which would be unexpected
// but not fatal
- if (meta.size() > 0)
+ if (!meta.empty()) {
BOOST_CHECK_EQUAL(meta["TEST"][0], "VALUE");
+ }
}
BOOST_AUTO_TEST_CASE(test_method_addDomainKey)
{
BOOST_TEST_MESSAGE("Testing addDomainKey method");
- int64_t id;
- be->addDomainKey(DNSName("unit.test."), k1, id);
- BOOST_CHECK_EQUAL(id, 1);
- be->addDomainKey(DNSName("unit.test."), k2, id);
- BOOST_CHECK_EQUAL(id, 2);
+ int64_t keyID = 0;
+ backendUnderTest->addDomainKey(DNSName("unit.test."), k1, keyID);
+ BOOST_CHECK_EQUAL(keyID, 1);
+ backendUnderTest->addDomainKey(DNSName("unit.test."), k2, keyID);
+ BOOST_CHECK_EQUAL(keyID, 2);
}
BOOST_AUTO_TEST_CASE(test_method_getDomainKeys)
std::vector<DNSBackend::KeyData> keys;
BOOST_TEST_MESSAGE("Testing getDomainKeys method");
// we expect to get two keys
- be->getDomainKeys(DNSName("unit.test."), keys);
+ backendUnderTest->getDomainKeys(DNSName("unit.test."), keys);
BOOST_CHECK_EQUAL(keys.size(), 2);
// in case we got more than 2 keys, which would be unexpected
// but not fatal
if (keys.size() > 1) {
// check that we have two keys
- for (DNSBackend::KeyData& kd : keys) {
- BOOST_CHECK(kd.id > 0);
- BOOST_CHECK(kd.flags == 256 || kd.flags == 257);
- BOOST_CHECK(kd.active == true);
- BOOST_CHECK(kd.published == true);
- BOOST_CHECK(kd.content.size() > 500);
+ for (DNSBackend::KeyData& keyData : keys) {
+ BOOST_CHECK(keyData.id > 0);
+ BOOST_CHECK(keyData.flags == 256 || keyData.flags == 257);
+ BOOST_CHECK(keyData.active == true);
+ BOOST_CHECK(keyData.published == true);
+ BOOST_CHECK(keyData.content.size() > 500);
}
}
}
BOOST_AUTO_TEST_CASE(test_method_deactivateDomainKey)
{
BOOST_TEST_MESSAGE("Testing deactivateDomainKey method");
- BOOST_CHECK(be->deactivateDomainKey(DNSName("unit.test."), 1));
+ BOOST_CHECK(backendUnderTest->deactivateDomainKey(DNSName("unit.test."), 1));
}
BOOST_AUTO_TEST_CASE(test_method_activateDomainKey)
{
BOOST_TEST_MESSAGE("Testing activateDomainKey method");
- BOOST_CHECK(be->activateDomainKey(DNSName("unit.test."), 1));
+ BOOST_CHECK(backendUnderTest->activateDomainKey(DNSName("unit.test."), 1));
}
BOOST_AUTO_TEST_CASE(test_method_removeDomainKey)
{
- BOOST_CHECK(be->removeDomainKey(DNSName("unit.test."), 2));
- BOOST_CHECK(be->removeDomainKey(DNSName("unit.test."), 1));
+ BOOST_CHECK(backendUnderTest->removeDomainKey(DNSName("unit.test."), 2));
+ BOOST_CHECK(backendUnderTest->removeDomainKey(DNSName("unit.test."), 1));
}
BOOST_AUTO_TEST_CASE(test_method_getBeforeAndAfterNamesAbsolute)
{
- DNSName unhashed, before, after;
+ DNSName unhashed;
+ DNSName before;
+ DNSName after;
BOOST_TEST_MESSAGE("Testing getBeforeAndAfterNamesAbsolute method");
- be->getBeforeAndAfterNamesAbsolute(-1, DNSName("middle.unit.test."), unhashed, before, after);
+ backendUnderTest->getBeforeAndAfterNamesAbsolute(1, DNSName("middle.unit.test."), unhashed, before, after);
BOOST_CHECK_EQUAL(unhashed.toString(), "middle.");
BOOST_CHECK_EQUAL(before.toString(), "begin.");
BOOST_CHECK_EQUAL(after.toString(), "stop.");
BOOST_AUTO_TEST_CASE(test_method_setTSIGKey)
{
- std::string algorithm, content;
+ std::string algorithm;
+ std::string content;
BOOST_TEST_MESSAGE("Testing setTSIGKey method");
- BOOST_CHECK_MESSAGE(be->setTSIGKey(DNSName("unit.test."), DNSName("hmac-md5."), "kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys="), "did not return true");
+ BOOST_CHECK_MESSAGE(backendUnderTest->setTSIGKey(DNSName("unit.test."), DNSName("hmac-md5."), "kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys="), "did not return true");
}
BOOST_AUTO_TEST_CASE(test_method_getTSIGKey)
DNSName algorithm;
std::string content;
BOOST_TEST_MESSAGE("Testing getTSIGKey method");
- be->getTSIGKey(DNSName("unit.test."), algorithm, content);
+ backendUnderTest->getTSIGKey(DNSName("unit.test."), algorithm, content);
BOOST_CHECK_EQUAL(algorithm.toString(), "hmac-md5.");
BOOST_CHECK_EQUAL(content, "kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys=");
}
BOOST_AUTO_TEST_CASE(test_method_deleteTSIGKey)
{
- std::string algorithm, content;
+ std::string algorithm;
+ std::string content;
BOOST_TEST_MESSAGE("Testing deleteTSIGKey method");
- BOOST_CHECK_MESSAGE(be->deleteTSIGKey(DNSName("unit.test.")), "did not return true");
+ BOOST_CHECK_MESSAGE(backendUnderTest->deleteTSIGKey(DNSName("unit.test.")), "did not return true");
}
BOOST_AUTO_TEST_CASE(test_method_getTSIGKeys)
{
std::vector<struct TSIGKey> keys;
BOOST_TEST_MESSAGE("Testing getTSIGKeys method");
- be->getTSIGKeys(keys);
- BOOST_CHECK(keys.size() > 0);
- if (keys.size() > 0) {
+ backendUnderTest->getTSIGKeys(keys);
+ BOOST_CHECK(!keys.empty());
+ if (!keys.empty()) {
BOOST_CHECK_EQUAL(keys[0].name.toString(), "test.");
BOOST_CHECK_EQUAL(keys[0].algorithm.toString(), "NULL.");
BOOST_CHECK_EQUAL(keys[0].key, "NULL");
BOOST_AUTO_TEST_CASE(test_method_setNotified)
{
BOOST_TEST_MESSAGE("Testing setNotified method");
- be->setNotified(1, 2);
+ backendUnderTest->setNotified(1, 2);
BOOST_CHECK(true); // we check this on next step
}
BOOST_AUTO_TEST_CASE(test_method_getDomainInfo)
{
- DomainInfo di;
+ DomainInfo domainInfo;
BOOST_TEST_MESSAGE("Testing getDomainInfo method");
- be->getDomainInfo(DNSName("unit.test."), di);
- BOOST_CHECK_EQUAL(di.zone.toString(), "unit.test.");
- BOOST_CHECK_EQUAL(di.serial, 2);
- BOOST_CHECK_EQUAL(di.notified_serial, 2);
- BOOST_CHECK_EQUAL(di.kind, DomainInfo::Native);
- BOOST_CHECK_EQUAL(di.backend, be);
+ backendUnderTest->getDomainInfo(DNSName("unit.test."), domainInfo);
+ BOOST_CHECK_EQUAL(domainInfo.zone.toString(), "unit.test.");
+ BOOST_CHECK_EQUAL(domainInfo.serial, 2);
+ BOOST_CHECK_EQUAL(domainInfo.notified_serial, 2);
+ BOOST_CHECK_EQUAL(domainInfo.kind, DomainInfo::Native);
+ BOOST_CHECK_EQUAL(domainInfo.backend, backendUnderTest.get());
}
BOOST_AUTO_TEST_CASE(test_method_getAllDomains)
{
- DomainInfo di;
+ DomainInfo domainInfo;
BOOST_TEST_MESSAGE("Testing getAllDomains method");
vector<DomainInfo> result;
- be->getAllDomains(&result, true, true);
+ backendUnderTest->getAllDomains(&result, true, true);
BOOST_REQUIRE(!result.empty());
- di = result.at(0);
- BOOST_CHECK_EQUAL(di.zone.toString(), "unit.test.");
- BOOST_CHECK_EQUAL(di.serial, 2);
- BOOST_CHECK_EQUAL(di.notified_serial, 2);
- BOOST_CHECK_EQUAL(di.kind, DomainInfo::Native);
- BOOST_CHECK_EQUAL(di.backend, be);
+ domainInfo = result.at(0);
+ BOOST_CHECK_EQUAL(domainInfo.zone.toString(), "unit.test.");
+ BOOST_CHECK_EQUAL(domainInfo.serial, 2);
+ BOOST_CHECK_EQUAL(domainInfo.notified_serial, 2);
+ BOOST_CHECK_EQUAL(domainInfo.kind, DomainInfo::Native);
+ BOOST_CHECK_EQUAL(domainInfo.backend, backendUnderTest.get());
}
-BOOST_AUTO_TEST_CASE(test_method_superMasterBackend)
+BOOST_AUTO_TEST_CASE(test_method_autoPrimaryBackend)
{
- DNSResourceRecord rr;
+ DNSResourceRecord resourceRecord;
std::vector<DNSResourceRecord> nsset;
- DNSBackend* dbd;
- BOOST_TEST_MESSAGE("Testing superMasterBackend method");
-
- rr.qname = DNSName("example.com.");
- rr.qtype = QType::NS;
- rr.qclass = QClass::IN;
- rr.ttl = 300;
- rr.content = "ns1.example.com.";
- nsset.push_back(rr);
- rr.qname = DNSName("example.com.");
- rr.qtype = QType::NS;
- rr.qclass = QClass::IN;
- rr.ttl = 300;
- rr.content = "ns2.example.com.";
- nsset.push_back(rr);
-
- BOOST_CHECK(be->superMasterBackend("10.0.0.1", DNSName("example.com."), nsset, NULL, NULL, &dbd));
+ DNSBackend* dbd = nullptr;
+ BOOST_TEST_MESSAGE("Testing autoPrimaryBackend method");
+
+ resourceRecord.qname = DNSName("example.com.");
+ resourceRecord.qtype = QType::NS;
+ resourceRecord.qclass = QClass::IN;
+ resourceRecord.ttl = 300;
+ resourceRecord.content = "ns1.example.com.";
+ nsset.push_back(resourceRecord);
+ resourceRecord.qname = DNSName("example.com.");
+ resourceRecord.qtype = QType::NS;
+ resourceRecord.qclass = QClass::IN;
+ resourceRecord.ttl = 300;
+ resourceRecord.content = "ns2.example.com.";
+ nsset.push_back(resourceRecord);
+
+ BOOST_CHECK(backendUnderTest->autoPrimaryBackend("10.0.0.1", DNSName("example.com."), nsset, nullptr, nullptr, &dbd));
// let's see what we got
- BOOST_CHECK_EQUAL(dbd, be);
+ BOOST_CHECK_EQUAL(dbd, backendUnderTest.get());
}
-BOOST_AUTO_TEST_CASE(test_method_createSlaveDomain)
+BOOST_AUTO_TEST_CASE(test_method_createSecondaryDomain)
{
- BOOST_TEST_MESSAGE("Testing createSlaveDomain method");
- BOOST_CHECK(be->createSlaveDomain("10.0.0.1", DNSName("pirate.unit.test."), "", ""));
+ BOOST_TEST_MESSAGE("Testing createSecondaryDomain method");
+ BOOST_CHECK(backendUnderTest->createSecondaryDomain("10.0.0.1", DNSName("pirate.unit.test."), "", ""));
}
BOOST_AUTO_TEST_CASE(test_method_feedRecord)
{
- DNSResourceRecord rr;
+ DNSResourceRecord resourceRecord;
BOOST_TEST_MESSAGE("Testing feedRecord method");
- be->startTransaction(DNSName("example.com."), 2);
- rr.qname = DNSName("example.com.");
- rr.qtype = QType::SOA;
- rr.qclass = QClass::IN;
- rr.ttl = 300;
- rr.content = "ns1.example.com. hostmaster.example.com. 2013013441 7200 3600 1209600 300";
- BOOST_CHECK(be->feedRecord(rr, DNSName()));
- rr.qname = DNSName("replace.example.com.");
- rr.qtype = QType::A;
- rr.qclass = QClass::IN;
- rr.ttl = 300;
- rr.content = "127.0.0.1";
- BOOST_CHECK(be->feedRecord(rr, DNSName()));
- be->commitTransaction();
+ backendUnderTest->startTransaction(DNSName("example.com."), 2);
+ resourceRecord.qname = DNSName("example.com.");
+ resourceRecord.qtype = QType::SOA;
+ resourceRecord.qclass = QClass::IN;
+ resourceRecord.ttl = 300;
+ resourceRecord.content = "ns1.example.com. hostmaster.example.com. 2013013441 7200 3600 1209600 300";
+ BOOST_CHECK(backendUnderTest->feedRecord(resourceRecord, DNSName()));
+ resourceRecord.qname = DNSName("replace.example.com.");
+ resourceRecord.qtype = QType::A;
+ resourceRecord.qclass = QClass::IN;
+ resourceRecord.ttl = 300;
+ resourceRecord.content = "127.0.0.1";
+ BOOST_CHECK(backendUnderTest->feedRecord(resourceRecord, DNSName()));
+ backendUnderTest->commitTransaction();
}
BOOST_AUTO_TEST_CASE(test_method_replaceRRSet)
{
- be->startTransaction(DNSName("example.com."), 2);
- DNSResourceRecord rr;
+ backendUnderTest->startTransaction(DNSName("example.com."), 2);
+ DNSResourceRecord resourceRecord;
std::vector<DNSResourceRecord> rrset;
BOOST_TEST_MESSAGE("Testing replaceRRSet method");
- rr.qname = DNSName("replace.example.com.");
- rr.qtype = QType::A;
- rr.qclass = QClass::IN;
- rr.ttl = 300;
- rr.content = "1.1.1.1";
- rrset.push_back(rr);
- BOOST_CHECK(be->replaceRRSet(2, DNSName("replace.example.com."), QType(QType::A), rrset));
- be->commitTransaction();
+ resourceRecord.qname = DNSName("replace.example.com.");
+ resourceRecord.qtype = QType::A;
+ resourceRecord.qclass = QClass::IN;
+ resourceRecord.ttl = 300;
+ resourceRecord.content = "1.1.1.1";
+ rrset.push_back(resourceRecord);
+ BOOST_CHECK(backendUnderTest->replaceRRSet(2, DNSName("replace.example.com."), QType(QType::A), rrset));
+ backendUnderTest->commitTransaction();
}
BOOST_AUTO_TEST_CASE(test_method_feedEnts)
{
BOOST_TEST_MESSAGE("Testing feedEnts method");
- be->startTransaction(DNSName("example.com."), 2);
+ backendUnderTest->startTransaction(DNSName("example.com."), 2);
map<DNSName, bool> nonterm = boost::assign::map_list_of(DNSName("_udp"), true)(DNSName("_sip._udp"), true);
- BOOST_CHECK(be->feedEnts(2, nonterm));
- be->commitTransaction();
+ BOOST_CHECK(backendUnderTest->feedEnts(2, nonterm));
+ backendUnderTest->commitTransaction();
}
BOOST_AUTO_TEST_CASE(test_method_feedEnts3)
{
BOOST_TEST_MESSAGE("Testing feedEnts3 method");
- be->startTransaction(DNSName("example.com"), 2);
+ backendUnderTest->startTransaction(DNSName("example.com"), 2);
NSEC3PARAMRecordContent ns3prc;
ns3prc.d_iterations = 1;
ns3prc.d_salt = "\u00aa\u00bb\u00cc\u00dd";
map<DNSName, bool> nonterm = boost::assign::map_list_of(DNSName("_udp"), true)(DNSName("_sip._udp"), true);
- BOOST_CHECK(be->feedEnts3(2, DNSName("example.com."), nonterm, ns3prc, 0));
- be->commitTransaction();
+ BOOST_CHECK(backendUnderTest->feedEnts3(2, DNSName("example.com."), nonterm, ns3prc, 0));
+ backendUnderTest->commitTransaction();
}
BOOST_AUTO_TEST_CASE(test_method_abortTransaction)
{
BOOST_TEST_MESSAGE("Testing abortTransaction method");
- be->startTransaction(DNSName("example.com."), 2);
- BOOST_CHECK(be->abortTransaction());
+ backendUnderTest->startTransaction(DNSName("example.com."), 2);
+ BOOST_CHECK(backendUnderTest->abortTransaction());
}
BOOST_AUTO_TEST_CASE(test_method_directBackendCmd)
{
BOOST_TEST_MESSAGE("Testing directBackendCmd method");
- BOOST_CHECK_EQUAL(be->directBackendCmd("PING 1234"), "PING 1234");
+ BOOST_CHECK_EQUAL(backendUnderTest->directBackendCmd("PING 1234"), "PING 1234");
}
-BOOST_AUTO_TEST_CASE(test_method_getUpdatedMasters)
+BOOST_AUTO_TEST_CASE(test_method_getUpdatedPrimaries)
{
- DomainInfo di;
- BOOST_TEST_MESSAGE("Testing getUpdatedMasters method");
+ DomainInfo domainInfo;
+ BOOST_TEST_MESSAGE("Testing getUpdatedPrimaries method");
vector<DomainInfo> result;
std::unordered_set<DNSName> catalogs;
CatalogHashMap hashes;
- be->getUpdatedMasters(result, catalogs, hashes);
+ backendUnderTest->getUpdatedPrimaries(result, catalogs, hashes);
- BOOST_REQUIRE(result.size() > 0);
+ BOOST_REQUIRE(!result.empty());
- di = result.at(0);
- BOOST_CHECK_EQUAL(di.zone.toString(), "master.test.");
- BOOST_CHECK_EQUAL(di.serial, 2);
- BOOST_CHECK_EQUAL(di.notified_serial, 2);
- BOOST_CHECK_EQUAL(di.kind, DomainInfo::Master);
- BOOST_CHECK_EQUAL(di.backend, be);
+ domainInfo = result.at(0);
+ BOOST_CHECK_EQUAL(domainInfo.zone.toString(), "master.test.");
+ BOOST_CHECK_EQUAL(domainInfo.serial, 2);
+ BOOST_CHECK_EQUAL(domainInfo.notified_serial, 2);
+ BOOST_CHECK_EQUAL(domainInfo.kind, DomainInfo::Primary);
+ BOOST_CHECK_EQUAL(domainInfo.backend, backendUnderTest.get());
}
BOOST_AUTO_TEST_SUITE_END();
new_api=0
mode=$1
-# keep the original arguments for new test harness api
-orig="$*"
-
# we could be ran with new API
while [ "$1" != "" ]
do
socat=$(which socat)
function start_web() {
- local service_logfile="${mode%\.test}_server.log"
+ local service_logfile="${mode_name%\.test}_server.log"
- ./unittest_${1}.rb >> ${service_logfile} 2>&1 &
+ ./unittest_"${1}".rb >> "${service_logfile}" 2>&1 &
webrick_pid=$!
local timeout=0
while [ ${timeout} -lt 20 ]; do
- local res=$(curl http://localhost:62434/ping 2>/dev/null)
- if [ "x$res" == "xpong" ]; then
+ local res
+ res=$(curl http://localhost:62434/ping 2>/dev/null)
+ if [ "$res" == "pong" ]; then
# server is up and running
return 0
fi
sleep 1
- let timeout=timeout+1
+ (( timeout=timeout+1 ))
done
if kill -0 ${webrick_pid} 2>/dev/null; then
fi
sleep 1
- let timeout=timeout+1
+ (( timeout=timeout+1 ))
done
if kill -0 ${webrick_pid} 2>/dev/null; then
exit 77
fi
- local service_logfile="${mode%\.test}_server.log"
+ local service_logfile="${mode_name%\.test}_server.log"
- ./unittest_zeromq.rb >> ${service_logfile} 2>&1 &
+ ./unittest_zeromq.rb >> "${service_logfile}" 2>&1 &
zeromq_pid=$!
local timeout=0
fi
sleep 1
- let timeout=timeout+1
+ (( timeout=timeout+1 ))
done
if kill -0 ${zeromq_pid} 2>/dev/null; then
fi
sleep 1
- let timeout=timeout+1
+ (( timeout=timeout+1 ))
done
if kill -0 ${zeromq_pid} 2>/dev/null; then
}
function start_unix() {
- if [ -z "$socat" -o ! -x "$socat" ]; then
+ if [ -z "$socat" ] || [ ! -x "$socat" ]; then
echo "INFO: Skipping \"UNIX socket\" test because \"socat\" executable wasn't found!"
exit 77
fi
fi
sleep 1
- let timeout=timeout+1
+ (( timeout=timeout+1 ))
done
if kill -0 ${socat_pid} 2>/dev/null; then
fi
sleep 1
- let timeout=timeout+1
+ (( timeout=timeout+1 ))
done
if kill -0 ${socat_pid} 2>/dev/null; then
function run_test() {
if [ $new_api -eq 0 ]; then
- ./$mode
+ ./"$mode_name"
else
- $orig
+ $mode
fi
}
-mode=`basename "$mode"`
+mode_name=$(basename "$mode")
-case "$mode" in
+case "$mode_name" in
remotebackend_pipe.test)
run_test
;;
remotebackend_unix.test)
start_unix
- run_test
+ run_test ; rv=$?
stop_unix
;;
remotebackend_http.test)
start_web "http"
- run_test
+ run_test ; rv=$?
stop_web "http"
;;
remotebackend_post.test)
start_web "post"
- run_test
+ run_test ; rv=$?
stop_web "post"
;;
remotebackend_json.test)
start_web "json"
- run_test
+ run_test ; rv=$?
stop_web "json"
;;
remotebackend_zeromq.test)
start_zeromq
- run_test
+ run_test ; rv=$?
stop_zeromq
;;
*)
;;
esac
-exit $?
+exit $rv
[do_getdomaininfo({'name'=>'unit.test.'})]
end
- def do_getupdatedmasters()
+ def do_getupdatedmasters(args)
[do_getdomaininfo({'name'=>'master.test.'})]
end
end
if h.respond_to?(method.to_sym) == false
res = false
- elsif args.size > 0
- res, log = h.send(method,args)
else
- res, log = h.send(method)
+ res, log = h.send(method,args)
end
puts ({:result => res, :log => log}).to_json
f.puts "#{Time.now.to_f} [pipe]: #{({:result => res, :log => log}).to_json}"
if h.respond_to?(method.to_sym) == false
res = false
- elsif args.size > 0
- res, log = h.send(method,args)
else
- res, log = h.send(method)
+ res, log = h.send(method,args)
end
socket.send_string ({:result => res, :log => log}).to_json + "\n" , 0
f.puts "#{Time.now.to_f} [zmq]: #{({:result => res, :log => log}).to_json}"
UnixsocketConnector::~UnixsocketConnector()
{
if (this->connected) {
- try {
- g_log << Logger::Info << "closing socket connection" << endl;
- }
- catch (...) {
- }
close(fd);
}
}
{
auto data = input.dump() + "\n";
int rv = this->write(data);
- if (rv == -1)
+ if (rv == -1) {
return -1;
+ }
return rv;
}
int UnixsocketConnector::recv_message(Json& output)
{
- int rv;
- std::string s_output, err;
-
- struct timeval t0, t;
-
- gettimeofday(&t0, NULL);
+ int rv = 0;
+ std::string s_output;
+ std::string err;
+
+ struct timeval t0
+ {
+ };
+ struct timeval t
+ {
+ };
+
+ gettimeofday(&t0, nullptr);
memcpy(&t, &t0, sizeof(t0));
s_output = "";
while ((t.tv_sec - t0.tv_sec) * 1000 + (t.tv_usec - t0.tv_usec) / 1000 < this->timeout) {
int avail = waitForData(this->fd, 0, this->timeout * 500); // use half the timeout as poll timeout
- if (avail < 0) // poll error
+ if (avail < 0) { // poll error
return -1;
+ }
if (avail == 0) { // timeout
- gettimeofday(&t, NULL);
+ gettimeofday(&t, nullptr);
continue;
}
rv = this->read(s_output);
- if (rv == -1)
+ if (rv == -1) {
return -1;
+ }
if (rv > 0) {
// see if it can be parsed
output = Json::parse(s_output, err);
- if (output != nullptr)
+ if (output != nullptr) {
return s_output.size();
+ }
}
- gettimeofday(&t, NULL);
+ gettimeofday(&t, nullptr);
}
close(fd);
ssize_t UnixsocketConnector::read(std::string& data)
{
- ssize_t nread;
+ ssize_t nread = 0;
char buf[1500] = {0};
reconnect();
- if (!connected)
+ if (!connected) {
return -1;
+ }
nread = ::read(this->fd, buf, sizeof buf);
// just try again later...
- if (nread == -1 && errno == EAGAIN)
+ if (nread == -1 && errno == EAGAIN) {
return 0;
+ }
if (nread == -1 || nread == 0) {
connected = false;
size_t pos = 0;
reconnect();
- if (!connected)
+ if (!connected) {
return -1;
+ }
while (pos < data.size()) {
ssize_t written = ::write(fd, &data.at(pos), data.size() - pos);
close(fd);
return -1;
}
- else {
- pos = pos + static_cast<size_t>(written);
- }
+ pos = pos + static_cast<size_t>(written);
}
return pos;
}
void UnixsocketConnector::reconnect()
{
- struct sockaddr_un sock;
- int rv;
+ struct sockaddr_un sock
+ {
+ };
+ int rv = 0;
- if (connected)
+ if (connected) {
return; // no point reconnecting if connected...
+ }
connected = true;
g_log << Logger::Info << "Reconnecting to backend" << std::endl;
return;
}
- if (makeUNsockaddr(path, &sock)) {
+ if (makeUNsockaddr(path, &sock) != 0) {
g_log << Logger::Error << "Unable to create UNIX domain socket: Path '" << path << "' is not a valid UNIX socket path." << std::endl;
return;
}
this->send(msg);
msg = nullptr;
- if (this->recv(msg) == false) {
+ if (!this->recv(msg)) {
g_log << Logger::Warning << "Failed to initialize backend" << std::endl;
close(fd);
this->connected = false;
this->send(msg);
msg = nullptr;
- if (this->recv(msg) == false) {
+ if (!this->recv(msg)) {
g_log << Logger::Error << "Failed to initialize zeromq" << std::endl;
throw PDNSException("Failed to initialize zeromq");
}
};
-ZeroMQConnector::~ZeroMQConnector() {}
+ZeroMQConnector::~ZeroMQConnector() = default;
int ZeroMQConnector::send_message(const Json& input)
{
// message was not sent
g_log << Logger::Error << "Cannot send to " << this->d_endpoint << ": " << zmq_strerror(errno) << std::endl;
}
- else
+ else {
return line.size();
+ }
}
}
}
// we have an event
if ((item.revents & ZMQ_POLLIN) == ZMQ_POLLIN) {
string data;
- size_t msg_size;
+ size_t msg_size = 0;
zmq_msg_init(&message);
// read something
if (zmq_msg_recv(&message, this->d_sock.get(), ZMQ_NOBLOCK) > 0) {
data.assign(reinterpret_cast<const char*>(zmq_msg_data(&message)), msg_size);
zmq_msg_close(&message);
output = Json::parse(data, err);
- if (output != nullptr)
+ if (output != nullptr) {
rv = msg_size;
- else
+ }
+ else {
g_log << Logger::Error << "Cannot parse JSON reply from " << this->d_endpoint << ": " << err << endl;
+ }
break;
}
- else if (errno == EAGAIN) {
+ if (errno == EAGAIN) {
continue; // try again }
}
- else {
- break;
- }
+ break;
}
}
}
-AM_CPPFLAGS += $(CDB_CFLAGS)
+AM_CPPFLAGS += $(CDB_CFLAGS) $(LIBCRYPTO_INCLUDES)
pkglib_LTLIBRARIES = libtinydnsbackend.la
../../pdns/cdb.cc ../../pdns/cdb.hh \
tinydnsbackend.cc tinydnsbackend.hh
-libtinydnsbackend_la_LDFLAGS = -module -avoid-version
+libtinydnsbackend_la_LDFLAGS = -module -avoid-version $(LIBCRYPTO_LDFLAGS) $(LIBSSL_LDFLAGS)
libtinydnsbackend_la_LIBADD = $(CDB_LIBS)
&italy.example.com::italy-ns2.example.com.:120
&usa.example.com::usa-ns1.usa.example.com.:120
&usa.example.com::usa-ns2.usa.example.com.:120
++\052.mixed-wc.example.com:192.168.1.1:120
+\052.w5.example.com:1.2.3.5:120
+bar.svcb.example.com:192.0.2.1:120
+double.example.com:192.168.5.1:120
@mail.example.com::smtp1.example.com.:25:120
@together-too-much.example.com::toomuchinfo-a.example.com.:25:120
@together-too-much.example.com::toomuchinfo-b.example.com.:25:120
+C\052.mixed-wc.example.com:outpost.example.com.:120
C\052.w1.example.com:x.y.z.w2.example.com.:120
C\052.w2.example.com:x.y.z.w3.example.com.:120
C\052.w3.example.com:x.y.z.w4.example.com.:120
d_isWildcardQuery = false;
}
-void TinyDNSBackend::getUpdatedMasters(vector<DomainInfo>& retDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
+void TinyDNSBackend::getUpdatedPrimaries(vector<DomainInfo>& retDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
{
auto domainInfo = s_domainInfo.lock(); //TODO: We could actually lock less if we do it per suffix.
if (!domainInfo->count(d_suffix)) {
di.id = -1; //TODO: Check if this is ok.
di.backend = this;
di.zone = rr.qname;
- di.kind = DomainInfo::Master;
+ di.kind = DomainInfo::Primary;
di.last_check = time(0);
if (getSerial) {
dr.d_type = rr.qtype.getCode();
dr.d_clen = val.size() - pr.getPosition();
- auto drc = DNSRecordContent::mastermake(dr, pr);
+ auto drc = DNSRecordContent::make(dr, pr);
rr.content = drc->getZoneRepresentation();
DLOG(cerr << "CONTENT: " << rr.content << endl);
}
void declareArguments(const string& suffix = "") override
{
- declare(suffix, "notify-on-startup", "Tell the TinyDNSBackend to notify all the slave nameservers on startup. Default is no.", "no");
+ declare(suffix, "notify-on-startup", "Tell the TinyDNSBackend to notify all the secondary nameservers on startup. Default is no.", "no");
declare(suffix, "dbfile", "Location of the cdb data file", "data.cdb");
declare(suffix, "tai-adjust", "This adjusts the TAI value if timestamps are used. These seconds will be added to the start point (1970) and will allow you to adjust for leap seconds. The default is 11.", "11");
declare(suffix, "locations", "Enable or Disable location support in the backend. Changing the value to 'no' will make the backend ignore the locations. This then returns all records!", "yes");
bool get(DNSResourceRecord& rr) override;
void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled) override;
- //Master mode operation
- void getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+ // Primary mode operation
+ void getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
void setNotified(uint32_t id, uint32_t serial) override;
private:
/comfun
/config.h
/mkbindist
-/pdns.init
/showvar
/stamp-h
/pdns_control
JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
+ARC4RANDOM_LIBS = $(top_builddir)/ext/arc4random/libarc4random.la
AM_CPPFLAGS += \
-I$(top_srcdir)/ext/json11 \
AM_LDFLAGS = \
$(PROGRAM_LDFLAGS) \
$(LIBCRYPTO_LIBS) \
+ $(ARC4RANDOM_LIBS) \
$(THREADFLAGS)
AM_LFLAGS = -i
dnslabeltext.rl \
dnslabeltext.cc \
dnsmessage.proto \
- mtasker.cc \
inflighter.cc \
- bindparser.h \
+ bindparser.hh \
named.conf.parsertest \
pdns.service.in \
ixfrdist.service.in \
lua-record.cc \
minicurl.cc \
minicurl.hh \
+ standalone_fuzz_target_runner.cc \
api-swagger.yaml \
api-swagger.json \
requirements.txt \
BUILT_SOURCES = \
bind-dnssec.schema.sqlite3.sql.h \
- bindparser.h \
+ bindparser.hh \
dnslabeltext.cc \
apidocfiles.h
# use a $(wildcard) wrapper here to allow build to proceed if output
# file is present but input file is not (e.g. in a dist tarball)
-api-swagger.yaml: $(wildcard ../docs/http-api/swagger/authoritative-api-swagger.yaml)
+api-swagger.yaml: $(wildcard ${srcdir}/../docs/http-api/swagger/authoritative-api-swagger.yaml)
cp $< $@
if HAVE_VENV
api-swagger.json: api-swagger.yaml requirements.txt
$(PYTHON) -m venv .venv
.venv/bin/pip install -U pip setuptools setuptools-git wheel
- .venv/bin/pip install -r requirements.txt
- .venv/bin/python convert-yaml-to-json.py $< $@
+ .venv/bin/pip install -r ${srcdir}/requirements.txt
+ .venv/bin/python ${srcdir}/convert-yaml-to-json.py $< $@
else # if HAVE_VENV
if !HAVE_API_SWAGGER_JSON
api-swagger.json:
endif
apidocfiles.h: api-swagger.yaml api-swagger.json
- ./incfiles $^ > $@
+ $(AM_V_GEN)$(srcdir)/incfiles $^ > $@.tmp
+ @mv $@.tmp $@
-noinst_SCRIPTS = pdns.init
sysconf_DATA = pdns.conf-dist
sbin_PROGRAMS = pdns_server
auth-catalogzone.cc auth-catalogzone.hh \
auth-main.cc auth-main.hh \
auth-packetcache.cc auth-packetcache.hh \
+ auth-primarycommunicator.cc \
auth-querycache.cc auth-querycache.hh \
+ auth-secondarycommunicator.cc \
auth-zonecache.cc auth-zonecache.hh \
axfr-retriever.cc axfr-retriever.hh \
backends/gsql/gsqlbackend.cc backends/gsql/gsqlbackend.hh \
circular_buffer.hh \
comment.hh \
communicator.cc communicator.hh \
+ coverage.cc coverage.hh \
credentials.cc credentials.hh \
dbdnsseckeeper.cc \
digests.hh \
distributor.hh \
dns.cc dns.hh \
- dns_random.cc dns_random.hh \
+ dns_random.hh \
dnsbackend.cc dnsbackend.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
logging.hh \
lua-auth4.cc lua-auth4.hh \
lua-base4.cc lua-base4.hh \
- mastercommunicator.cc \
misc.cc misc.hh \
nameserver.cc nameserver.hh \
namespaces.hh \
shuffle.cc shuffle.hh \
signingpipe.cc signingpipe.hh \
sillyrecords.cc \
- slavecommunicator.cc \
stat_t.hh \
statbag.cc statbag.hh \
stubresolver.cc stubresolver.hh \
utility.hh \
uuid-utils.hh uuid-utils.cc \
version.cc version.hh \
+ views.hh \
webserver.cc webserver.hh \
ws-api.cc ws-api.hh \
ws-auth.cc ws-auth.hh \
credentials.cc credentials.hh \
dbdnsseckeeper.cc \
dns.cc \
- dns_random.cc \
dnsbackend.cc \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
bindparser.yy \
bindparserclasses.hh \
dns.cc \
- dns_random_urandom.cc \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc \
bindlexer.l \
bindparser.yy \
bindparserclasses.hh \
- dns_random_urandom.cc \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc \
dnsrecords.cc \
dnswriter.cc dnswriter.hh \
dolog.hh \
+ ednsextendederror.cc ednsextendederror.hh \
ednssubnet.cc iputils.cc \
libssl.cc libssl.hh \
logger.cc \
base32.cc \
base64.cc base64.hh \
calidns.cc \
- dns_random_urandom.cc dns_random.hh \
+ dns_random.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc dnsparser.hh \
arguments.cc arguments.hh \
base32.cc \
base64.cc \
- dns_random_urandom.cc \
dnslabeltext.cc \
dnsname.cc \
dnsparser.cc \
dnsrecords.cc \
dnswriter.cc \
+ ednsoptions.cc ednsoptions.hh \
+ ednssubnet.cc ednssubnet.hh \
iputils.cc \
logger.cc \
misc.cc \
saxfr_SOURCES = \
base32.cc \
base64.cc base64.hh \
- dns_random_urandom.cc dns_random.hh \
+ dns_random.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc dnsparser.hh \
ixfrdist_SOURCES = \
arguments.cc \
+ auth-caches.cc auth-caches.hh \
+ auth-packetcache.cc auth-packetcache.hh \
+ auth-querycache.cc auth-querycache.hh \
+ auth-zonecache.cc auth-zonecache.hh \
axfr-retriever.cc \
base32.cc \
base64.cc base64.hh \
credentials.cc credentials.hh \
dns.cc \
- dns_random_urandom.cc dns_random.hh \
+ dns_random.hh \
+ dnsbackend.cc \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
+ dnspacket.cc dnspacket.hh \
dnsparser.cc dnsparser.hh \
dnsrecords.cc \
dnssecinfra.cc \
dnswriter.cc dnswriter.hh \
+ ednscookies.cc ednscookies.hh \
+ ednsextendederror.cc ednsextendederror.hh \
+ ednsoptions.cc ednsoptions.hh \
+ ednssubnet.cc ednssubnet.hh \
gss_context.cc gss_context.hh \
iputils.hh iputils.cc \
ixfr.cc ixfr.hh \
query-local-address.hh query-local-address.cc \
rcpgenerator.cc rcpgenerator.hh \
resolver.cc \
+ shuffle.cc shuffle.hh \
sillyrecords.cc \
sstuff.hh \
statbag.cc \
svc-records.cc svc-records.hh \
threadname.hh threadname.cc \
tsigverifier.cc tsigverifier.hh \
+ ueberbackend.cc ueberbackend.hh \
unix_utility.cc \
uuid-utils.hh uuid-utils.cc \
webserver.hh webserver.cc \
ixfrdist_LDADD = \
$(BOOST_PROGRAM_OPTIONS_LIBS) \
$(JSON11_LIBS) \
+ $(LIBDL) \
$(LIBCRYPTO_LIBS) \
$(YAHTTP_LIBS) \
$(YAML_LIBS)
base32.cc \
base64.cc base64.hh \
dns.cc \
- dns_random_urandom.cc dns_random.hh \
+ dns_random.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc dnsparser.hh \
dnsrecords.cc \
dnssecinfra.cc \
dnswriter.cc dnswriter.hh \
- gss_context.cc gss_context.hh \
+ ednsoptions.cc ednsoptions.hh \
+ ednssubnet.cc ednssubnet.hh \
+ gss_context.cc gss_context.hh \
iputils.cc \
ixfr.cc ixfr.hh \
ixfrutils.cc ixfrutils.hh \
base64.cc base64.hh \
digests.hh \
dns.cc \
- dns_random_urandom.cc dns_random.hh \
+ dns_random.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc dnsparser.hh \
base32.cc \
base64.cc base64.hh \
credentials.cc credentials.hh \
- dns_random.cc dns_random.hh \
+ dns_random.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc dnsparser.hh \
arguments.cc arguments.hh \
base32.cc \
base64.cc \
- dns_random.cc dns_random.hh \
+ dns_random.hh \
dnsbulktest.cc \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc \
dnsrecords.cc \
dnswriter.cc \
+ iputils.cc iputils.hh \
logger.cc \
misc.cc \
nsecrecords.cc \
base32.cc \
base64.cc base64.hh \
dns.cc \
- dns_random.cc \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.cc dnsparser.hh \
base64.cc \
bindlexer.l \
bindparser.yy \
+ channel.cc channel.hh \
credentials.cc credentials.hh \
dbdnsseckeeper.cc \
dns.cc \
- dns_random.cc \
dnsbackend.cc \
dnslabeltext.cc \
dnsname.cc \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc \
gettime.cc gettime.hh \
- gss_context.cc gss_context.hh \
+ gss_context.cc gss_context.hh \
histogram.hh \
ipcipher.cc ipcipher.hh \
iputils.cc \
test-base32_cc.cc \
test-base64_cc.cc \
test-bindparser_cc.cc \
+ test-channel.cc \
test-common.hh \
test-communicator_hh.cc \
test-credentials_cc.cc \
test-trusted-notification-proxy_cc.cc \
test-tsig.cc \
test-ueberbackend_cc.cc \
+ test-webserver_cc.cc \
test-zonemd_cc.cc \
test-zoneparser_tng_cc.cc \
testrunner.cc \
tsigverifier.cc tsigverifier.hh \
ueberbackend.cc ueberbackend.hh \
unix_utility.cc \
+ uuid-utils.cc \
validate.hh \
+ webserver.cc \
zonemd.cc zonemd.hh \
zoneparser-tng.cc zoneparser-tng.hh
$(RT_LIBS) \
$(LUA_LIBS) \
$(LIBDL) \
- $(IPCRYPT_LIBS)
+ $(IPCRYPT_LIBS) \
+ $(YAHTTP_LIBS) \
+ $(JSON11_LIBS)
if GSS_TSIG
testrunner_LDADD += $(GSS_LIBS)
+speedtest_LDADD += $(GSS_LIBS)
endif
if PKCS11
$(AM_LDFLAGS) \
$(LIBCRYPTO_LDFLAGS)
+noinst_PROGRAMS = speedtest
+
if UNIT_TESTS
-noinst_PROGRAMS = testrunner
+noinst_PROGRAMS += testrunner
if HAVE_BOOST_GE_148
-TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message SRCDIR='$(srcdir)'
+TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message BOOST_TEST_RANDOM=1 SRCDIR='$(srcdir)'
TESTS=testrunner
else
check-local:
standalone_fuzz_target_runner.o: standalone_fuzz_target_runner.cc
fuzz_targets_programs = \
- fuzz_target_dnsdistcache \
fuzz_target_moadnsparser \
fuzz_target_packetcache \
fuzz_target_proxyprotocol \
fuzz_target_yahttp \
fuzz_target_zoneparsertng
-fuzz_targets: $(fuzz_targets_programs)
+fuzz_targets: $(ARC4RANDOM_LIBS) $(fuzz_targets_programs)
bin_PROGRAMS += \
$(fuzz_targets_programs)
fuzz_target_proxyprotocol_LDFLAGS = $(fuzz_targets_ldflags)
fuzz_target_proxyprotocol_LDADD = $(fuzz_targets_libs)
-fuzz_target_dnsdistcache_SOURCES = \
- dns.cc dns.hh \
- dnsdist-cache.cc dnsdist-cache.hh \
- dnsdist-ecs.cc dnsdist-ecs.hh \
- dnsdist-idstate.hh \
- dnsdist-protocols.cc dnsdist-protocols.hh \
- dnslabeltext.cc \
- dnsname.cc dnsname.hh \
- dnsparser.cc dnsparser.hh \
- dnswriter.cc dnswriter.hh \
- doh.hh \
- ednsoptions.cc ednsoptions.hh \
- ednssubnet.cc ednssubnet.hh \
- fuzz_dnsdistcache.cc \
- iputils.cc iputils.hh \
- misc.cc misc.hh \
- packetcache.hh \
- qtype.cc qtype.hh \
- svc-records.cc svc-records.hh
-
-fuzz_target_dnsdistcache_DEPENDENCIES = $(fuzz_targets_deps)
-fuzz_target_dnsdistcache_LDFLAGS = $(fuzz_targets_ldflags)
-fuzz_target_dnsdistcache_LDADD = $(fuzz_targets_libs)
-
fuzz_target_yahttp_SOURCES = \
fuzz_yahttp.cc
bind-dnssec.schema.sqlite3.sql.h: bind-dnssec.schema.sqlite3.sql
( echo '#pragma once'; echo 'static char sqlCreate[] __attribute__((unused))=' ; sed 's/$$/"/g' $< | sed 's/^/"/g' ; echo ';' ) > $@
-# for bindparser.h/hh
-.hh.h:
- cp $< $@
-
-bindlexer.$(OBJEXT): bindparser.h
+bindlexer.$(OBJEXT): bindparser.hh
pdns_recursor rec_control:
@echo "Please build the recursor from the recursordist/ dir"
struct QuestionIdentifier
{
- QuestionIdentifier()
- {}
+ QuestionIdentifier() = default;
bool operator<(const QuestionIdentifier& rhs) const
{
#include <unistd.h>
#include <climits>
-const ArgvMap::param_t::const_iterator ArgvMap::begin()
+ArgvMap::param_t::const_iterator ArgvMap::begin()
{
return d_params.begin();
}
-const ArgvMap::param_t::const_iterator ArgvMap::end()
+ArgvMap::param_t::const_iterator ArgvMap::end()
{
return d_params.end();
}
-string & ArgvMap::set(const string &var)
+string& ArgvMap::set(const string& var)
{
return d_params[var];
}
-void ArgvMap::setDefault(const string &var, const string &value)
+void ArgvMap::setDefault(const string& var, const string& value)
{
- if(! defaultmap.count(var))
+ if (defaultmap.count(var) == 0) {
defaultmap.insert(pair<string, string>(var, value));
+ }
}
void ArgvMap::setDefaults()
{
- for (const auto& i: d_params)
- if(! defaultmap.count(i.first))
- defaultmap.insert(i);
+ for (const auto& param : d_params) {
+ if (defaultmap.count(param.first) == 0) {
+ defaultmap.insert(param);
+ }
+ }
}
-bool ArgvMap::mustDo(const string &var)
+bool ArgvMap::mustDo(const string& var)
{
- return ((*this)[var]!="no") && ((*this)[var]!="off");
+ return ((*this)[var] != "no") && ((*this)[var] != "off");
}
-vector<string>ArgvMap::list()
+vector<string> ArgvMap::list()
{
vector<string> ret;
- for (const auto& i: d_params)
- ret.push_back(i.first);
+ ret.reserve(d_params.size());
+ for (const auto& param : d_params) {
+ ret.push_back(param.first);
+ }
return ret;
}
-string ArgvMap::getHelp(const string &item)
+string& ArgvMap::set(const string& var, const string& help)
{
- return helpmap[item];
-}
-
-string & ArgvMap::set(const string &var, const string &help)
-{
- helpmap[var]=help;
- d_typeMap[var]="Parameter";
+ helpmap[var] = help;
+ d_typeMap[var] = "Parameter";
return set(var);
}
-void ArgvMap::setCmd(const string &var, const string &help)
+void ArgvMap::setCmd(const string& var, const string& help)
{
- helpmap[var]=help;
- d_typeMap[var]="Command";
- set(var)="no";
+ helpmap[var] = help;
+ d_typeMap[var] = "Command";
+ set(var) = "no";
}
-string & ArgvMap::setSwitch(const string &var, const string &help)
+string& ArgvMap::setSwitch(const string& var, const string& help)
{
- helpmap[var]=help;
- d_typeMap[var]="Switch";
+ helpmap[var] = help;
+ d_typeMap[var] = "Switch";
return set(var);
}
-
-bool ArgvMap::contains(const string &var, const string &val)
+bool ArgvMap::contains(const string& var, const string& val)
{
const auto& param = d_params.find(var);
- if(param == d_params.end() || param->second.empty()) {
+ if (param == d_params.end() || param->second.empty()) {
return false;
}
vector<string> parts;
stringtok(parts, param->second, ", \t");
- for (const auto& part: parts) {
- if (part == val) {
- return true;
- }
- }
-
- return false;
+ return std::any_of(parts.begin(), parts.end(), [&](const std::string& str) { return str == val; });
}
string ArgvMap::helpstring(string prefix)
{
- if(prefix=="no")
- prefix="";
+ if (prefix == "no") {
+ prefix = "";
+ }
string help;
- for (const auto& i: helpmap) {
- if(!prefix.empty() && i.first.find(prefix) != 0) // only print items with prefix
- continue;
-
- help+=" --";
- help+=i.first;
-
- string type=d_typeMap[i.first];
-
- if(type=="Parameter")
- help+="=...";
- else if(type=="Switch")
- {
- help+=" | --"+i.first+"=yes";
- help+=" | --"+i.first+"=no";
- }
+ for (const auto& helpitem : helpmap) {
+ if (!prefix.empty() && helpitem.first.find(prefix) != 0) { // only print items with prefix
+ continue;
+ }
+ help += " --";
+ help += helpitem.first;
- help+="\n\t";
- help+=i.second;
- help+="\n";
+ string type = d_typeMap[helpitem.first];
+ if (type == "Parameter") {
+ help += "=...";
}
+ else if (type == "Switch") {
+ help += " | --" + helpitem.first + "=yes";
+ help += " | --" + helpitem.first + "=no";
+ }
+
+ help += "\n\t";
+ help += helpitem.second;
+ help += "\n";
+ }
return help;
}
-const string ArgvMap::formatOne(bool running, bool full, const string &var, const string &help, const string& theDefault, const string& current)
+string ArgvMap::formatOne(bool running, bool full, const string& var, const string& help, const string& theDefault, const string& current)
{
string out;
out += "\t";
out += help;
out += "\n#\n";
- } else {
+ }
+ else {
if (theDefault == current) {
return "";
}
}
- if (! running || theDefault == current) {
+ if (!running || theDefault == current) {
out += "# ";
}
if (full) {
out += "\n";
}
- } else {
+ }
+ else {
out += var + "=" + theDefault + "\n\n";
}
{
string help;
- if (running)
- help="# Autogenerated configuration file based on running instance ("+nowTime()+")\n\n";
- else
- help="# Autogenerated configuration file template\n\n";
+ if (running) {
+ help = "# Autogenerated configuration file based on running instance (" + nowTime() + ")\n\n";
+ }
+ else {
+ help = "# Autogenerated configuration file template\n\n";
+ }
// Affects parsing, should come first.
help += formatOne(running, full, "ignore-unknown-settings", helpmap["ignore-unknown-settings"], defaultmap["ignore-unknown-settings"], d_params["ignore-unknown-settings"]);
- for(const auto& i: helpmap) {
- if (d_typeMap[i.first] == "Command")
+ for (const auto& helpitem : helpmap) {
+ if (d_typeMap[helpitem.first] == "Command") {
continue;
- if (i.first == "ignore-unknown-settings")
+ }
+ if (helpitem.first == "ignore-unknown-settings") {
continue;
+ }
- if (!defaultmap.count(i.first)) {
- throw ArgException(string("Default for setting '")+i.first+"' not set");
+ if (defaultmap.count(helpitem.first) == 0) {
+ throw ArgException(string("Default for setting '") + helpitem.first + "' not set");
}
- help += formatOne(running, full, i.first, i.second, defaultmap[i.first], d_params[i.first]);
+ help += formatOne(running, full, helpitem.first, helpitem.second, defaultmap[helpitem.first], d_params[helpitem.first]);
}
if (running) {
- for(const auto& i: d_unknownParams) {
- help += formatOne(running, full, i.first, "unknown setting", "", i.second);
+ for (const auto& unknown : d_unknownParams) {
+ help += formatOne(running, full, unknown.first, "unknown setting", "", unknown.second);
}
}
return help;
}
-const string & ArgvMap::operator[](const string &arg)
+const string& ArgvMap::operator[](const string& arg)
{
- if(!parmIsset(arg))
- throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+ if (!parmIsset(arg)) {
+ throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+ }
return d_params[arg];
}
-mode_t ArgvMap::asMode(const string &arg)
+mode_t ArgvMap::asMode(const string& arg)
{
- mode_t mode;
- const char *cptr_orig;
- char *cptr_ret = nullptr;
+ if (!parmIsset(arg)) {
+ throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+ }
- if(!parmIsset(arg))
- throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+ const auto* const cptr_orig = d_params[arg].c_str();
+ char* cptr_ret = nullptr;
- cptr_orig = d_params[arg].c_str();
- mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8));
- if (mode == 0 && cptr_ret == cptr_orig)
+ auto mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8));
+ if (mode == 0 && cptr_ret == cptr_orig) {
throw ArgException("'" + arg + string("' contains invalid octal mode"));
- return mode;
+ }
+ return mode;
}
-gid_t ArgvMap::asGid(const string &arg)
+gid_t ArgvMap::asGid(const string& arg)
{
- gid_t gid;
- const char *cptr_orig;
- char *cptr_ret = nullptr;
-
- if(!parmIsset(arg))
- throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+ if (!parmIsset(arg)) {
+ throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+ }
- cptr_orig = d_params[arg].c_str();
- gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0));
+ const auto* cptr_orig = d_params[arg].c_str();
+ char* cptr_ret = nullptr;
+ auto gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0));
if (gid == 0 && cptr_ret == cptr_orig) {
// try to resolve
- struct group *group = getgrnam(d_params[arg].c_str());
- if (group == nullptr)
- throw ArgException("'" + arg + string("' contains invalid group"));
+
+ struct group* group = getgrnam(d_params[arg].c_str()); // NOLINT: called before going multi-threaded
+ if (group == nullptr) {
+ throw ArgException("'" + arg + string("' contains invalid group"));
+ }
gid = group->gr_gid;
- }
- return gid;
+ }
+ return gid;
}
-uid_t ArgvMap::asUid(const string &arg)
+uid_t ArgvMap::asUid(const string& arg)
{
- uid_t uid;
- const char *cptr_orig;
- char *cptr_ret = nullptr;
+ if (!parmIsset(arg)) {
+ throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+ }
- if(!parmIsset(arg))
- throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+ const auto* cptr_orig = d_params[arg].c_str();
+ char* cptr_ret = nullptr;
- cptr_orig = d_params[arg].c_str();
- uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0));
+ auto uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0));
if (uid == 0 && cptr_ret == cptr_orig) {
// try to resolve
- struct passwd *pwent = getpwnam(d_params[arg].c_str());
- if (pwent == nullptr)
- throw ArgException("'" + arg + string("' contains invalid group"));
+ struct passwd* pwent = getpwnam(d_params[arg].c_str()); // NOLINT: called before going multi-threaded
+ if (pwent == nullptr) {
+ throw ArgException("'" + arg + string("' contains invalid group"));
+ }
uid = pwent->pw_uid;
- }
- return uid;
+ }
+ return uid;
}
-int ArgvMap::asNum(const string &arg, int def)
+int ArgvMap::asNum(const string& arg, int def)
{
- int retval;
- const char *cptr_orig;
- char *cptr_ret = nullptr;
-
- if(!parmIsset(arg))
- throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+ if (!parmIsset(arg)) {
+ throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+ }
// use default for empty values
- if (d_params[arg].empty())
- return def;
+ if (d_params[arg].empty()) {
+ return def;
+ }
- cptr_orig = d_params[arg].c_str();
- retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0));
- if (!retval && cptr_ret == cptr_orig)
- throw ArgException("'"+arg+"' value '"+string(cptr_orig) + string( "' is not a valid number"));
+ const auto* cptr_orig = d_params[arg].c_str();
+ char* cptr_ret = nullptr;
+ auto retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0));
+ if (retval == 0 && cptr_ret == cptr_orig) {
+ throw ArgException("'" + arg + "' value '" + string(cptr_orig) + string("' is not a valid number"));
+ }
return retval;
}
-bool ArgvMap::isEmpty(const string &arg)
+bool ArgvMap::isEmpty(const string& arg)
{
- if(!parmIsset(arg))
+ if (!parmIsset(arg)) {
return true;
- return d_params[arg].empty();
+ }
+ return d_params[arg].empty();
}
-double ArgvMap::asDouble(const string &arg)
+double ArgvMap::asDouble(const string& arg)
{
- double retval;
- const char *cptr_orig;
- char *cptr_ret = nullptr;
-
- if(!parmIsset(arg))
- throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+ if (!parmIsset(arg)) {
+ throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+ }
- if (d_params[arg].empty())
- return 0.0;
+ if (d_params[arg].empty()) {
+ return 0.0;
+ }
- cptr_orig = d_params[arg].c_str();
- retval = strtod(cptr_orig, &cptr_ret);
+ const auto* cptr_orig = d_params[arg].c_str();
+ char* cptr_ret = nullptr;
+ auto retval = strtod(cptr_orig, &cptr_ret);
- if (retval == 0 && cptr_ret == cptr_orig)
- throw ArgException("'"+arg+string("' is not valid double"));
+ if (retval == 0 && cptr_ret == cptr_orig) {
+ throw ArgException("'" + arg + string("' is not valid double"));
+ }
return retval;
}
ArgvMap::ArgvMap()
{
- set("ignore-unknown-settings","Configuration settings to ignore if they are unknown")="";
+ set("ignore-unknown-settings", "Configuration settings to ignore if they are unknown") = "";
}
-bool ArgvMap::parmIsset(const string &var)
+bool ArgvMap::parmIsset(const string& var)
{
return d_params.find(var) != d_params.end();
}
// ATM Shared between Recursor and Auth, is that a good idea?
-static const map<string,string> deprecateList = {
- { "stats-api-blacklist", "stats-api-disabled-list" },
- { "stats-carbon-blacklist", "stats-carbon-disabled-list" },
- { "stats-rec-control-blacklist", "stats-rec-control-disabled-list" },
- { "stats-snmp-blacklist", "stats-snmp-disabled-list" },
- { "edns-subnet-whitelist", "edns-subnet-allow-list" },
- { "new-domain-whitelist", "new-domain-ignore-list" },
- { "snmp-master-socket", "snmp-daemon-socket" },
- { "xpf-allow-from", "Proxy Protocol" },
- { "xpf-rr-code", "Proxy Protocol" },
+static const map<string, string> deprecateList = {
+ {"stats-api-blacklist", "stats-api-disabled-list"},
+ {"stats-carbon-blacklist", "stats-carbon-disabled-list"},
+ {"stats-rec-control-blacklist", "stats-rec-control-disabled-list"},
+ {"stats-snmp-blacklist", "stats-snmp-disabled-list"},
+ {"edns-subnet-whitelist", "edns-subnet-allow-list"},
+ {"new-domain-whitelist", "new-domain-ignore-list"},
+ {"snmp-master-socket", "snmp-daemon-socket"},
+ {"xpf-allow-from", "Proxy Protocol"},
+ {"xpf-rr-code", "Proxy Protocol"},
+ {"domain-metadata-cache-ttl", "zone-metadata-cache-ttl"},
};
-void ArgvMap::warnIfDeprecated(const string& var)
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
+void ArgvMap::warnIfDeprecated(const string& var) const
{
const auto msg = deprecateList.find(var);
if (msg != deprecateList.end()) {
}
}
-void ArgvMap::parseOne(const string &arg, const string &parseOnly, bool lax)
+string ArgvMap::isDeprecated(const string& var)
{
- string var, val;
- string::size_type pos;
+ const auto msg = deprecateList.find(var);
+ return msg != deprecateList.end() ? msg->second : "";
+}
+
+void ArgvMap::parseOne(const string& arg, const string& parseOnly, bool lax)
+{
+ string var;
+ string val;
+ string::size_type pos = 0;
bool incremental = false;
- if(arg.find("--") == 0 && (pos=arg.find("+="))!=string::npos) // this is a --port+=25 case
+ if (arg.find("--") == 0 && (pos = arg.find("+=")) != string::npos) // this is a --port+=25 case
{
- var=arg.substr(2,pos-2);
- val=arg.substr(pos+2);
+ var = arg.substr(2, pos - 2);
+ val = arg.substr(pos + 2);
incremental = true;
}
- else if(arg.find("--") == 0 && (pos=arg.find('='))!=string::npos) // this is a --port=25 case
+ else if (arg.find("--") == 0 && (pos = arg.find('=')) != string::npos) // this is a --port=25 case
{
- var=arg.substr(2,pos-2);
- val=arg.substr(pos+1);
+ var = arg.substr(2, pos - 2);
+ val = arg.substr(pos + 1);
}
- else if(arg.find("--") == 0 && (arg.find('=')==string::npos)) // this is a --daemon case
+ else if (arg.find("--") == 0 && (arg.find('=') == string::npos)) // this is a --daemon case
{
- var=arg.substr(2);
- val="";
+ var = arg.substr(2);
+ val = "";
}
- else if(arg[0]=='-' && arg.length() > 1)
- {
- var=arg.substr(1);
- val="";
+ else if (arg[0] == '-' && arg.length() > 1) {
+ var = arg.substr(1);
+ val = "";
}
- else // command
+ else { // command
d_cmds.push_back(arg);
+ }
boost::trim(var);
- if(var!="" && (parseOnly.empty() || var==parseOnly)) {
+ if (!var.empty() && (parseOnly.empty() || var == parseOnly)) {
if (!lax) {
warnIfDeprecated(var);
}
- pos=val.find_first_not_of(" \t"); // strip leading whitespace
- if(pos && pos!=string::npos)
- val=val.substr(pos);
- if(parmIsset(var))
- {
- if(incremental)
- {
- if(d_params[var].empty())
- {
- if(!d_cleared.count(var))
- throw ArgException("Incremental setting '"+var+"' without a parent");
+ pos = val.find_first_not_of(" \t"); // strip leading whitespace
+ if (pos != 0 && pos != string::npos) {
+ val = val.substr(pos);
+ }
+ if (parmIsset(var)) {
+ if (incremental) {
+ if (d_params[var].empty()) {
+ if (d_cleared.count(var) == 0) {
+ throw ArgException("Incremental setting '" + var + "' without a parent");
+ }
d_params[var] = val;
}
- else
+ else {
d_params[var] += ", " + val;
+ }
}
- else
- {
+ else {
d_params[var] = val;
d_cleared.insert(var);
}
}
- else
- {
+ else {
// unknown setting encountered. see if its on the ignore list before throwing.
vector<string> parts;
stringtok(parts, d_params["ignore-unknown-settings"], " ,\t\n\r");
if (find(parts.begin(), parts.end(), var) != parts.end()) {
- d_unknownParams[var] = val;
- SLOG(g_log<<Logger::Warning<<"Ignoring unknown setting '"<<var<<"' as requested"<<endl,
+ d_unknownParams[var] = std::move(val);
+ SLOG(g_log << Logger::Warning << "Ignoring unknown setting '" << var << "' as requested" << endl,
d_log->info(Logr::Warning, "Ignoring unknown setting as requested", "name", Logging::Loggable(var)));
return;
}
if (!lax) {
- throw ArgException("Trying to set unknown setting '"+var+"'");
+ throw ArgException("Trying to set unknown setting '" + var + "'");
}
}
}
}
-const vector<string>&ArgvMap::getCommands()
+const vector<string>& ArgvMap::getCommands()
{
return d_cmds;
}
-void ArgvMap::parse(int &argc, char **argv, bool lax)
+void ArgvMap::parse(int& argc, char** argv, bool lax)
{
d_cmds.clear();
d_cleared.clear();
- for(int n=1;n<argc;n++) {
- parseOne(argv[n],"",lax);
+ for (int i = 1; i < argc; i++) {
+ parseOne(argv[i], "", lax); // NOLINT: Posix argument parsing
}
}
-void ArgvMap::preParse(int &argc, char **argv, const string &arg)
+void ArgvMap::preParse(int& argc, char** argv, const string& arg)
{
- for(int n=1;n<argc;n++) {
- string varval=argv[n];
- if(varval.find("--"+arg) == 0)
- parseOne(argv[n]);
+ for (int i = 1; i < argc; i++) {
+ string varval = argv[i]; // NOLINT: Posix argument parsing
+ if (varval.find("--" + arg) == 0) {
+ parseOne(argv[i]); // NOLINT: Posix argument parsing
+ }
}
}
-bool ArgvMap::parseFile(const char* fname, const string& arg, bool lax)
+bool ArgvMap::parseFile(const string& fname, const string& arg, bool lax)
{
string line;
string pline;
return true;
}
-bool ArgvMap::preParseFile(const char *fname, const string &arg, const string& theDefault)
+bool ArgvMap::preParseFile(const string& fname, const string& arg, const string& theDefault)
{
d_params[arg] = theDefault;
return parseFile(fname, arg, false);
}
-bool ArgvMap::file(const char* fname, bool lax)
+bool ArgvMap::file(const string& fname, bool lax)
{
return file(fname, lax, false);
}
-bool ArgvMap::file(const char* fname, bool lax, bool included)
+bool ArgvMap::file(const string& fname, bool lax, bool included)
{
if (!parmIsset("include-dir")) { // inject include-dir
set("include-dir", "Directory to include configuration files from");
// handle include here (avoid re-include)
if (!included && !d_params["include-dir"].empty()) {
std::vector<std::string> extraConfigs;
- gatherIncludes(extraConfigs);
+ gatherIncludes(d_params["include-dir"], ".conf", extraConfigs);
for (const std::string& filename : extraConfigs) {
if (!file(filename.c_str(), lax, true)) {
SLOG(g_log << Logger::Error << filename << " could not be parsed" << std::endl,
return true;
}
-void ArgvMap::gatherIncludes(std::vector<std::string> &extraConfigs) {
- extraConfigs.clear();
- if (d_params["include-dir"].empty())
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
+void ArgvMap::gatherIncludes(const std::string& directory, const std::string& suffix, std::vector<std::string>& extraConfigs)
+{
+ if (directory.empty()) {
return; // nothing to do
-
- DIR *dir;
- if (!(dir = opendir(d_params["include-dir"].c_str()))) {
- int err = errno;
- string msg = d_params["include-dir"] + " is not accessible: " + strerror(err);
- SLOG(g_log << Logger::Error << msg << std::endl,
- d_log->error(Logr::Error, err, "Directory is not accessible", "name", Logging::Loggable(d_params["include-dir"])));
- throw ArgException(msg);
}
- struct dirent *ent;
- while ((ent = readdir(dir)) != nullptr) {
- if (ent->d_name[0] == '.')
- continue; // skip any dots
- if (boost::ends_with(ent->d_name, ".conf")) {
+ std::vector<std::string> vec;
+ auto directoryError = pdns::visit_directory(directory, [this, &directory, &suffix, &vec]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+ (void)this;
+ if (boost::starts_with(name, ".")) {
+ return true; // skip any dots
+ }
+ if (boost::ends_with(name, suffix)) {
// build name
- string name = d_params["include-dir"] + "/" + ent->d_name; // FIXME: Use some path separator
+ string fullName = directory + "/" + std::string(name);
// ensure it's readable file
- struct stat st;
- if (stat(name.c_str(), &st) || !S_ISREG(st.st_mode)) {
- string msg = name + " is not a regular file";
+ struct stat statInfo
+ {
+ };
+ if (stat(fullName.c_str(), &statInfo) != 0 || !S_ISREG(statInfo.st_mode)) {
+ string msg = fullName + " is not a regular file";
SLOG(g_log << Logger::Error << msg << std::endl,
- d_log->info(Logr::Error, "Unable to open non-regular file", "name", Logging::Loggable(name)));
- closedir(dir);
+ d_log->info(Logr::Error, "Unable to open non-regular file", "name", Logging::Loggable(fullName)));
throw ArgException(msg);
}
- extraConfigs.push_back(name);
+ vec.emplace_back(fullName);
}
+ return true;
+ });
+
+ if (directoryError) {
+ int err = errno;
+ string msg = directory + " is not accessible: " + stringerror(err);
+ SLOG(g_log << Logger::Error << msg << std::endl,
+ d_log->error(Logr::Error, err, "Directory is not accessible", "name", Logging::Loggable(directory)));
+ throw ArgException(msg);
}
- std::sort(extraConfigs.begin(), extraConfigs.end(), CIStringComparePOSIX());
- closedir(dir);
+
+ std::sort(vec.begin(), vec.end(), CIStringComparePOSIX());
+ extraConfigs.insert(extraConfigs.end(), vec.begin(), vec.end());
}
#include "namespaces.hh"
#include "logging.hh"
-typedef PDNSException ArgException;
+using ArgException = PDNSException;
/** This class helps parsing argc and argv into a map of parameters. We have 3 kinds of formats:
\endcode
*/
-
-
class ArgvMap
{
public:
ArgvMap();
- void parse(int &argc, char **argv, bool lax=false); //!< use this to parse from argc and argv
- void laxParse(int &argc, char **argv) //!< use this to parse from argc and argv
+ void parse(int& argc, char** argv, bool lax = false); //!< use this to parse from argc and argv
+ void laxParse(int& argc, char** argv) //!< use this to parse from argc and argv
{
- parse(argc,argv,true);
+ parse(argc, argv, true);
}
- void preParse(int &argc, char **argv, const string &arg); //!< use this to preparse a single var
- bool preParseFile(const char *fname, const string &arg, const string& theDefault=""); //!< use this to preparse a single var in configuration
+ void preParse(int& argc, char** argv, const string& arg); //!< use this to preparse a single var
+ bool preParseFile(const string& fname, const string& arg, const string& theDefault = ""); //!< use this to preparse a single var in configuration
- bool file(const char* fname, bool lax = false); //!< Parses a file with parameters
- bool file(const char* fname, bool lax, bool included);
- bool laxFile(const char *fname)
+ bool file(const string& fname, bool lax = false); //!< Parses a file with parameters
+ bool file(const string& fname, bool lax, bool included);
+ bool laxFile(const string& fname)
{
- return file(fname,true);
+ return file(fname, true);
}
- bool parseFile(const char *fname, const string& arg, bool lax); //<! parse one line
- typedef map<string,string> param_t; //!< use this if you need to know the content of the map
- bool parmIsset(const string &var); //!< Checks if a parameter is set to *a* value
- bool mustDo(const string &var); //!< if a switch is given, if we must do something (--help)
- int asNum(const string &var, int def=0); //!< return a variable value as a number or the default if the variable is empty
- mode_t asMode(const string &var); //!< return value interpreted as octal number
- uid_t asUid(const string &var); //!< return user id, resolves if necessary
- gid_t asGid(const string &var); //!< return group id, resolves if necessary
- double asDouble(const string &var); //!< return a variable value as a number
- string &set(const string &); //!< Gives a writable reference and allocates space for it
- string &set(const string &, const string &); //!< Does the same but also allows one to specify a help message
- void setCmd(const string &, const string &); //!< Add a command flag
- string &setSwitch(const string &, const string &); //!< Add a switch flag
- string helpstring(string prefix=""); //!< generates the --help
- string configstring(bool current, bool full); //!< generates the --config
- bool contains(const string &var, const string &val);
- bool isEmpty(const string &var); //!< checks if variable has value
- void setDefault(const string &var, const string &value);
+ bool parseFile(const string& fname, const string& arg, bool lax); //<! parse one line
+ bool parmIsset(const string& var); //!< Checks if a parameter is set to *a* value
+ bool mustDo(const string& var); //!< if a switch is given, if we must do something (--help)
+ int asNum(const string& arg, int def = 0); //!< return a variable value as a number or the default if the variable is empty
+ mode_t asMode(const string& arg); //!< return value interpreted as octal number
+ uid_t asUid(const string& arg); //!< return user id, resolves if necessary
+ gid_t asGid(const string& arg); //!< return group id, resolves if necessary
+ double asDouble(const string& arg); //!< return a variable value as a number
+ string& set(const string&); //!< Gives a writable reference and allocates space for it
+ string& set(const string&, const string&); //!< Does the same but also allows one to specify a help message
+ void setCmd(const string&, const string&); //!< Add a command flag
+ string& setSwitch(const string&, const string&); //!< Add a switch flag
+ string helpstring(string prefix = ""); //!< generates the --help
+ string configstring(bool running, bool full); //!< generates the --config
+ bool contains(const string& var, const string& val);
+ bool isEmpty(const string& arg); //!< checks if variable has value
+ void setDefault(const string& var, const string& value);
void setDefaults();
- vector<string>list();
- string getHelp(const string &item);
-
- const param_t::const_iterator begin(); //!< iterator semantics
- const param_t::const_iterator end(); //!< iterator semantics
- const string &operator[](const string &); //!< iterator semantics
- const vector<string>&getCommands();
- void gatherIncludes(std::vector<std::string> &extraConfigs);
+ vector<string> list();
+ [[nodiscard]] string getHelp(const string& item) const
+ {
+ auto iter = helpmap.find(item);
+ return iter == helpmap.end() ? "" : iter->second;
+ }
+ [[nodiscard]] string getDefault(const string& item) const
+ {
+ auto iter = defaultmap.find(item);
+ return iter == defaultmap.end() ? "" : iter->second;
+ }
+ using param_t = map<string, string>; //!< use this if you need to know the content of the map
+
+ param_t::const_iterator begin(); //!< iterator semantics
+ param_t::const_iterator end(); //!< iterator semantics
+ const string& operator[](const string&); //!< iterator semantics
+ const vector<string>& getCommands();
+ void gatherIncludes(const std::string& dir, const std::string& suffix, std::vector<std::string>& extraConfigs);
+ void warnIfDeprecated(const string& var) const;
+ [[nodiscard]] static string isDeprecated(const string& var);
#ifdef RECURSOR
void setSLog(Logr::log_t log)
{
}
#endif
private:
- void warnIfDeprecated(const string& var);
- void parseOne(const string &unparsed, const string &parseOnly="", bool lax=false);
- const string formatOne(bool running, bool full, const string &var, const string &help, const string& theDefault, const string& value);
- map<string,string> d_params;
- map<string,string> d_unknownParams;
- map<string,string> helpmap;
- map<string,string> defaultmap;
- map<string,string> d_typeMap;
+ void parseOne(const string& arg, const string& parseOnly = "", bool lax = false);
+ static string formatOne(bool running, bool full, const string& var, const string& help, const string& theDefault, const string& current);
+ map<string, string> d_params;
+ map<string, string> d_unknownParams;
+ map<string, string> helpmap;
+ map<string, string> defaultmap;
+ map<string, string> d_typeMap;
vector<string> d_cmds;
std::set<string> d_cleared;
#ifdef RECURSOR
#endif
};
-extern ArgvMap &arg();
+extern ArgvMap& arg();
}
if (!d_group.empty()) {
json11::Json::array entries;
- for (const string& group : d_group) {
+ for (const auto& group : d_group) {
entries.push_back(group);
}
object["group"] = entries;
void CatalogInfo::updateHash(CatalogHashMap& hashes, const DomainInfo& di) const
{
- hashes[di.catalog].process(static_cast<char>(di.id) + di.zone.toLogString() + "\0" + d_coo.toLogString() + "\0" + d_unique.toLogString());
+ hashes[di.catalog].process(std::to_string(di.id) + di.zone.toLogString() + string("\0", 1) + d_coo.toLogString() + string("\0", 1) + d_unique.toLogString());
+ for (const auto& group : d_group) {
+ hashes[di.catalog].process(std::to_string(group.length()) + group);
+ }
}
DNSZoneRecord CatalogInfo::getCatalogVersionRecord(const DNSName& zone)
#include "ext/json11/json11.hpp"
#include "base32.hh"
-#include "dnsbackend.hh"
#include "dnssecinfra.hh"
struct DomainInfo;
#endif
#include "auth-main.hh"
+#include "coverage.hh"
#include "secpoll-auth.hh"
#include "dynhandler.hh"
#include "dnsseckeeper.hh"
int g_luaRecordExecLimit;
time_t g_luaHealthChecksInterval{5};
time_t g_luaHealthChecksExpireDelay{3600};
+time_t g_luaConsistentHashesExpireDelay{86400};
+time_t g_luaConsistentHashesCleanupInterval{3600};
#endif
#ifdef ENABLE_GSS_TSIG
bool g_doGssTSIG;
::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values") = "512";
::arg().setSwitch("send-signed-notify", "Send TSIG secured NOTIFY if TSIG key is configured for a zone") = "yes";
::arg().set("allow-unsigned-notify", "Allow unsigned notifications for TSIG secured zones") = "yes"; // FIXME: change to 'no' later
- ::arg().set("allow-unsigned-supermaster", "Allow supermasters to create zones without TSIG signed NOTIFY") = "yes";
::arg().set("allow-unsigned-autoprimary", "Allow autoprimaries to create zones without TSIG signed NOTIFY") = "yes";
- ::arg().setSwitch("forward-dnsupdate", "A global setting to allow DNS update packages that are for a Slave zone, to be forwarded to the master.") = "yes";
+ ::arg().setSwitch("forward-dnsupdate", "A global setting to allow DNS update packages that are for a Secondary zone, to be forwarded to the primary.") = "yes";
::arg().setSwitch("log-dns-details", "If PDNS should log DNS non-erroneous details") = "no";
::arg().setSwitch("log-dns-queries", "If PDNS should log all incoming DNS queries") = "no";
::arg().set("local-address", "Local IP addresses to which we bind") = "0.0.0.0, ::";
::arg().set("overload-queue-length", "Maximum queuelength moving to packetcache only") = "0";
::arg().set("max-queue-length", "Maximum queuelength before considering situation lost") = "5000";
- ::arg().set("retrieval-threads", "Number of AXFR-retrieval threads for slave operation") = "2";
+ ::arg().set("retrieval-threads", "Number of AXFR-retrieval threads for secondary operation") = "2";
::arg().setSwitch("api", "Enable/disable the REST API (including HTTP listener)") = "no";
::arg().set("api-key", "Static pre-shared authentication key for access to the REST API") = "";
::arg().setSwitch("default-api-rectify", "Default API-RECTIFY value for zones") = "yes";
::arg().set("version-string", "PowerDNS version in packets - full, anonymous, powerdns or custom") = "full";
::arg().set("control-console", "Debugging switch - don't use") = "no"; // but I know you will!
::arg().set("loglevel", "Amount of logging. Higher is more. Do not set below 3") = "4";
- ::arg().set("disable-syslog", "Disable logging to syslog, useful when running inside a supervisor that logs stdout") = "no";
+ ::arg().setSwitch("loglevel-show", "Include log level indicator in log output") = "no";
+ ::arg().set("disable-syslog", "Disable logging to syslog, useful when running inside a supervisor that logs stderr") = "no";
::arg().set("log-timestamp", "Print timestamps in log lines") = "yes";
::arg().set("distributor-threads", "Default number of Distributor (backend) threads to start") = "3";
::arg().set("signing-threads", "Default number of signer threads to start") = "3";
+ ::arg().setSwitch("workaround-11804", "Workaround for issue 11804: send single RR per AXFR chunk") = "no";
::arg().set("receiver-threads", "Default number of receiver threads to start") = "1";
::arg().set("queue-limit", "Maximum number of milliseconds to queue a query") = "1500";
::arg().set("resolver", "Use this resolver for ALIAS and the internal stub resolver") = "no";
::arg().set("only-notify", "Only send AXFR NOTIFY to these IP addresses or netmasks") = "0.0.0.0/0,::/0";
::arg().set("also-notify", "When notifying a zone, also notify these nameservers") = "";
::arg().set("allow-notify-from", "Allow AXFR NOTIFY from these IP ranges. If empty, drop all incoming notifies.") = "0.0.0.0/0,::/0";
- ::arg().set("slave-cycle-interval", "Schedule slave freshness checks once every .. seconds") = "";
::arg().set("xfr-cycle-interval", "Schedule primary/secondary SOA freshness checks once every .. seconds") = "60";
::arg().set("secondary-check-signature-freshness", "Check signatures in SOA freshness check. Sets DO flag on SOA queries. Outside some very problematic scenarios, say yes here.") = "yes";
::arg().set("tcp-control-secret", "If set, PowerDNS can be controlled over TCP after passing this secret") = "";
::arg().set("tcp-control-range", "If set, remote control of PowerDNS is possible over these networks only") = "127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fe80::/10";
- ::arg().setSwitch("slave", "Act as a secondary") = "no";
::arg().setSwitch("secondary", "Act as a secondary") = "no";
- ::arg().setSwitch("master", "Act as a primary") = "no";
::arg().setSwitch("primary", "Act as a primary") = "no";
- ::arg().setSwitch("superslave", "Act as a autosecondary") = "no";
- ::arg().setSwitch("autosecondary", "Act as an autosecondary (formerly superslave)") = "no";
+ ::arg().setSwitch("autosecondary", "Act as an autosecondary") = "no";
::arg().setSwitch("disable-axfr-rectify", "Disable the rectify step during an outgoing AXFR. Only required for regression testing.") = "no";
::arg().setSwitch("guardian", "Run within a guardian process") = "no";
::arg().setSwitch("prevent-self-notification", "Don't send notifications to what we think is ourself") = "yes";
::arg().setSwitch("any-to-tcp", "Answer ANY queries with tc=1, shunting to TCP") = "yes";
::arg().setSwitch("edns-subnet-processing", "If we should act on EDNS Subnet options") = "no";
+ ::arg().set("delay-notifications", "Configure a delay to send out notifications, no delay by default") = "0";
::arg().set("edns-cookie-secret", "When set, set a server cookie when responding to a query with a Client cookie (in hex)") = "";
::arg().set("zone-metadata-cache-ttl", "Seconds to cache zone metadata from the database") = "60";
::arg().set("trusted-notification-proxy", "IP address of incoming notification proxy") = "";
- ::arg().set("slave-renotify", "If we should send out notifications for secondaried updates") = "no";
::arg().set("secondary-do-renotify", "If this secondary should send out notifications after receiving zone transfers from a primary") = "no";
- ::arg().set("forward-notify", "IP addresses to forward received notifications to regardless of master or slave settings") = "";
+ ::arg().set("forward-notify", "IP addresses to forward received notifications to regardless of primary or secondary settings") = "";
::arg().set("default-ttl", "Seconds a result is valid if not set otherwise") = "3600";
::arg().set("max-tcp-connections", "Maximum number of TCP connections") = "20";
::arg().set("security-poll-suffix", "Zone name from which to query security update notifications") = "secpoll.powerdns.com.";
::arg().setSwitch("expand-alias", "Expand ALIAS records") = "no";
- ::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR") = "no";
+ ::arg().set("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR") = "no";
::arg().setSwitch("8bit-dns", "Allow 8bit dns queries") = "no";
#ifdef HAVE_LUA_RECORDS
::arg().setSwitch("enable-lua-records", "Process LUA records for all zones (metadata overrides this)") = "no";
::arg().set("lua-records-exec-limit", "LUA records scripts execution limit (instructions count). Values <= 0 mean no limit") = "1000";
::arg().set("lua-health-checks-expire-delay", "Stops doing health checks after the record hasn't been used for that delay (in seconds)") = "3600";
::arg().set("lua-health-checks-interval", "LUA records health checks monitoring interval in seconds") = "5";
+ ::arg().set("lua-consistent-hashes-cleanup-interval", "Pre-computed hashes cleanup interval (in seconds)") = "3600";
+ ::arg().set("lua-consistent-hashes-expire-delay", "Cleanup pre-computed hashes that haven't been used for the given delay (in seconds). See pickchashed() LUA function") = "86400";
#endif
- ::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a master with a lower serial") = "no";
+ ::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a primary with a lower serial") = "no";
::arg().set("lua-axfr-script", "Script to be used to edit incoming AXFRs") = "";
::arg().set("xfr-max-received-mbytes", "Maximum number of megabytes received from an incoming XFR") = "100";
::arg().setSwitch("consistent-backends", "Assume individual zones are not divided over backends. Send only ANY lookup operations to the backend to reduce the number of lookups") = "yes";
::arg().set("rng", "Specify the random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.") = "auto";
+
+ ::arg().set("default-catalog-zone", "Catalog zone to assign newly created primary zones (via the API) to") = "";
+
#ifdef ENABLE_GSS_TSIG
::arg().setSwitch("enable-gss-tsig", "Enable GSS TSIG processing") = "no";
#endif
static void mainthread()
{
- Utility::srandom();
-
gid_t newgid = 0;
if (!::arg()["setgid"].empty())
newgid = strToGID(::arg()["setgid"]);
g_LuaRecordSharedState = (::arg()["enable-lua-records"] == "shared");
g_luaRecordExecLimit = ::arg().asNum("lua-records-exec-limit");
g_luaHealthChecksInterval = ::arg().asNum("lua-health-checks-interval");
+ g_luaConsistentHashesExpireDelay = ::arg().asNum("lua-consistent-hashes-expire-delay");
+ g_luaConsistentHashesCleanupInterval = ::arg().asNum("lua-consistent-hashes-cleanup-interval");
g_luaHealthChecksExpireDelay = ::arg().asNum("lua-health-checks-expire-delay");
#endif
#ifdef ENABLE_GSS_TSIG
if (!PC.enabled() && ::arg().mustDo("log-dns-queries")) {
g_log << Logger::Warning << "Packet cache disabled, logging queries without HIT/MISS" << endl;
}
+ if (::arg()["outgoing-axfr-expand-alias"] == "ignore-errors") {
+ g_log << Logger::Error << "Ignoring ALIAS resolve failures on outgoing AXFR transfers, see option \"outgoing-axfr-expand-alias\"" << endl;
+ }
stubParseResolveConf();
// NOW SAFE TO CREATE THREADS!
s_dynListener->go();
- if (::arg().mustDo("webserver") || ::arg().mustDo("api"))
- webserver.go();
+ if (::arg().mustDo("webserver") || ::arg().mustDo("api")) {
+ webserver.go(S);
+ }
if (::arg().mustDo("primary") || ::arg().mustDo("secondary") || !::arg()["forward-notify"].empty())
Communicator.go();
}
#endif
+#ifdef COVERAGE
+static void sigTermHandler([[maybe_unused]] int signal)
+{
+ pdns::coverage::dumpCoverageData();
+ _exit(EXIT_SUCCESS);
+}
+#endif /* COVERAGE */
+
//! The main function of pdns, the pdns process
int main(int argc, char** argv)
{
if (::arg().mustDo("version")) {
showProductVersion();
showBuildConfiguration();
- exit(99);
+ return 0;
}
if (::arg()["config-name"] != "")
g_log << Logger::Error << "Unknown logging facility " << ::arg().asNum("logging-facility") << endl;
}
- if (::arg().mustDo("master"))
- ::arg().set("primary") = "yes";
- if (::arg().mustDo("slave"))
- ::arg().set("secondary") = "yes";
- if (::arg().mustDo("slave-renotify"))
- ::arg().set("secondary-do-renotify") = "yes";
- if (::arg().mustDo("superslave"))
- ::arg().set("autosecondary") = "yes";
- if (::arg().mustDo("allow-unsigned-supermaster"))
- ::arg().set("allow-unsigned-autoprimary") = "yes";
if (!::arg().isEmpty("domain-metadata-cache-ttl"))
::arg().set("zone-metadata-cache-ttl") = ::arg()["domain-metadata-cache-ttl"];
- if (!::arg().isEmpty("slave-cycle-interval"))
- ::arg().set("xfr-cycle-interval") = ::arg()["slave-cycle-interval"];
// this mirroring back is on purpose, so that config dumps reflect the actual setting on both names
- if (::arg().mustDo("primary"))
- ::arg().set("master") = "yes";
- if (::arg().mustDo("secondary"))
- ::arg().set("slave") = "yes";
- if (::arg().mustDo("secondary-do-renotify"))
- ::arg().set("slave-renotify") = "yes";
- if (::arg().mustDo("autosecondary"))
- ::arg().set("superslave") = "yes";
- if (::arg().mustDo("allow-unsigned-autoprimary"))
- ::arg().set("allow-unsigned-supermaster") = "yes";
::arg().set("domain-metadata-cache-ttl") = ::arg()["zone-metadata-cache-ttl"];
- ::arg().set("slave-cycle-interval") = ::arg()["xfr-cycle-interval"];
g_log.setLoglevel((Logger::Urgency)(::arg().asNum("loglevel")));
+ g_log.setPrefixed(::arg().mustDo("loglevel-show"));
g_log.disableSyslog(::arg().mustDo("disable-syslog"));
g_log.setTimestamps(::arg().mustDo("log-timestamp"));
g_log.toConsole((Logger::Urgency)(::arg().asNum("loglevel")));
cerr << "Um, we did get here!" << endl;
}
+#ifdef COVERAGE
+ if (!::arg().mustDo("guardian") && !::arg().mustDo("daemon")) {
+ signal(SIGTERM, sigTermHandler);
+ }
+#endif
+
// we really need to do work - either standalone or as an instance
#if defined(__GLIBC__) && !defined(__UCLIBC__)
openssl_thread_setup();
openssl_seed();
- /* setup rng */
- dns_random_init();
#ifdef HAVE_LUA_RECORDS
MiniCurl::init();
DynListener::registerFunc("RESPSIZES", &DLRSizesHandler, "get histogram of response sizes");
DynListener::registerFunc("REMOTES", &DLRemotesHandler, "get top remotes");
DynListener::registerFunc("SET", &DLSettingsHandler, "set config variables", "<var> <value>");
- DynListener::registerFunc("RETRIEVE", &DLNotifyRetrieveHandler, "retrieve slave zone", "<zone> [<ip>]");
+ DynListener::registerFunc("RETRIEVE", &DLNotifyRetrieveHandler, "retrieve secondary zone", "<zone> [<ip>]");
DynListener::registerFunc("CURRENT-CONFIG", &DLCurrentConfigHandler, "retrieve the current configuration", "[diff]");
DynListener::registerFunc("LIST-ZONES", &DLListZones, "show list of zones", "[primary|secondary|native|consumer|producer]");
DynListener::registerFunc("TOKEN-LOGIN", &DLTokenLogin, "Login to a PKCS#11 token", "<module> <slot> <pin>");
g_log << Logger::Error << "Fatal error: " << A.reason << endl;
exit(1);
}
+ catch (const std::exception& e) {
+ g_log << Logger::Error << "Fatal error: " << e.what() << endl;
+ exit(1);
+ }
try {
declareStats();
}
- catch (PDNSException& PE) {
+ catch (const PDNSException& PE) {
g_log << Logger::Error << "Exiting because: " << PE.reason << endl;
exit(1);
}
+
+ try {
+ auto defaultCatalog = ::arg()["default-catalog-zone"];
+ if (!defaultCatalog.empty()) {
+ auto defCatalog = DNSName(defaultCatalog);
+ }
+ }
+ catch (const std::exception& e) {
+ g_log << Logger::Error << "Invalid value '" << ::arg()["default-catalog-zone"] << "' for default-catalog-zone: " << e.what() << endl;
+ exit(1);
+ }
S.blacklist("special-memory-usage");
DLOG(g_log << Logger::Warning << "Verbose logging in effect" << endl);
try {
mainthread();
}
- catch (PDNSException& e) {
- if (!::arg().mustDo("daemon"))
- cerr << "Exiting because: " << e.reason << endl;
+ catch (const PDNSException& e) {
+ try {
+ if (!::arg().mustDo("daemon")) {
+ cerr << "Exiting because: " << e.reason << endl;
+ }
+ }
+ catch (const ArgException& A) {
+ }
g_log << Logger::Error << "Exiting because: " << e.reason << endl;
}
- catch (std::exception& e) {
- if (!::arg().mustDo("daemon"))
- cerr << "Exiting because of STL error: " << e.what() << endl;
+ catch (const std::exception& e) {
+ try {
+ if (!::arg().mustDo("daemon")) {
+ cerr << "Exiting because of STL error: " << e.what() << endl;
+ }
+ }
+ catch (const ArgException& A) {
+ }
g_log << Logger::Error << "Exiting because of STL error: " << e.what() << endl;
}
catch (...) {
extern bool g_LuaRecordSharedState;
extern time_t g_luaHealthChecksInterval;
extern time_t g_luaHealthChecksExpireDelay;
+extern time_t g_luaConsistentHashesExpireDelay;
+extern time_t g_luaConsistentHashesCleanupInterval;
#endif // HAVE_LUA_RECORDS
struct MapCombo
{
- MapCombo() {
- }
- ~MapCombo() {
- }
+ MapCombo() = default;
+ ~MapCombo() = default;
MapCombo(const MapCombo&) = delete;
MapCombo& operator=(const MapCombo&) = delete;
#include "namespaces.hh"
#include "query-local-address.hh"
-
void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B)
{
- bool hasQueuedItem=false;
+ bool hasQueuedItem = false;
set<string> ips;
set<DNSName> nsset;
DNSZoneRecord rr;
FindNS fns;
try {
- if (d_onlyNotify.size()) {
- B->lookup(QType(QType::NS), di.zone, di.id);
- while(B->get(rr))
- nsset.insert(getRR<NSRecordContent>(rr.dr)->getNS());
-
- for(const auto & ns : nsset) {
- vector<string> nsips=fns.lookup(ns, B);
- if(nsips.empty())
- g_log<<Logger::Warning<<"Unable to queue notification of domain '"<<di.zone<<"' to nameserver '"<<ns<<"': nameserver does not resolve!"<<endl;
- else
- for(const auto & nsip : nsips) {
- const ComboAddress caIp(nsip, 53);
- if(!d_preventSelfNotification || !AddressIsUs(caIp)) {
- if(!d_onlyNotify.match(&caIp))
- g_log<<Logger::Notice<<"Skipped notification of domain '"<<di.zone<<"' to "<<ns<<" because "<<caIp<<" does not match only-notify."<<endl;
- else
- ips.insert(caIp.toStringWithPort());
+ if (d_onlyNotify.size()) {
+ B->lookup(QType(QType::NS), di.zone, di.id);
+ while (B->get(rr))
+ nsset.insert(getRR<NSRecordContent>(rr.dr)->getNS());
+
+ for (const auto& ns : nsset) {
+ vector<string> nsips = fns.lookup(ns, B);
+ if (nsips.empty())
+ g_log << Logger::Warning << "Unable to queue notification of domain '" << di.zone << "' to nameserver '" << ns << "': nameserver does not resolve!" << endl;
+ else
+ for (const auto& nsip : nsips) {
+ const ComboAddress caIp(nsip, 53);
+ if (!d_preventSelfNotification || !AddressIsUs(caIp)) {
+ if (!d_onlyNotify.match(&caIp))
+ g_log << Logger::Notice << "Skipped notification of domain '" << di.zone << "' to " << ns << " because " << caIp << " does not match only-notify." << endl;
+ else
+ ips.insert(caIp.toStringWithPort());
+ }
}
- }
- }
+ }
- for(const auto & ip : ips) {
- g_log<<Logger::Notice<<"Queued notification of domain '"<<di.zone<<"' to "<<ip<<endl;
- d_nq.add(di.zone,ip);
- hasQueuedItem=true;
+ for (const auto& ip : ips) {
+ g_log << Logger::Notice << "Queued notification of domain '" << di.zone << "' to " << ip << endl;
+ d_nq.add(di.zone, ip, d_delayNotifications);
+ hasQueuedItem = true;
+ }
}
}
- }
- catch (PDNSException &ae) {
+ catch (PDNSException& ae) {
g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << ae.reason << endl;
return;
}
- catch (std::exception &e) {
+ catch (std::exception& e) {
g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << e.what() << endl;
return;
}
-
set<string> alsoNotify(d_alsoNotify);
B->alsoNotifies(di.zone, &alsoNotify);
- for(const auto & j : alsoNotify) {
+ for (const auto& j : alsoNotify) {
try {
const ComboAddress caIp(j, 53);
- g_log<<Logger::Notice<<"Queued also-notification of domain '"<<di.zone<<"' to "<<caIp.toStringWithPort()<<endl;
+ g_log << Logger::Notice << "Queued also-notification of domain '" << di.zone << "' to " << caIp.toStringWithPort() << endl;
if (!ips.count(caIp.toStringWithPort())) {
ips.insert(caIp.toStringWithPort());
- d_nq.add(di.zone, caIp.toStringWithPort());
+ d_nq.add(di.zone, caIp.toStringWithPort(), d_delayNotifications);
}
- hasQueuedItem=true;
+ hasQueuedItem = true;
}
- catch(PDNSException &e) {
- g_log<<Logger::Warning<<"Unparseable IP in ALSO-NOTIFY metadata of domain '"<<di.zone<<"'. Warning: "<<e.reason<<endl;
+ catch (PDNSException& e) {
+ g_log << Logger::Warning << "Unparseable IP in ALSO-NOTIFY metadata of domain '" << di.zone << "'. Warning: " << e.reason << endl;
}
}
if (!hasQueuedItem)
- g_log<<Logger::Warning<<"Request to queue notification for domain '"<<di.zone<<"' was processed, but no valid nameservers or ALSO-NOTIFYs found. Not notifying!"<<endl;
+ g_log << Logger::Warning << "Request to queue notification for domain '" << di.zone << "' was processed, but no valid nameservers or ALSO-NOTIFYs found. Not notifying!" << endl;
}
-
-bool CommunicatorClass::notifyDomain(const DNSName &domain, UeberBackend* B)
+bool CommunicatorClass::notifyDomain(const DNSName& domain, UeberBackend* B)
{
DomainInfo di;
- if(!B->getDomainInfo(domain, di)) {
- g_log<<Logger::Warning<<"No such domain '"<<domain<<"' in our database"<<endl;
+ if (!B->getDomainInfo(domain, di)) {
+ g_log << Logger::Warning << "No such domain '" << domain << "' in our database" << endl;
return false;
}
queueNotifyDomain(di, B);
void NotificationQueue::dump()
{
- cerr<<"Waiting for notification responses: "<<endl;
- for(NotificationRequest& nr : d_nqueue) {
- cerr<<nr.domain<<", "<<nr.ip<<endl;
+ cerr << "Waiting for notification responses: " << endl;
+ for (NotificationRequest& nr : d_nqueue) {
+ cerr << nr.domain << ", " << nr.ip << endl;
}
}
}
}
-void CommunicatorClass::masterUpdateCheck(PacketHandler *P)
+void CommunicatorClass::primaryUpdateCheck(PacketHandler* P)
{
- if(!::arg().mustDo("primary"))
+ if (!::arg().mustDo("primary"))
return;
- UeberBackend *B=P->getBackend();
+ UeberBackend* B = P->getBackend();
vector<DomainInfo> cmdomains;
std::unordered_set<DNSName> catalogs;
CatalogHashMap catalogHashes;
- B->getUpdatedMasters(cmdomains, catalogs, catalogHashes);
+ B->getUpdatedPrimaries(cmdomains, catalogs, catalogHashes);
getUpdatedProducers(B, cmdomains, catalogs, catalogHashes);
- if(cmdomains.empty()) {
+ if (cmdomains.empty()) {
g_log << Logger::Info << "no primary or producer domains need notifications" << endl;
}
else {
g_log << Logger::Info << cmdomains.size() << " domain" << addS(cmdomains.size()) << " for which we are primary or consumer need" << addS(cmdomains.size()) << " notifications" << endl;
}
- for(auto& di : cmdomains) {
+ for (auto& di : cmdomains) {
purgeAuthCachesExact(di.zone);
g_zoneCache.add(di.zone, di.id);
queueNotifyDomain(di, B);
return d_nq.earliest();
}
-void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackend *B)
+void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackend* B)
{
vector<string> meta;
DNSName tsigkeyname;
trc.d_algoName = tsigalgorithm;
trc.d_time = time(nullptr);
trc.d_fudge = 300;
- trc.d_origID=ntohs(id);
- trc.d_eRcode=0;
+ trc.d_origID = ntohs(id);
+ trc.d_eRcode = 0;
if (B64Decode(tsigsecret64, tsigsecret) == -1) {
- g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<tsigkeyname<<"' for domain '"<<domain<<"'"<<endl;
+ g_log << Logger::Error << "Unable to Base-64 decode TSIG key '" << tsigkeyname << "' for domain '" << domain << "'" << endl;
return;
}
addTSIG(pw, trc, tsigkeyname, tsigsecret, "", false);
}
- if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
- throw ResolverException("Unable to send notify to "+remote.toStringWithPort()+": "+stringerror());
+ if (sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
+ throw ResolverException("Unable to send notify to " + remote.toStringWithPort() + ": " + stringerror());
}
}
-void CommunicatorClass::drillHole(const DNSName &domain, const string &ip)
+void CommunicatorClass::drillHole(const DNSName& domain, const string& ip)
{
- (*d_holes.lock())[pair(domain,ip)]=time(nullptr);
+ (*d_holes.lock())[pair(domain, ip)] = time(nullptr);
}
-bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip)
+bool CommunicatorClass::justNotified(const DNSName& domain, const string& ip)
{
auto holes = d_holes.lock();
- auto it = holes->find(pair(domain,ip));
+ auto it = holes->find(pair(domain, ip));
if (it == holes->end()) {
// no hole
return false;
}
- if (it->second > time(nullptr)-900) {
+ if (it->second > time(nullptr) - 900) {
// recent hole
return true;
}
void CommunicatorClass::makeNotifySockets()
{
- if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
+ if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
d_nsock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind"));
- } else {
+ }
+ else {
d_nsock4 = -1;
}
- if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
+ if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
d_nsock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind"));
- } else {
+ }
+ else {
d_nsock6 = -1;
}
}
-void CommunicatorClass::notify(const DNSName &domain, const string &ip)
+void CommunicatorClass::notify(const DNSName& domain, const string& ip)
{
d_nq.add(domain, ip);
}
struct MapCombo
{
- MapCombo() {
- }
- ~MapCombo() {
- }
+ MapCombo() = default;
+ ~MapCombo() = default;
MapCombo(const MapCombo &) = delete;
MapCombo & operator=(const MapCombo &) = delete;
#include "ixfr.hh"
-void CommunicatorClass::addSuckRequest(const DNSName &domain, const ComboAddress& master, SuckRequest::RequestPriority priority, bool force)
+void CommunicatorClass::addSuckRequest(const DNSName& domain, const ComboAddress& primary, SuckRequest::RequestPriority priority, bool force)
{
auto data = d_data.lock();
SuckRequest sr;
sr.domain = domain;
- sr.master = master;
+ sr.primary = primary;
sr.force = force;
sr.priorityAndOrder.first = priority;
sr.priorityAndOrder.second = data->d_sorthelper++;
- pair<UniQueue::iterator, bool> res;
+ pair<UniQueue::iterator, bool> res;
res = data->d_suckdomains.insert(sr);
- if(res.second) {
+ if (res.second) {
d_suck_sem.post();
- } else {
- data->d_suckdomains.modify(res.first, [priorityAndOrder = sr.priorityAndOrder] (SuckRequest& so) {
+ }
+ else {
+ data->d_suckdomains.modify(res.first, [priorityAndOrder = sr.priorityAndOrder](SuckRequest& so) {
if (priorityAndOrder.first < so.priorityAndOrder.first) {
so.priorityAndOrder = priorityAndOrder;
}
{
bool isDnssecZone{false};
bool isPresigned{false};
- bool isNSEC3 {false};
- bool optOutFlag {false};
+ bool isNSEC3{false};
+ bool optOutFlag{false};
NSEC3PARAMRecordContent ns3pr;
bool isNarrow{false};
di.backend->setOptions(ciXFR.d_zone, ciDB.toJson());
}
- if (di.masters != ciDB.d_primaries) { // update primaries
+ if (di.primaries != ciDB.d_primaries) { // update primaries
if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
doTransaction = false;
}
vector<string> primaries;
- for (const auto& primary : di.masters) {
+ for (const auto& primary : di.primaries) {
primaries.push_back(primary.toStringWithPortExcept(53));
}
g_log << Logger::Warning << logPrefix << "update primaries for zone '" << ciXFR.d_zone << "' to '" << boost::join(primaries, ", ") << "'" << endl;
- di.backend->setMasters(ciXFR.d_zone, di.masters);
+ di.backend->setPrimaries(ciXFR.d_zone, di.primaries);
retrieve.emplace_back(ciXFR);
}
doTransaction = false;
}
- di.backend->setMasters(ciCreate.d_zone, di.masters);
+ di.backend->setPrimaries(ciCreate.d_zone, di.primaries);
di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
di.backend->setCatalog(ciCreate.d_zone, di.zone);
}
g_log << Logger::Warning << logPrefix << "create zone '" << ciCreate.d_zone << "'" << endl;
- di.backend->createDomain(ciCreate.d_zone, DomainInfo::Slave, ciCreate.d_primaries, "");
+ di.backend->createDomain(ciCreate.d_zone, DomainInfo::Secondary, ciCreate.d_primaries, "");
- di.backend->setMasters(ciCreate.d_zone, di.masters);
+ di.backend->setPrimaries(ciCreate.d_zone, di.primaries);
di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
di.backend->setCatalog(ciCreate.d_zone, di.zone);
}
// retrieve new and updated zones with new primaries
- auto masters = di.masters;
- if (!masters.empty()) {
+ auto primaries = di.primaries;
+ if (!primaries.empty()) {
for (auto& ret : retrieve) {
- shuffle(masters.begin(), masters.end(), pdns::dns_random_engine());
- const auto& master = masters.front();
- Communicator.addSuckRequest(ret.d_zone, master, SuckRequest::Notify);
+ shuffle(primaries.begin(), primaries.end(), pdns::dns_random_engine());
+ const auto& primary = primaries.front();
+ Communicator.addSuckRequest(ret.d_zone, primary, SuckRequest::Notify);
}
}
hasSOA = true;
continue;
}
+ if (rr.qtype == QType::NS) {
+ continue;
+ }
}
else if (rr.qname == DNSName("version") + di.zone && rr.qtype == QType::TXT) {
void CommunicatorClass::ixfrSuck(const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, ZoneStatus& zs, vector<DNSRecord>* axfr)
{
- string logPrefix="IXFR-in zone '"+domain.toLogString()+"', primary '"+remote.toString()+"', ";
+ string logPrefix = "IXFR-in zone '" + domain.toLogString() + "', primary '" + remote.toString() + "', ";
UeberBackend B; // fresh UeberBackend
DomainInfo di;
- di.backend=nullptr;
+ di.backend = nullptr;
// bool transaction=false;
try {
- DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper
+ DNSSECKeeper dk(&B); // reuse our UeberBackend copy for DNSSECKeeper
bool wrongDomainKind = false;
// this checks three error conditions, and sets wrongDomainKind if we hit the third & had an error
- if(!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, di.kind != DomainInfo::Slave)) { // di.backend and B are mostly identical
- if(wrongDomainKind)
- g_log<<Logger::Warning<<logPrefix<<"can't determine backend, not configured as slave"<<endl;
+ if (!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, di.kind != DomainInfo::Secondary)) { // di.backend and B are mostly identical
+ if (wrongDomainKind)
+ g_log << Logger::Warning << logPrefix << "can't determine backend, not configured as secondary" << endl;
else
- g_log<<Logger::Warning<<logPrefix<<"can't determine backend"<<endl;
+ g_log << Logger::Warning << logPrefix << "can't determine backend" << endl;
return;
}
uint16_t xfrTimeout = ::arg().asNum("axfr-fetch-timeout");
soatimes st;
memset(&st, 0, sizeof(st));
- st.serial=di.serial;
+ st.serial = di.serial;
DNSRecord drsoa;
drsoa.setContent(std::make_shared<SOARecordContent>(g_rootdnsname, g_rootdnsname, st));
- auto deltas = getIXFRDeltas(remote, domain, drsoa, xfrTimeout, false, tt, laddr.sin4.sin_family ? &laddr : nullptr, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
- zs.numDeltas=deltas.size();
+ auto deltas = getIXFRDeltas(remote, domain, drsoa, xfrTimeout, false, tt, laddr.sin4.sin_family ? &laddr : nullptr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
+ zs.numDeltas = deltas.size();
// cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
- for(const auto& d : deltas) {
+ for (const auto& d : deltas) {
const auto& remove = d.first;
const auto& add = d.second;
// cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl;
- if(remove.empty()) { // we got passed an AXFR!
+ if (remove.empty()) { // we got passed an AXFR!
*axfr = add;
return;
}
-
// our hammer is 'replaceRRSet(domain_id, qname, qt, vector<DNSResourceRecord>& rrset)
// which thinks in terms of RRSETs
// however, IXFR does not, and removes and adds *records* (bummer)
// this means that we must group updates by {qname,qtype}, retrieve the RRSET, apply
// the add/remove updates, and replaceRRSet the whole thing.
+ map<pair<DNSName, uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord>>> grouped;
- map<pair<DNSName,uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord> > > grouped;
-
- for(const auto& x: remove)
+ for (const auto& x : remove)
grouped[{x.d_name, x.d_type}].first.push_back(x);
- for(const auto& x: add)
+ for (const auto& x : add)
grouped[{x.d_name, x.d_type}].second.push_back(x);
di.backend->startTransaction(domain, -1);
- for(const auto& g : grouped) {
+ for (const auto& g : grouped) {
vector<DNSRecord> rrset;
{
DNSZoneRecord zrr;
- di.backend->lookup(QType(g.first.second), g.first.first+domain, di.id);
- while(di.backend->get(zrr)) {
+ di.backend->lookup(QType(g.first.second), g.first.first + domain, di.id);
+ while (di.backend->get(zrr)) {
zrr.dr.d_name.makeUsRelative(domain);
rrset.push_back(zrr.dr);
}
[&g](const DNSRecord& dr) {
return count(g.second.first.cbegin(),
g.second.first.cend(), dr);
- }), rrset.end());
+ }),
+ rrset.end());
// the DNSRecord== operator compares on name, type, class and lowercase content representation
- for(const auto& x : g.second.second) {
+ for (const auto& x : g.second.second) {
rrset.push_back(x);
}
vector<DNSResourceRecord> replacement;
- for(const auto& dr : rrset) {
+ for (const auto& dr : rrset) {
auto rr = DNSResourceRecord::fromWire(dr);
rr.qname += domain;
rr.domain_id = di.id;
- if(dr.d_type == QType::SOA) {
+ if (dr.d_type == QType::SOA) {
// cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl;
auto sr = getRR<SOARecordContent>(dr);
- zs.soa_serial=sr->d_st.serial;
+ zs.soa_serial = sr->d_st.serial;
}
replacement.push_back(rr);
}
- di.backend->replaceRRSet(di.id, g.first.first+domain, QType(g.first.second), replacement);
+ di.backend->replaceRRSet(di.id, g.first.first + domain, QType(g.first.second), replacement);
}
di.backend->commitTransaction();
}
}
- catch(std::exception& p) {
- g_log<<Logger::Error<<logPrefix<<"got exception (std::exception): "<<p.what()<<endl;
+ catch (std::exception& p) {
+ g_log << Logger::Error << logPrefix << "got exception (std::exception): " << p.what() << endl;
throw;
}
- catch(PDNSException& p) {
- g_log<<Logger::Error<<logPrefix<<"got exception (PDNSException): "<<p.reason<<endl;
+ catch (PDNSException& p) {
+ g_log << Logger::Error << logPrefix << "got exception (PDNSException): " << p.reason << endl;
throw;
}
}
static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResourceRecord& rr, ZoneStatus& zs)
{
- switch(rr.qtype.getCode()) {
+ switch (rr.qtype.getCode()) {
case QType::NSEC3PARAM:
zs.ns3pr = NSEC3PARAMRecordContent(rr.content);
zs.isDnssecZone = zs.isNSEC3 = true;
if (firstNSEC3) {
zs.isDnssecZone = zs.isPresigned = true;
firstNSEC3 = false;
- } else if (zs.optOutFlag != (ns3rc.d_flags & 1))
+ }
+ else if (zs.optOutFlag != (ns3rc.d_flags & 1))
throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported.");
zs.optOutFlag = ns3rc.d_flags & 1;
- if (ns3rc.isSet(QType::NS) && !(rr.qname==domain)) {
+ if (ns3rc.isSet(QType::NS) && !(rr.qname == domain)) {
DNSName hashPart = rr.qname.makeRelative(domain);
zs.secured.insert(hashPart);
}
return false;
case QType::NS:
- if(rr.qname!=domain)
+ if (rr.qname != domain)
zs.nsset.insert(rr.qname);
break;
}
zs.qnames.insert(rr.qname);
- rr.domain_id=zs.domain_id;
+ rr.domain_id = zs.domain_id;
return true;
}
/* So this code does a number of things.
- 1) It will AXFR a domain from a master
+ 1) It will AXFR a domain from a primary
The code can retrieve the current serial number in the database itself.
It may attempt an IXFR
2) It will filter the zone through a lua *filter* script
5) It updates the Empty Non Terminals
*/
-static vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, unique_ptr<AuthLua4>& pdl, ZoneStatus& zs)
+static vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, unique_ptr<AuthLua4>& pdl, ZoneStatus& zs)
{
- uint16_t axfr_timeout=::arg().asNum("axfr-fetch-timeout");
+ uint16_t axfr_timeout = ::arg().asNum("axfr-fetch-timeout");
vector<DNSResourceRecord> rrs;
- AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? nullptr : &laddr, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024, axfr_timeout);
+ AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? nullptr : &laddr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024, axfr_timeout);
Resolver::res_t recs;
- bool first=true;
+ bool first = true;
bool firstNSEC3{true};
- bool soa_received {false};
- string logPrefix="AXFR-in zone '"+domain.toLogString()+"', primary '"+raddr.toString()+"', ";
- while(retriever.getChunk(recs, nullptr, axfr_timeout)) {
- if(first) {
- g_log<<Logger::Notice<<logPrefix<<"retrieval started"<<endl;
- first=false;
+ bool soa_received{false};
+ string logPrefix = "AXFR-in zone '" + domain.toLogString() + "', primary '" + raddr.toString() + "', ";
+ while (retriever.getChunk(recs, nullptr, axfr_timeout)) {
+ if (first) {
+ g_log << Logger::Notice << logPrefix << "retrieval started" << endl;
+ first = false;
}
- for(auto & rec : recs) {
+ for (auto& rec : recs) {
rec.qname.makeUsLowerCase();
- if(rec.qtype.getCode() == QType::OPT || rec.qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG
+ if (rec.qtype.getCode() == QType::OPT || rec.qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG
continue;
- if(!rec.qname.isPartOf(domain)) {
- g_log<<Logger::Warning<<logPrefix<<"primary tried to sneak in out-of-zone data '"<<rec.qname<<"'|"<<rec.qtype.toString()<<", ignoring"<<endl;
+ if (!rec.qname.isPartOf(domain)) {
+ g_log << Logger::Warning << logPrefix << "primary tried to sneak in out-of-zone data '" << rec.qname << "'|" << rec.qtype.toString() << ", ignoring" << endl;
continue;
}
vector<DNSResourceRecord> out;
- if(!pdl || !pdl->axfrfilter(raddr, domain, rec, out)) {
+ if (!pdl || !pdl->axfrfilter(raddr, domain, rec, out)) {
out.push_back(rec); // if axfrfilter didn't do anything, we put our record in 'out' ourselves
}
- for(auto& rr : out) {
- if(!rr.qname.isPartOf(domain)) {
- g_log<<Logger::Error<<logPrefix<<"axfrfilter() filter tried to sneak in out-of-zone data '"<<rr.qname<<"'|"<<rr.qtype.toString()<<", ignoring"<<endl;
+ for (auto& rr : out) {
+ if (!rr.qname.isPartOf(domain)) {
+ g_log << Logger::Error << logPrefix << "axfrfilter() filter tried to sneak in out-of-zone data '" << rr.qname << "'|" << rr.qtype.toString() << ", ignoring" << endl;
continue;
}
- if(!processRecordForZS(domain, firstNSEC3, rr, zs))
+ if (!processRecordForZS(domain, firstNSEC3, rr, zs))
continue;
- if(rr.qtype.getCode() == QType::SOA) {
- if(soa_received)
- continue; //skip the last SOA
+ if (rr.qtype.getCode() == QType::SOA) {
+ if (soa_received)
+ continue; // skip the last SOA
SOAData sd;
- fillSOAData(rr.content,sd);
+ fillSOAData(rr.content, sd);
zs.soa_serial = sd.serial;
soa_received = true;
}
rrs.push_back(rr);
-
}
}
}
return rrs;
}
-
-void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote, bool force)
+void CommunicatorClass::suck(const DNSName& domain, const ComboAddress& remote, bool force)
{
{
auto data = d_data.lock();
}
RemoveSentinel rs(domain, this); // this removes us from d_inprogress when we go out of scope
- string logPrefix="XFR-in zone: '"+domain.toLogString()+"', primary: '"+remote.toString()+"', ";
+ string logPrefix = "XFR-in zone: '" + domain.toLogString() + "', primary: '" + remote.toString() + "', ";
- g_log<<Logger::Notice<<logPrefix<<"initiating transfer"<<endl;
+ g_log << Logger::Notice << logPrefix << "initiating transfer" << endl;
UeberBackend B; // fresh UeberBackend
DomainInfo di;
- di.backend=nullptr;
- bool transaction=false;
+ di.backend = nullptr;
+ bool transaction = false;
try {
- DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper
+ DNSSECKeeper dk(&B); // reuse our UeberBackend copy for DNSSECKeeper
bool wrongDomainKind = false;
// this checks three error conditions & sets wrongDomainKind if we hit the third
if (!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, !force && !di.isSecondaryType())) { // di.backend and B are mostly identical
- if(wrongDomainKind)
+ if (wrongDomainKind)
g_log << Logger::Warning << logPrefix << "can't determine backend, not configured as secondary" << endl;
else
- g_log<<Logger::Warning<<logPrefix<<"can't determine backend"<<endl;
+ g_log << Logger::Warning << logPrefix << "can't determine backend" << endl;
return;
}
ZoneStatus zs;
- zs.domain_id=di.id;
+ zs.domain_id = di.id;
TSIGTriplet tt;
- if(dk.getTSIGForAccess(domain, remote, &tt.name)) {
+ if (dk.getTSIGForAccess(domain, remote, &tt.name)) {
string tsigsecret64;
if (B.getTSIGKey(tt.name, tt.algo, tsigsecret64)) {
- if(B64Decode(tsigsecret64, tt.secret)) {
- g_log<<Logger::Error<<logPrefix<<"unable to Base-64 decode TSIG key '"<<tt.name<<"' or zone not found"<<endl;
+ if (B64Decode(tsigsecret64, tt.secret)) {
+ g_log << Logger::Error << logPrefix << "unable to Base-64 decode TSIG key '" << tt.name << "' or zone not found" << endl;
return;
}
}
else {
- g_log<<Logger::Warning<<logPrefix<<"TSIG key '"<<tt.name<<"' for zone not found"<<endl;
+ g_log << Logger::Warning << logPrefix << "TSIG key '" << tt.name << "' for zone not found" << endl;
return;
}
}
-
unique_ptr<AuthLua4> pdl{nullptr};
vector<string> scripts;
- string script=::arg()["lua-axfr-script"];
- if(B.getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) {
+ string script = ::arg()["lua-axfr-script"];
+ if (B.getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) {
if (pdns_iequals(scripts[0], "NONE")) {
script.clear();
- } else {
- script=scripts[0];
+ }
+ else {
+ script = scripts[0];
}
}
- if(!script.empty()){
+ if (!script.empty()) {
try {
pdl = make_unique<AuthLua4>();
pdl->loadFile(script);
- g_log<<Logger::Info<<logPrefix<<"loaded Lua script '"<<script<<"'"<<endl;
+ g_log << Logger::Info << logPrefix << "loaded Lua script '" << script << "'" << endl;
}
- catch(std::exception& e) {
- g_log<<Logger::Error<<logPrefix<<"failed to load Lua script '"<<script<<"': "<<e.what()<<endl;
+ catch (std::exception& e) {
+ g_log << Logger::Error << logPrefix << "failed to load Lua script '" << script << "': " << e.what() << endl;
return;
}
}
vector<string> localaddr;
ComboAddress laddr;
- if(B.getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
+ if (B.getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
try {
laddr = ComboAddress(localaddr[0]);
- g_log<<Logger::Info<<logPrefix<<"xfr source set to "<<localaddr[0]<<endl;
+ g_log << Logger::Info << logPrefix << "xfr source set to " << localaddr[0] << endl;
}
- catch(std::exception& e) {
- g_log<<Logger::Error<<logPrefix<<"failed to set xfr source '"<<localaddr[0]<<"': "<<e.what()<<endl;
+ catch (std::exception& e) {
+ g_log << Logger::Error << logPrefix << "failed to set xfr source '" << localaddr[0] << "': " << e.what() << endl;
return;
}
- } else {
+ }
+ else {
if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
bool isV6 = remote.sin4.sin_family == AF_INET6;
- g_log<<Logger::Warning<<logPrefix<<"unable to xfr, address family (IPv"<< (isV6 ? "6" : "4") <<
- " is not enabled for outgoing traffic (query-local-address)"<<endl;
+ g_log << Logger::Warning << logPrefix << "unable to xfr, address family (IPv" << (isV6 ? "6" : "4") << " is not enabled for outgoing traffic (query-local-address)" << endl;
return;
}
laddr = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
bool hadPresigned = false;
bool hadNSEC3 = false;
NSEC3PARAMRecordContent hadNs3pr;
- bool hadNarrow=false;
-
+ bool hadNarrow = false;
vector<DNSResourceRecord> rrs;
if (dk.isSecuredZone(domain, false)) {
- hadDnssecZone=true;
+ hadDnssecZone = true;
hadPresigned = dk.isPresigned(domain, false);
if (dk.getNSEC3PARAM(domain, &zs.ns3pr, &zs.isNarrow, false)) {
hadNSEC3 = true;
hadNarrow = zs.isNarrow;
}
}
- else if(di.serial) {
+ else if (di.serial) {
vector<string> meta;
B.getDomainMetadata(domain, "IXFR", meta);
- if(!meta.empty() && meta[0]=="1") {
+ if (!meta.empty() && meta[0] == "1") {
logPrefix = "I" + logPrefix; // XFR -> IXFR
vector<DNSRecord> axfr;
- g_log<<Logger::Notice<<logPrefix<<"starting IXFR"<<endl;
+ g_log << Logger::Notice << logPrefix << "starting IXFR" << endl;
ixfrSuck(domain, tt, laddr, remote, zs, &axfr);
- if(!axfr.empty()) {
- g_log<<Logger::Notice<<logPrefix<<"IXFR turned into an AXFR"<<endl;
- logPrefix[0]='A'; // IXFR -> AXFR
- bool firstNSEC3=true;
+ if (!axfr.empty()) {
+ g_log << Logger::Notice << logPrefix << "IXFR turned into an AXFR" << endl;
+ logPrefix[0] = 'A'; // IXFR -> AXFR
+ bool firstNSEC3 = true;
rrs.reserve(axfr.size());
- for(const auto& dr : axfr) {
+ for (const auto& dr : axfr) {
auto rr = DNSResourceRecord::fromWire(dr);
(rr.qname += domain).makeUsLowerCase();
rr.domain_id = zs.domain_id;
- if(!processRecordForZS(domain, firstNSEC3, rr, zs))
+ if (!processRecordForZS(domain, firstNSEC3, rr, zs))
continue;
- if(dr.d_type == QType::SOA) {
+ if (dr.d_type == QType::SOA) {
auto sd = getRR<SOARecordContent>(dr);
zs.soa_serial = sd->d_st.serial;
}
}
}
else {
- g_log<<Logger::Warning<<logPrefix<<"got "<<zs.numDeltas<<" delta"<<addS(zs.numDeltas)<<", zone committed with serial "<<zs.soa_serial<<endl;
- purgeAuthCaches(domain.toString()+"$");
+ g_log << Logger::Warning << logPrefix << "got " << zs.numDeltas << " delta" << addS(zs.numDeltas) << ", zone committed with serial " << zs.soa_serial << endl;
+ purgeAuthCaches(domain.toString() + "$");
return;
}
}
}
- if(rrs.empty()) {
- g_log<<Logger::Notice<<logPrefix<<"starting AXFR"<<endl;
+ if (rrs.empty()) {
+ g_log << Logger::Notice << logPrefix << "starting AXFR" << endl;
rrs = doAxfr(remote, domain, tt, laddr, pdl, zs);
logPrefix = "A" + logPrefix; // XFR -> AXFR
- g_log<<Logger::Notice<<logPrefix<<"retrieval finished"<<endl;
+ g_log << Logger::Notice << logPrefix << "retrieval finished" << endl;
}
if (di.kind == DomainInfo::Consumer) {
}
}
- if(zs.isNSEC3) {
+ if (zs.isNSEC3) {
zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0;
}
- if(!zs.isPresigned) {
+ if (!zs.isPresigned) {
DNSSECKeeper::keyset_t keys = dk.getKeys(domain, false);
- if(!keys.empty()) {
+ if (!keys.empty()) {
zs.isDnssecZone = true;
zs.isNSEC3 = hadNSEC3;
zs.ns3pr = hadNs3pr;
}
}
- if(zs.isDnssecZone) {
- if(!zs.isNSEC3)
- g_log<<Logger::Debug<<logPrefix<<"adding NSEC ordering information"<<endl;
- else if(!zs.isNarrow)
- g_log<<Logger::Debug<<logPrefix<<"adding NSEC3 hashed ordering information"<<endl;
+ if (zs.isDnssecZone) {
+ if (!zs.isNSEC3)
+ g_log << Logger::Debug << logPrefix << "adding NSEC ordering information" << endl;
+ else if (!zs.isNarrow)
+ g_log << Logger::Debug << logPrefix << "adding NSEC3 hashed ordering information" << endl;
else
- g_log<<Logger::Debug<<logPrefix<<"zone is narrow, only setting 'auth' fields"<<endl;
+ g_log << Logger::Debug << logPrefix << "zone is narrow, only setting 'auth' fields" << endl;
}
-
- transaction=di.backend->startTransaction(domain, zs.domain_id);
- g_log<<Logger::Info<<logPrefix<<"storage transaction started"<<endl;
+ transaction = di.backend->startTransaction(domain, zs.domain_id);
+ g_log << Logger::Info << logPrefix << "storage transaction started" << endl;
// update the presigned flag and NSEC3PARAM
if (zs.isDnssecZone) {
if (zs.isPresigned && !hadPresigned) {
// zone is now presigned
dk.setPresigned(domain);
- } else if (hadPresigned && !zs.isPresigned) {
+ }
+ else if (hadPresigned && !zs.isPresigned) {
// zone is no longer presigned
dk.unsetPresigned(domain);
}
// update NSEC3PARAM
if (zs.isNSEC3) {
// zone is NSEC3, only update if there was a change
- if (!hadNSEC3 || (hadNarrow != zs.isNarrow) ||
- (zs.ns3pr.d_algorithm != hadNs3pr.d_algorithm) ||
- (zs.ns3pr.d_flags != hadNs3pr.d_flags) ||
- (zs.ns3pr.d_iterations != hadNs3pr.d_iterations) ||
- (zs.ns3pr.d_salt != hadNs3pr.d_salt)) {
+ if (!hadNSEC3 || (hadNarrow != zs.isNarrow) || (zs.ns3pr.d_algorithm != hadNs3pr.d_algorithm) || (zs.ns3pr.d_flags != hadNs3pr.d_flags) || (zs.ns3pr.d_iterations != hadNs3pr.d_iterations) || (zs.ns3pr.d_salt != hadNs3pr.d_salt)) {
dk.setNSEC3PARAM(domain, zs.ns3pr, zs.isNarrow);
}
- } else if (hadNSEC3 ) {
- // zone is no longer NSEC3
- dk.unsetNSEC3PARAM(domain);
}
- } else if (hadDnssecZone) {
+ else if (hadNSEC3) {
+ // zone is no longer NSEC3
+ dk.unsetNSEC3PARAM(domain);
+ }
+ }
+ else if (hadDnssecZone) {
// zone is no longer signed
if (hadPresigned) {
// remove presigned
}
}
- bool doent=true;
+ bool doent = true;
uint32_t maxent = ::arg().asNum("max-ent-entries");
DNSName shorter, ordername;
set<DNSName> rrterm;
- map<DNSName,bool> nonterm;
-
+ map<DNSName, bool> nonterm;
- for(DNSResourceRecord& rr : rrs) {
- if(!zs.isPresigned) {
+ for (DNSResourceRecord& rr : rrs) {
+ if (!zs.isPresigned) {
if (rr.qtype.getCode() == QType::RRSIG)
continue;
- if(zs.isDnssecZone && rr.qtype.getCode() == QType::DNSKEY && !::arg().mustDo("direct-dnskey"))
+ if (zs.isDnssecZone && rr.qtype.getCode() == QType::DNSKEY && !::arg().mustDo("direct-dnskey"))
continue;
}
// Figure out auth and ents
- rr.auth=true;
- shorter=rr.qname;
+ rr.auth = true;
+ shorter = rr.qname;
rrterm.clear();
do {
- if(doent) {
+ if (doent) {
if (!zs.qnames.count(shorter))
rrterm.insert(shorter);
}
- if(zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS)
- rr.auth=false;
+ if (zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS)
+ rr.auth = false;
- if (shorter==domain) // stop at apex
+ if (shorter == domain) // stop at apex
break;
- }while(shorter.chopOff());
+ } while (shorter.chopOff());
// Insert ents
- if(doent && !rrterm.empty()) {
+ if (doent && !rrterm.empty()) {
bool auth;
if (!rr.auth && rr.qtype.getCode() == QType::NS) {
if (zs.isNSEC3)
- ordername=DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
- auth=(!zs.isNSEC3 || !zs.optOutFlag || zs.secured.count(ordername));
- } else
- auth=rr.auth;
+ ordername = DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
+ auth = (!zs.isNSEC3 || !zs.optOutFlag || zs.secured.count(ordername));
+ }
+ else
+ auth = rr.auth;
- for(const auto &nt: rrterm){
+ for (const auto& nt : rrterm) {
if (!nonterm.count(nt))
- nonterm.insert(pair<DNSName, bool>(nt, auth));
- else if (auth)
- nonterm[nt]=true;
+ nonterm.insert(pair<DNSName, bool>(nt, auth));
+ else if (auth)
+ nonterm[nt] = true;
}
- if(nonterm.size() > maxent) {
- g_log<<Logger::Warning<<logPrefix<<"zone has too many empty non terminals"<<endl;
+ if (nonterm.size() > maxent) {
+ g_log << Logger::Warning << logPrefix << "zone has too many empty non terminals" << endl;
nonterm.clear();
- doent=false;
+ doent = false;
}
}
// RRSIG is always auth, even inside a delegation
if (rr.qtype.getCode() == QType::RRSIG)
- rr.auth=true;
+ rr.auth = true;
// Add ordername and insert record
if (zs.isDnssecZone && rr.qtype.getCode() != QType::RRSIG) {
if (zs.isNSEC3) {
// NSEC3
- ordername=DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
- if(!zs.isNarrow && (rr.auth || (rr.qtype.getCode() == QType::NS && (!zs.optOutFlag || zs.secured.count(ordername))))) {
+ ordername = DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
+ if (!zs.isNarrow && (rr.auth || (rr.qtype.getCode() == QType::NS && (!zs.optOutFlag || zs.secured.count(ordername))))) {
di.backend->feedRecord(rr, ordername, true);
- } else
+ }
+ else
di.backend->feedRecord(rr, DNSName());
- } else {
+ }
+ else {
// NSEC
if (rr.auth || rr.qtype.getCode() == QType::NS) {
- ordername=rr.qname.makeRelative(domain);
+ ordername = rr.qname.makeRelative(domain);
di.backend->feedRecord(rr, ordername);
- } else
+ }
+ else
di.backend->feedRecord(rr, DNSName());
}
- } else
+ }
+ else
di.backend->feedRecord(rr, DNSName());
}
// Insert empty non-terminals
- if(doent && !nonterm.empty()) {
+ if (doent && !nonterm.empty()) {
if (zs.isNSEC3) {
di.backend->feedEnts3(zs.domain_id, domain, nonterm, zs.ns3pr, zs.isNarrow);
- } else
+ }
+ else
di.backend->feedEnts(zs.domain_id, nonterm);
}
di.backend->commitTransaction();
transaction = false;
di.backend->setFresh(zs.domain_id);
- purgeAuthCaches(domain.toString()+"$");
+ purgeAuthCaches(domain.toString() + "$");
- g_log<<Logger::Warning<<logPrefix<<"zone committed with serial "<<zs.soa_serial<<endl;
+ g_log << Logger::Warning << logPrefix << "zone committed with serial " << zs.soa_serial << endl;
- // Send slave re-notifications
+ // Send secondary re-notifications
bool doNotify;
vector<string> meta;
- if(B.getDomainMetadata(domain, "SLAVE-RENOTIFY", meta ) && !meta.empty()) {
- doNotify=(meta.front() == "1");
- } else {
- doNotify=(::arg().mustDo("slave-renotify"));
+ if (B.getDomainMetadata(domain, "SLAVE-RENOTIFY", meta) && !meta.empty()) {
+ doNotify = (meta.front() == "1");
}
- if(doNotify) {
+ else {
+ doNotify = (::arg().mustDo("secondary-do-renotify"));
+ }
+ if (doNotify) {
notifyDomain(domain, &B);
}
-
}
- catch(DBException &re) {
- g_log<<Logger::Error<<logPrefix<<"unable to feed record: "<<re.reason<<endl;
- if(di.backend && transaction) {
- g_log<<Logger::Info<<logPrefix<<"aborting possible open transaction"<<endl;
+ catch (DBException& re) {
+ g_log << Logger::Error << logPrefix << "unable to feed record: " << re.reason << endl;
+ if (di.backend && transaction) {
+ g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
di.backend->abortTransaction();
}
}
- catch(const MOADNSException &mde) {
- g_log<<Logger::Error<<logPrefix<<"unable to parse record (MOADNSException): "<<mde.what()<<endl;
- if(di.backend && transaction) {
- g_log<<Logger::Info<<logPrefix<<"aborting possible open transaction"<<endl;
+ catch (const MOADNSException& mde) {
+ g_log << Logger::Error << logPrefix << "unable to parse record (MOADNSException): " << mde.what() << endl;
+ if (di.backend && transaction) {
+ g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
di.backend->abortTransaction();
}
}
- catch(std::exception &re) {
- g_log<<Logger::Error<<logPrefix<<"unable to xfr zone (std::exception): "<<re.what()<<endl;
- if(di.backend && transaction) {
- g_log<<Logger::Info<<logPrefix<<"aborting possible open transaction"<<endl;
+ catch (std::exception& re) {
+ g_log << Logger::Error << logPrefix << "unable to xfr zone (std::exception): " << re.what() << endl;
+ if (di.backend && transaction) {
+ g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
di.backend->abortTransaction();
}
}
- catch(ResolverException &re) {
+ catch (ResolverException& re) {
{
auto data = d_data.lock();
- // The AXFR probably failed due to a problem on the master server. If SOA-checks against this master
+ // The AXFR probably failed due to a problem on the primary server. If SOA-checks against this primary
// still succeed, we would constantly try to AXFR the zone. To avoid this, we add the zone to the list of
- // failed slave-checks. This will suspend slave-checks (and subsequent AXFR) for this zone for some time.
+ // failed secondary-checks. This will suspend secondary-checks (and subsequent AXFR) for this zone for some time.
uint64_t newCount = 1;
time_t now = time(nullptr);
- const auto failedEntry = data->d_failedSlaveRefresh.find(domain);
- if (failedEntry != data->d_failedSlaveRefresh.end()) {
- newCount = data->d_failedSlaveRefresh[domain].first + 1;
+ const auto failedEntry = data->d_failedSecondaryRefresh.find(domain);
+ if (failedEntry != data->d_failedSecondaryRefresh.end()) {
+ newCount = data->d_failedSecondaryRefresh[domain].first + 1;
}
time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("default-ttl"));
- data->d_failedSlaveRefresh[domain] = {newCount, nextCheck};
- g_log<<Logger::Warning<<logPrefix<<"unable to xfr zone (ResolverException): "<<re.reason<<" (This was attempt number "<<newCount<<". Excluding zone from slave-checks until "<<nextCheck<<")"<<endl;
+ data->d_failedSecondaryRefresh[domain] = {newCount, nextCheck};
+ g_log << Logger::Warning << logPrefix << "unable to xfr zone (ResolverException): " << re.reason << " (This was attempt number " << newCount << ". Excluding zone from secondary-checks until " << nextCheck << ")" << endl;
}
- if(di.backend && transaction) {
- g_log<<Logger::Info<<"aborting possible open transaction"<<endl;
+ if (di.backend && transaction) {
+ g_log << Logger::Info << "aborting possible open transaction" << endl;
di.backend->abortTransaction();
}
}
- catch(PDNSException &ae) {
- g_log<<Logger::Error<<logPrefix<<"unable to xfr zone (PDNSException): "<<ae.reason<<endl;
- if(di.backend && transaction) {
- g_log<<Logger::Info<<logPrefix<<"aborting possible open transaction"<<endl;
+ catch (PDNSException& ae) {
+ g_log << Logger::Error << logPrefix << "unable to xfr zone (PDNSException): " << ae.reason << endl;
+ if (di.backend && transaction) {
+ g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
di.backend->abortTransaction();
}
}
}
-namespace {
+namespace
+{
struct DomainNotificationInfo
{
DomainInfo di;
};
}
-
-struct SlaveSenderReceiver
+struct SecondarySenderReceiver
{
typedef std::tuple<DNSName, ComboAddress, uint16_t> Identifier;
- struct Answer {
+ struct Answer
+ {
uint32_t theirSerial;
uint32_t theirInception;
uint32_t theirExpire;
map<uint32_t, Answer> d_freshness;
- SlaveSenderReceiver()
- {
- }
-
void deliverTimeout(const Identifier& /* i */)
{
}
Identifier send(DomainNotificationInfo& dni)
{
- shuffle(dni.di.masters.begin(), dni.di.masters.end(), pdns::dns_random_engine());
+ shuffle(dni.di.primaries.begin(), dni.di.primaries.end(), pdns::dns_random_engine());
try {
- return std::make_tuple(dni.di.zone,
- *dni.di.masters.begin(),
- d_resolver.sendResolve(*dni.di.masters.begin(),
- dni.localaddr,
- dni.di.zone,
- QType::SOA,
- nullptr,
- dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)
- );
+ return {dni.di.zone,
+ *dni.di.primaries.begin(),
+ d_resolver.sendResolve(*dni.di.primaries.begin(),
+ dni.localaddr,
+ dni.di.zone,
+ QType::SOA,
+ nullptr,
+ dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)};
}
- catch(PDNSException& e) {
- throw runtime_error("While attempting to query freshness of '"+dni.di.zone.toLogString()+"': "+e.reason);
+ catch (PDNSException& e) {
+ throw runtime_error("While attempting to query freshness of '" + dni.di.zone.toLogString() + "': " + e.reason);
}
}
void deliverAnswer(const DomainNotificationInfo& dni, const Answer& a, unsigned int /* usec */)
{
- d_freshness[dni.di.id]=a;
+ d_freshness[dni.di.id] = a;
}
Resolver d_resolver;
};
-void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote)
+void CommunicatorClass::addSecondaryCheckRequest(const DomainInfo& di, const ComboAddress& remote)
{
auto data = d_data.lock();
DomainInfo ours = di;
ours.backend = nullptr;
// When adding a check, if the remote addr from which notification was
- // received is a master, clear all other masters so we can be sure the
+ // received is a primary, clear all other primaries so we can be sure the
// query goes to that one.
- for (const auto& master : di.masters) {
- if (ComboAddress::addressOnlyEqual()(remote, master)) {
- ours.masters.clear();
- ours.masters.push_back(master);
+ for (const auto& primary : di.primaries) {
+ if (ComboAddress::addressOnlyEqual()(remote, primary)) {
+ ours.primaries.clear();
+ ours.primaries.push_back(primary);
break;
}
}
d_any_sem.post(); // kick the loop!
}
-void CommunicatorClass::addTrySuperMasterRequest(const DNSPacket& p)
+void CommunicatorClass::addTryAutoPrimaryRequest(const DNSPacket& p)
{
const DNSPacket& ours = p;
auto data = d_data.lock();
- if (data->d_potentialsupermasters.insert(ours).second) {
+ if (data->d_potentialautoprimaries.insert(ours).second) {
d_any_sem.post(); // kick the loop!
}
}
-void CommunicatorClass::slaveRefresh(PacketHandler *P)
+void CommunicatorClass::secondaryRefresh(PacketHandler* P)
{
- // not unless we are slave
- if (!::arg().mustDo("secondary")) return;
+ // not unless we are secondary
+ if (!::arg().mustDo("secondary"))
+ return;
- UeberBackend *B=P->getBackend();
+ UeberBackend* B = P->getBackend();
vector<DomainInfo> rdomains;
vector<DomainNotificationInfo> sdomains;
set<DNSPacket, Data::cmp> trysuperdomains;
auto data = d_data.lock();
set<DomainInfo> requeue;
rdomains.reserve(data->d_tocheck.size());
- for (const auto& di: data->d_tocheck) {
+ for (const auto& di : data->d_tocheck) {
if (data->d_inprogress.count(di.zone)) {
- g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<" while AXFR in progress, requeueing SOA check"<<endl;
+ g_log << Logger::Debug << "Got NOTIFY for " << di.zone << " while AXFR in progress, requeueing SOA check" << endl;
requeue.insert(di);
}
else {
- // We received a NOTIFY for a zone. This means at least one of the zone's master server is working.
- // Therefore we delete the zone from the list of failed slave-checks to allow immediate checking.
- const auto wasFailedDomain = data->d_failedSlaveRefresh.find(di.zone);
- if (wasFailedDomain != data->d_failedSlaveRefresh.end()) {
- g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<", removing zone from list of failed slave-checks and going to check SOA serial"<<endl;
- data->d_failedSlaveRefresh.erase(di.zone);
- } else {
- g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<", going to check SOA serial"<<endl;
+ // We received a NOTIFY for a zone. This means at least one of the zone's primary server is working.
+ // Therefore we delete the zone from the list of failed secondary-checks to allow immediate checking.
+ const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
+ if (wasFailedDomain != data->d_failedSecondaryRefresh.end()) {
+ g_log << Logger::Debug << "Got NOTIFY for " << di.zone << ", removing zone from list of failed secondary-checks and going to check SOA serial" << endl;
+ data->d_failedSecondaryRefresh.erase(di.zone);
+ }
+ else {
+ g_log << Logger::Debug << "Got NOTIFY for " << di.zone << ", going to check SOA serial" << endl;
}
rdomains.push_back(di);
}
}
data->d_tocheck.swap(requeue);
- trysuperdomains = std::move(data->d_potentialsupermasters);
- data->d_potentialsupermasters.clear();
+ trysuperdomains = std::move(data->d_potentialautoprimaries);
+ data->d_potentialautoprimaries.clear();
}
- for(const DNSPacket& dp : trysuperdomains) {
+ for (const DNSPacket& dp : trysuperdomains) {
// get the TSIG key name
TSIGRecordContent trc;
DNSName tsigkeyname;
dp.getTSIGDetails(&trc, &tsigkeyname);
- P->trySuperMasterSynchronous(dp, tsigkeyname); // FIXME could use some error logging
+ P->tryAutoPrimarySynchronous(dp, tsigkeyname); // FIXME could use some error logging
}
- if(rdomains.empty()) { // if we have priority domains, check them first
- B->getUnfreshSlaveInfos(&rdomains);
+ if (rdomains.empty()) { // if we have priority domains, check them first
+ B->getUnfreshSecondaryInfos(&rdomains);
}
sdomains.reserve(rdomains.size());
DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
bool checkSignatures = ::arg().mustDo("secondary-check-signature-freshness") && dk.doesDNSSEC();
{
auto data = d_data.lock();
- domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(data->d_suckdomains);
+ domains_by_name_t& nameindex = boost::multi_index::get<IDTag>(data->d_suckdomains);
time_t now = time(nullptr);
- for(DomainInfo& di : rdomains) {
- const auto failed = data->d_failedSlaveRefresh.find(di.zone);
- if (failed != data->d_failedSlaveRefresh.end() && now < failed->second.second ) {
+ for (DomainInfo& di : rdomains) {
+ const auto failed = data->d_failedSecondaryRefresh.find(di.zone);
+ if (failed != data->d_failedSecondaryRefresh.end() && now < failed->second.second) {
// If the domain has failed before and the time before the next check has not expired, skip this domain
- g_log<<Logger::Debug<<"Zone '"<<di.zone<<"' is on the list of failed SOA checks. Skipping SOA checks until "<< failed->second.second<<endl;
+ g_log << Logger::Debug << "Zone '" << di.zone << "' is on the list of failed SOA checks. Skipping SOA checks until " << failed->second.second << endl;
continue;
}
std::vector<std::string> localaddr;
SuckRequest sr;
- sr.domain=di.zone;
- if(di.masters.empty()) // slave domains w/o masters are ignored
+ sr.domain = di.zone;
+ if (di.primaries.empty()) // secondary domains w/o primaries are ignored
continue;
// remove unfresh domains already queued for AXFR, no sense polling them again
- sr.master=*di.masters.begin();
- if(nameindex.count(sr)) { // this does NOT however protect us against AXFRs already in progress!
+ sr.primary = *di.primaries.begin();
+ if (nameindex.count(sr)) { // this does NOT however protect us against AXFRs already in progress!
continue;
}
- if(data->d_inprogress.count(sr.domain)) { // this does
+ if (data->d_inprogress.count(sr.domain)) { // this does
continue;
}
dni.di = di;
dni.dnssecOk = checkSignatures;
- if(dk.getTSIGForAccess(di.zone, sr.master, &dni.tsigkeyname)) {
+ if (dk.getTSIGForAccess(di.zone, sr.primary, &dni.tsigkeyname)) {
string secret64;
if (!B->getTSIGKey(dni.tsigkeyname, dni.tsigalgname, secret64)) {
- g_log<<Logger::Warning<<"TSIG key '"<<dni.tsigkeyname<<"' for domain '"<<di.zone<<"' not found, can not AXFR."<<endl;
+ g_log << Logger::Warning << "TSIG key '" << dni.tsigkeyname << "' for domain '" << di.zone << "' not found, can not AXFR." << endl;
continue;
}
if (B64Decode(secret64, dni.tsigsecret) == -1) {
- g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<dni.tsigkeyname<<"' for domain '"<<di.zone<<"', can not AXFR."<<endl;
+ g_log << Logger::Error << "Unable to Base-64 decode TSIG key '" << dni.tsigkeyname << "' for domain '" << di.zone << "', can not AXFR." << endl;
continue;
}
}
localaddr.clear();
// check for AXFR-SOURCE
- if(B->getDomainMetadata(di.zone, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
+ if (B->getDomainMetadata(di.zone, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
try {
dni.localaddr = ComboAddress(localaddr[0]);
- g_log<<Logger::Info<<"Freshness check source (AXFR-SOURCE) for domain '"<<di.zone<<"' set to "<<localaddr[0]<<endl;
+ g_log << Logger::Info << "Freshness check source (AXFR-SOURCE) for domain '" << di.zone << "' set to " << localaddr[0] << endl;
}
- catch(std::exception& e) {
- g_log<<Logger::Error<<"Failed to load freshness check source '"<<localaddr[0]<<"' for '"<<di.zone<<"': "<<e.what()<<endl;
+ catch (std::exception& e) {
+ g_log << Logger::Error << "Failed to load freshness check source '" << localaddr[0] << "' for '" << di.zone << "': " << e.what() << endl;
return;
}
- } else {
+ }
+ else {
dni.localaddr.sin4.sin_family = 0;
}
sdomains.push_back(std::move(dni));
}
}
- if(sdomains.empty())
- {
- if (d_slaveschanged) {
+ if (sdomains.empty()) {
+ if (d_secondarieschanged) {
auto data = d_data.lock();
- g_log<<Logger::Info<<"No new unfresh slave domains, "<<data->d_suckdomains.size()<<" queued for AXFR already, "<<data->d_inprogress.size()<<" in progress"<<endl;
+ g_log << Logger::Info << "No new unfresh secondary domains, " << data->d_suckdomains.size() << " queued for AXFR already, " << data->d_inprogress.size() << " in progress" << endl;
}
- d_slaveschanged = !rdomains.empty();
+ d_secondarieschanged = !rdomains.empty();
return;
}
else {
auto data = d_data.lock();
- g_log<<Logger::Info<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<<
- (sdomains.size()>1 ? "" : "s")<<
- " checking, "<<data->d_suckdomains.size()<<" queued for AXFR"<<endl;
+ g_log << Logger::Info << sdomains.size() << " secondary domain" << (sdomains.size() > 1 ? "s" : "") << " need" << (sdomains.size() > 1 ? "" : "s") << " checking, " << data->d_suckdomains.size() << " queued for AXFR" << endl;
}
- SlaveSenderReceiver ssr;
+ SecondarySenderReceiver ssr;
- Inflighter<vector<DomainNotificationInfo>, SlaveSenderReceiver> ifl(sdomains, ssr);
+ Inflighter<vector<DomainNotificationInfo>, SecondarySenderReceiver> ifl(sdomains, ssr);
ifl.d_maxInFlight = 200;
- for(;;) {
+ for (;;) {
try {
ifl.run();
break;
}
- catch(std::exception& e) {
- g_log<<Logger::Error<<"While checking domain freshness: " << e.what()<<endl;
+ catch (std::exception& e) {
+ g_log << Logger::Error << "While checking domain freshness: " << e.what() << endl;
}
- catch(PDNSException &re) {
- g_log<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl;
+ catch (PDNSException& re) {
+ g_log << Logger::Error << "While checking domain freshness: " << re.reason << endl;
}
}
if (ifl.getTimeouts()) {
- g_log<<Logger::Warning<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zone"<<addS(ssr.d_freshness.size())<<", had "<<ifl.getTimeouts()<<" timeout"<<addS(ifl.getTimeouts())<<endl;
- } else {
- g_log<<Logger::Info<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zone"<<addS(ssr.d_freshness.size())<<endl;
+ g_log << Logger::Warning << "Received serial number updates for " << ssr.d_freshness.size() << " zone" << addS(ssr.d_freshness.size()) << ", had " << ifl.getTimeouts() << " timeout" << addS(ifl.getTimeouts()) << endl;
+ }
+ else {
+ g_log << Logger::Info << "Received serial number updates for " << ssr.d_freshness.size() << " zone" << addS(ssr.d_freshness.size()) << endl;
}
time_t now = time(nullptr);
- for(auto& val : sdomains) {
+ for (auto& val : sdomains) {
DomainInfo& di(val.di);
// If our di comes from packethandler (caused by incoming NOTIFY), di.backend will not be filled out,
// and di.serial will not either.
- // Conversely, if our di came from getUnfreshSlaveInfos, di.backend and di.serial are valid.
- if(!di.backend) {
+ // Conversely, if our di came from getUnfreshSecondaryInfos, di.backend and di.serial are valid.
+ if (!di.backend) {
// Do not overwrite received DI just to make sure it exists in backend:
- // di.masters should contain the picked master (as first entry)!
+ // di.primaries should contain the picked primary (as first entry)!
DomainInfo tempdi;
if (!B->getDomainInfo(di.zone, tempdi, false)) {
- g_log<<Logger::Info<<"Ignore domain "<< di.zone<<" since it has been removed from our backend"<<endl;
+ g_log << Logger::Info << "Ignore domain " << di.zone << " since it has been removed from our backend" << endl;
continue;
}
// Backend for di still doesn't exist and this might cause us to
di.backend = tempdi.backend;
}
- if(!ssr.d_freshness.count(di.id)) { // If we don't have an answer for the domain
+ if (!ssr.d_freshness.count(di.id)) { // If we don't have an answer for the domain
uint64_t newCount = 1;
auto data = d_data.lock();
- const auto failedEntry = data->d_failedSlaveRefresh.find(di.zone);
- if (failedEntry != data->d_failedSlaveRefresh.end())
- newCount = data->d_failedSlaveRefresh[di.zone].first + 1;
+ const auto failedEntry = data->d_failedSecondaryRefresh.find(di.zone);
+ if (failedEntry != data->d_failedSecondaryRefresh.end())
+ newCount = data->d_failedSecondaryRefresh[di.zone].first + 1;
time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("default-ttl"));
- data->d_failedSlaveRefresh[di.zone] = {newCount, nextCheck};
+ data->d_failedSecondaryRefresh[di.zone] = {newCount, nextCheck};
if (newCount == 1) {
- g_log<<Logger::Warning<<"Unable to retrieve SOA for "<<di.zone<<
- ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x "<<
- d_tickinterval<<" seconds', with a maximum of "<<(uint64_t)::arg().asNum("default-ttl")<<" seconds. Skipping SOA checks until "<<nextCheck<<endl;
- } else if (newCount % 10 == 0) {
- g_log<<Logger::Notice<<"Unable to retrieve SOA for "<<di.zone<<", this was the "<<std::to_string(newCount)<<"th time. Skipping SOA checks until "<<nextCheck<<endl;
+ g_log << Logger::Warning << "Unable to retrieve SOA for " << di.zone << ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x " << d_tickinterval << " seconds', with a maximum of " << (uint64_t)::arg().asNum("default-ttl") << " seconds. Skipping SOA checks until " << nextCheck << endl;
+ }
+ else if (newCount % 10 == 0) {
+ g_log << Logger::Notice << "Unable to retrieve SOA for " << di.zone << ", this was the " << std::to_string(newCount) << "th time. Skipping SOA checks until " << nextCheck << endl;
}
// Make sure we recheck SOA for notifies
if (di.receivedNotify) {
{
auto data = d_data.lock();
- const auto wasFailedDomain = data->d_failedSlaveRefresh.find(di.zone);
- if (wasFailedDomain != data->d_failedSlaveRefresh.end())
- data->d_failedSlaveRefresh.erase(di.zone);
+ const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
+ if (wasFailedDomain != data->d_failedSecondaryRefresh.end())
+ data->d_failedSecondaryRefresh.erase(di.zone);
}
bool hasSOA = false;
hasSOA = B->get(zr);
if (hasSOA) {
fillSOAData(zr, sd);
- while(B->get(zr));
+ while (B->get(zr))
+ ;
}
}
- catch(...) {}
+ catch (...) {
+ }
uint32_t theirserial = ssr.d_freshness[di.id].theirSerial;
uint32_t ourserial = sd.serial;
- const ComboAddress remote = *di.masters.begin();
+ const ComboAddress remote = *di.primaries.begin();
- if(hasSOA && rfc1982LessThan(theirserial, ourserial) && !::arg().mustDo("axfr-lower-serial")) {
- g_log<<Logger::Warning<<"Domain '" << di.zone << "' more recent than master " << remote.toStringWithPortExcept(53) << ", our serial "<< ourserial<< " > their serial "<< theirserial << endl;
+ if (hasSOA && rfc1982LessThan(theirserial, ourserial) && !::arg().mustDo("axfr-lower-serial")) {
+ g_log << Logger::Warning << "Domain '" << di.zone << "' more recent than primary " << remote.toStringWithPortExcept(53) << ", our serial " << ourserial << " > their serial " << theirserial << endl;
di.backend->setFresh(di.id);
}
- else if(hasSOA && theirserial == ourserial) {
- uint32_t maxExpire=0, maxInception=0;
- if(checkSignatures && dk.isPresigned(di.zone)) {
+ else if (hasSOA && theirserial == ourserial) {
+ uint32_t maxExpire = 0, maxInception = 0;
+ if (checkSignatures && dk.isPresigned(di.zone)) {
B->lookup(QType(QType::RRSIG), di.zone, di.id); // can't use DK before we are done with this lookup!
DNSZoneRecord zr;
- while(B->get(zr)) {
+ while (B->get(zr)) {
auto rrsig = getRR<RRSIGRecordContent>(zr.dr);
- if(rrsig->d_type == QType::SOA) {
+ if (rrsig->d_type == QType::SOA) {
maxInception = std::max(maxInception, rrsig->d_siginception);
maxExpire = std::max(maxExpire, rrsig->d_sigexpire);
}
prio = SuckRequest::Notify;
}
- if(! maxInception && ! ssr.d_freshness[di.id].theirInception) {
- g_log<<Logger::Info<<"Domain '"<< di.zone << "' is fresh (no DNSSEC), serial is " << ourserial << " (checked master " << remote.toStringWithPortExcept(53) << ")" << endl;
+ if (!maxInception && !ssr.d_freshness[di.id].theirInception) {
+ g_log << Logger::Info << "Domain '" << di.zone << "' is fresh (no DNSSEC), serial is " << ourserial << " (checked primary " << remote.toStringWithPortExcept(53) << ")" << endl;
di.backend->setFresh(di.id);
}
- else if(maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) {
- g_log<<Logger::Info<<"Domain '"<< di.zone << "' is fresh and SOA RRSIGs match, serial is " << ourserial << " (checked master " << remote.toStringWithPortExcept(53) << ")" << endl;
+ else if (maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) {
+ g_log << Logger::Info << "Domain '" << di.zone << "' is fresh and SOA RRSIGs match, serial is " << ourserial << " (checked primary " << remote.toStringWithPortExcept(53) << ")" << endl;
di.backend->setFresh(di.id);
}
- else if(maxExpire >= now && ! ssr.d_freshness[di.id].theirInception ) {
- g_log<<Logger::Info<<"Domain '"<< di.zone << "' is fresh, master " << remote.toStringWithPortExcept(53) << " is no longer signed but (some) signatures are still valid, serial is " << ourserial << endl;
+ else if (maxExpire >= now && !ssr.d_freshness[di.id].theirInception) {
+ g_log << Logger::Info << "Domain '" << di.zone << "' is fresh, primary " << remote.toStringWithPortExcept(53) << " is no longer signed but (some) signatures are still valid, serial is " << ourserial << endl;
di.backend->setFresh(di.id);
}
- else if(maxInception && ! ssr.d_freshness[di.id].theirInception ) {
- g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " is no longer signed and all signatures have expired, serial is " << ourserial << endl;
+ else if (maxInception && !ssr.d_freshness[di.id].theirInception) {
+ g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " is no longer signed and all signatures have expired, serial is " << ourserial << endl;
addSuckRequest(di.zone, remote, prio);
}
- else if(dk.doesDNSSEC() && ! maxInception && ssr.d_freshness[di.id].theirInception) {
- g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " has signed, serial is " << ourserial << endl;
+ else if (dk.doesDNSSEC() && !maxInception && ssr.d_freshness[di.id].theirInception) {
+ g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " has signed, serial is " << ourserial << endl;
addSuckRequest(di.zone, remote, prio);
}
else {
- g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is fresh, but RRSIGs differ on master " << remote.toStringWithPortExcept(53)<<", so DNSSEC is stale, serial is " << ourserial << endl;
+ g_log << Logger::Notice << "Domain '" << di.zone << "' is fresh, but RRSIGs differ on primary " << remote.toStringWithPortExcept(53) << ", so DNSSEC is stale, serial is " << ourserial << endl;
addSuckRequest(di.zone, remote, prio);
}
}
}
if (hasSOA) {
- g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " serial " << theirserial << ", our serial " << ourserial << endl;
+ g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << ", our serial " << ourserial << endl;
}
else {
- g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is empty, master " << remote.toStringWithPortExcept(53) << " serial " << theirserial << endl;
+ g_log << Logger::Notice << "Domain '" << di.zone << "' is empty, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << endl;
}
addSuckRequest(di.zone, remote, prio);
}
}
}
-vector<pair<DNSName, ComboAddress> > CommunicatorClass::getSuckRequests() {
- vector<pair<DNSName, ComboAddress> > ret;
+vector<pair<DNSName, ComboAddress>> CommunicatorClass::getSuckRequests()
+{
+ vector<pair<DNSName, ComboAddress>> ret;
auto data = d_data.lock();
ret.reserve(data->d_suckdomains.size());
- for (auto const &d : data->d_suckdomains) {
- ret.emplace_back(d.domain, d.master);
+ for (auto const& d : data->d_suckdomains) {
+ ret.emplace_back(d.domain, d.primary);
}
return ret;
}
-size_t CommunicatorClass::getSuckRequestsWaiting() {
+size_t CommunicatorClass::getSuckRequestsWaiting()
+{
return d_data.lock()->d_suckdomains.size();
}
uint32_t getRefreshInterval() const
{
+ // coverity[store_truncates_time_t]
return d_refreshinterval;
}
struct MapCombo
{
- MapCombo() {}
- ~MapCombo() {}
+ MapCombo() = default;
+ ~MapCombo() = default;
MapCombo(const MapCombo&) = delete;
MapCombo& operator=(const MapCombo&) = delete;
int n=0;
int numread;
while(n<bytes) {
- int res=waitForData(d_sock, timeoutsec-(time(nullptr)-start));
+ // coverity[store_truncates_time_t]
+ int res=waitForData(d_sock, static_cast<int>(timeoutsec - (time(nullptr) - start)));
if(res<0)
throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror());
if(!res)
d_listSubZoneQuery=getArg("list-subzone-query");
d_InfoOfDomainsZoneQuery=getArg("info-zone-query");
- d_InfoOfAllSlaveDomainsQuery=getArg("info-all-slaves-query");
- d_SuperMasterInfoQuery=getArg("supermaster-query");
- d_GetSuperMasterIPs=getArg("supermaster-name-to-ips");
- d_AddSuperMaster=getArg("supermaster-add");
+ d_InfoOfAllSecondaryDomainsQuery = getArg("info-all-secondaries-query");
+ d_AutoPrimaryInfoQuery = getArg("autoprimary-query");
+ d_GetAutoPrimaryIPs = getArg("autoprimary-name-to-ips");
+ d_AddAutoPrimary = getArg("autoprimary-add");
d_RemoveAutoPrimaryQuery=getArg("autoprimary-remove");
d_ListAutoPrimariesQuery=getArg("list-autoprimaries");
d_InsertZoneQuery=getArg("insert-zone-query");
d_InsertRecordQuery=getArg("insert-record-query");
- d_UpdateMasterOfZoneQuery=getArg("update-master-query");
+ d_UpdatePrimaryOfZoneQuery = getArg("update-primary-query");
d_UpdateKindOfZoneQuery=getArg("update-kind-query");
d_UpdateSerialOfZoneQuery=getArg("update-serial-query");
d_UpdateLastCheckOfZoneQuery=getArg("update-lastcheck-query");
d_UpdateOptionsOfZoneQuery = getArg("update-options-query");
d_UpdateCatalogOfZoneQuery = getArg("update-catalog-query");
d_UpdateAccountOfZoneQuery=getArg("update-account-query");
- d_InfoOfAllMasterDomainsQuery=getArg("info-all-master-query");
+ d_InfoOfAllPrimaryDomainsQuery = getArg("info-all-primary-query");
d_InfoProducerMembersQuery = getArg("info-producer-members-query");
d_InfoConsumerMembersQuery = getArg("info-consumer-members-query");
d_DeleteDomainQuery=getArg("delete-domain-query");
d_listQuery_stmt = nullptr;
d_listSubZoneQuery_stmt = nullptr;
d_InfoOfDomainsZoneQuery_stmt = nullptr;
- d_InfoOfAllSlaveDomainsQuery_stmt = nullptr;
- d_SuperMasterInfoQuery_stmt = nullptr;
- d_GetSuperMasterIPs_stmt = nullptr;
- d_AddSuperMaster_stmt = nullptr;
+ d_InfoOfAllSecondaryDomainsQuery_stmt = nullptr;
+ d_AutoPrimaryInfoQuery_stmt = nullptr;
+ d_GetAutoPrimaryIPs_stmt = nullptr;
+ d_AddAutoPrimary_stmt = nullptr;
d_RemoveAutoPrimary_stmt = nullptr;
d_ListAutoPrimaries_stmt = nullptr;
d_InsertZoneQuery_stmt = nullptr;
d_InsertRecordQuery_stmt = nullptr;
d_InsertEmptyNonTerminalOrderQuery_stmt = nullptr;
- d_UpdateMasterOfZoneQuery_stmt = nullptr;
+ d_UpdatePrimaryOfZoneQuery_stmt = nullptr;
d_UpdateKindOfZoneQuery_stmt = nullptr;
d_UpdateSerialOfZoneQuery_stmt = nullptr;
d_UpdateLastCheckOfZoneQuery_stmt = nullptr;
d_UpdateOptionsOfZoneQuery_stmt = nullptr;
d_UpdateCatalogOfZoneQuery_stmt = nullptr;
d_UpdateAccountOfZoneQuery_stmt = nullptr;
- d_InfoOfAllMasterDomainsQuery_stmt = nullptr;
+ d_InfoOfAllPrimaryDomainsQuery_stmt = nullptr;
d_InfoProducerMembersQuery_stmt = nullptr;
d_InfoConsumerMembersQuery_stmt = nullptr;
d_DeleteDomainQuery_stmt = nullptr;
try {
reconnectIfNeeded();
+ // clang-format off
d_UpdateSerialOfZoneQuery_stmt->
bind("serial", serial)->
bind("domain_id", domain_id)->
execute()->
reset();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to refresh domain_id "+std::to_string(domain_id)+": "+e.txtReason());
try {
reconnectIfNeeded();
- d_UpdateLastCheckOfZoneQuery_stmt->bind("last_check", lastcheck)->bind("domain_id", domain_id)->execute()->reset();
+ // clang-format off
+ d_UpdateLastCheckOfZoneQuery_stmt->
+ bind("last_check", lastcheck)->
+ bind("domain_id", domain_id)->
+ execute()->reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to update last_check for domain_id " + std::to_string(domain_id) + ": " + e.txtReason());
setLastCheck(domain_id, time(nullptr));
}
-bool GSQLBackend::setMasters(const DNSName &domain, const vector<ComboAddress> &masters)
+bool GSQLBackend::setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries)
{
- vector<string> masters_s;
- masters_s.reserve(masters.size());
- for (const auto& master : masters) {
- masters_s.push_back(master.toStringWithPortExcept(53));
+ vector<string> primaries_s;
+ primaries_s.reserve(primaries.size());
+ for (const auto& primary : primaries) {
+ primaries_s.push_back(primary.toStringWithPortExcept(53));
}
- auto tmp = boost::join(masters_s, ", ");
+ auto tmp = boost::join(primaries_s, ", ");
try {
reconnectIfNeeded();
- d_UpdateMasterOfZoneQuery_stmt->
+ // clang-format off
+ d_UpdatePrimaryOfZoneQuery_stmt->
bind("master", tmp)->
bind("domain", domain)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
- throw PDNSException("GSQLBackend unable to set masters of domain '"+domain.toLogString()+"' to " + tmp + ": "+e.txtReason());
+ throw PDNSException("GSQLBackend unable to set primaries of domain '" + domain.toLogString() + "' to " + tmp + ": " + e.txtReason());
}
return true;
}
try {
reconnectIfNeeded();
+ // clang-format off
d_UpdateKindOfZoneQuery_stmt->
bind("kind", toUpper(DomainInfo::getKindString(kind)))->
bind("domain", domain)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to set kind of domain '"+domain.toLogString()+"' to " + toUpper(DomainInfo::getKindString(kind)) + ": "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_UpdateAccountOfZoneQuery_stmt->
- bind("account", account)->
- bind("domain", domain)->
- execute()->
- reset();
+ bind("account", account)->
+ bind("domain", domain)->
+ execute()->
+ reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to set account of domain '"+domain.toLogString()+"' to '" + account + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_InfoOfDomainsZoneQuery_stmt->
bind("domain", domain)->
execute()->
getResult(d_result)->
reset();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to retrieve information about domain '" + domain.toLogString() + "': "+e.txtReason());
di.account = d_result[0][8];
di.kind = DomainInfo::stringToKind(type);
- vector<string> masters;
- stringtok(masters, d_result[0][2], " ,\t");
- for(const auto& m : masters)
- di.masters.emplace_back(m, 53);
+ vector<string> primaries;
+ stringtok(primaries, d_result[0][2], " ,\t");
+ for (const auto& m : primaries)
+ di.primaries.emplace_back(m, 53);
pdns::checked_stoi_into(di.last_check, d_result[0][3]);
pdns::checked_stoi_into(di.notified_serial, d_result[0][4]);
di.backend=this;
return true;
}
-void GSQLBackend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
+void GSQLBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
{
/*
list all domains that need refreshing for which we are secondary, and insert into
reconnectIfNeeded();
// clang-format off
- d_InfoOfAllSlaveDomainsQuery_stmt->
+ d_InfoOfAllSecondaryDomainsQuery_stmt->
execute()->
getResult(d_result)->
reset();
// clang-format on
}
catch (SSqlException &e) {
- throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of slave domains: " + e.txtReason());
+ throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of secondary domains: " + e.txtReason());
}
SOAData sd;
DomainInfo di;
- vector<string> masters;
+ vector<string> primaries;
unfreshDomains->reserve(d_result.size());
for (const auto& row : d_result) { // id, name, type, master, last_check, catalog, content
- ASSERT_ROW_COLUMNS("info-all-slaves-query", row, 6);
+ ASSERT_ROW_COLUMNS("info-all-secondaries-query", row, 6);
try {
di.zone = DNSName(row[1]);
continue;
}
- di.masters.clear();
- masters.clear();
- stringtok(masters, row[3], ", \t");
- for(const auto& m : masters) {
+ di.primaries.clear();
+ primaries.clear();
+ stringtok(primaries, row[3], ", \t");
+ for (const auto& m : primaries) {
try {
- di.masters.emplace_back(m, 53);
+ di.primaries.emplace_back(m, 53);
} catch(const PDNSException &e) {
- g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse master address '" << m << "' for zone '" << di.zone << "': " << e.reason << endl;
+ g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse primary address '" << m << "' for zone '" << di.zone << "': " << e.reason << endl;
}
}
- if (di.masters.empty()) {
- g_log << Logger::Warning << __PRETTY_FUNCTION__ << " no masters for secondary zone '" << di.zone << "' found in the database" << endl;
+ if (di.primaries.empty()) {
+ g_log << Logger::Warning << __PRETTY_FUNCTION__ << " no primaries for secondary zone '" << di.zone << "' found in the database" << endl;
continue;
}
if (pdns_iequals(row[2], "SLAVE")) {
- di.kind = DomainInfo::Slave;
+ di.kind = DomainInfo::Secondary;
}
else if (pdns_iequals(row[2], "CONSUMER")) {
di.kind = DomainInfo::Consumer;
}
}
-void GSQLBackend::getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+void GSQLBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
{
/*
list all domains that need notifications for which we are promary, and insert into
reconnectIfNeeded();
// clang-format off
- d_InfoOfAllMasterDomainsQuery_stmt->
+ d_InfoOfAllPrimaryDomainsQuery_stmt->
execute()->
getResult(d_result)->
reset();
// clang-format on
}
catch(SSqlException &e) {
- throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of master domains: " + e.txtReason());
+ throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of primary domains: " + e.txtReason());
}
SOAData sd;
updatedDomains.reserve(d_result.size());
for (const auto& row : d_result) { // id, name, type, notified_serial, options, catalog, content
- ASSERT_ROW_COLUMNS("info-all-master-query", row, 7);
+ ASSERT_ROW_COLUMNS("info-all-primary-query", row, 7);
di.backend = this;
}
if (di.notified_serial != sd.serial) {
- di.kind = DomainInfo::Master;
+ di.kind = DomainInfo::Primary;
di.serial = sd.serial;
di.catalog.clear();
}
if (row.size() >= 4) { // Consumer only
- vector<string> masters;
- stringtok(masters, row[3], ", \t");
- for (const auto& m : masters) {
+ vector<string> primaries;
+ stringtok(primaries, row[3], ", \t");
+ for (const auto& m : primaries) {
try {
ci.d_primaries.emplace_back(m, 53);
}
catch (const PDNSException& e) {
- g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse master address '" << m << "' for zone '" << ci.d_zone << "': " << e.reason << endl;
+ g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse primary address '" << m << "' for zone '" << ci.d_zone << "': " << e.reason << endl;
members.clear();
return false;
}
try {
reconnectIfNeeded();
+ // clang-format off
d_updateOrderNameAndAuthQuery_stmt->
bind("ordername", ordername.labelReverse().toString(" ", false))->
bind("auth", auth)->
bind("qname", qname)->
execute()->
reset();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to update ordername and auth for " + qname.toLogString() + " for domain_id "+std::to_string(domain_id)+", domain name '" + qname.toLogString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_updateOrderNameAndAuthTypeQuery_stmt->
bind("ordername", ordername.labelReverse().toString(" ", false))->
bind("auth", auth)->
bind("qtype", QType(qtype).toString())->
execute()->
reset();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to update ordername and auth for " + qname.toLogString() + "|" + QType(qtype).toString() + " for domain_id "+std::to_string(domain_id)+": "+e.txtReason());
reconnectIfNeeded();
try {
+ // clang-format off
d_nullifyOrderNameAndUpdateAuthQuery_stmt->
bind("auth", auth)->
bind("domain_id", domain_id)->
bind("qname", qname)->
execute()->
reset();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to nullify ordername and update auth for " + qname.toLogString() + " for domain_id "+std::to_string(domain_id)+": "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_nullifyOrderNameAndUpdateAuthTypeQuery_stmt->
bind("auth", auth)->
bind("domain_id", domain_id)->
bind("qtype", QType(qtype).toString())->
execute()->
reset();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to nullify ordername and update auth for " + qname.toLogString() + "|" + QType(qtype).toString() + " for domain_id "+std::to_string(domain_id)+": "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_RemoveEmptyNonTerminalsFromZoneQuery_stmt->
bind("domain_id", domain_id)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to delete empty non-terminal records from domain_id "+std::to_string(domain_id)+": "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_DeleteEmptyNonTerminalQuery_stmt->
bind("domain_id", domain_id)->
bind("qname", qname)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to delete empty non-terminal rr '"+qname.toLogString()+"' from domain_id "+std::to_string(domain_id)+": "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_InsertEmptyNonTerminalOrderQuery_stmt->
bind("domain_id", domain_id)->
bind("qname", qname)->
bind("auth", true)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to insert empty non-terminal rr '"+qname.toLogString()+"' in domain_id "+std::to_string(domain_id)+": "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_afterOrderQuery_stmt->
bind("ordername", qname.labelReverse().toString(" ", false))->
bind("domain_id", id)->
execute();
+ // clang-format on
while(d_afterOrderQuery_stmt->hasNextRow()) {
d_afterOrderQuery_stmt->nextRow(row);
ASSERT_ROW_COLUMNS("get-order-after-query", row, 1);
try {
reconnectIfNeeded();
+ // clang-format off
d_firstOrderQuery_stmt->
bind("domain_id", id)->
execute();
+ // clang-format on
while(d_firstOrderQuery_stmt->hasNextRow()) {
d_firstOrderQuery_stmt->nextRow(row);
ASSERT_ROW_COLUMNS("get-order-first-query", row, 1);
try {
reconnectIfNeeded();
+ // clang-format off
d_beforeOrderQuery_stmt->
bind("ordername", qname.labelReverse().toString(" ", false))->
bind("domain_id", id)->
execute();
+ // clang-format on
while(d_beforeOrderQuery_stmt->hasNextRow()) {
d_beforeOrderQuery_stmt->nextRow(row);
ASSERT_ROW_COLUMNS("get-order-before-query", row, 2);
try {
reconnectIfNeeded();
+ // clang-format off
d_lastOrderQuery_stmt->
bind("domain_id", id)->
execute();
+ // clang-format on
while(d_lastOrderQuery_stmt->hasNextRow()) {
d_lastOrderQuery_stmt->nextRow(row);
ASSERT_ROW_COLUMNS("get-order-last-query", row, 2);
try {
reconnectIfNeeded();
+ // clang-format off
d_AddDomainKeyQuery_stmt->
bind("flags", key.flags)->
bind("active", key.active)->
bind("content", key.content)->
bind("domain", name)->
execute();
+ // clang-format on
if (d_AddDomainKeyQuery_stmt->hasNextRow()) {
SSqlStatement::row_t row;
try {
reconnectIfNeeded();
+ // clang-format off
d_ActivateDomainKeyQuery_stmt->
bind("domain", name)->
bind("key_id", id)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to activate key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_DeactivateDomainKeyQuery_stmt->
bind("domain", name)->
bind("key_id", id)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to deactivate key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_PublishDomainKeyQuery_stmt->
bind("domain", name)->
bind("key_id", id)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to publish key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_UnpublishDomainKeyQuery_stmt->
bind("domain", name)->
bind("key_id", id)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to unpublish key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_RemoveDomainKeyQuery_stmt->
bind("domain", name)->
bind("key_id", id)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to remove key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_getTSIGKeyQuery_stmt->
bind("key_name", name)->
execute();
+ // clang-format on
SSqlStatement::row_t row;
try {
reconnectIfNeeded();
+ // clang-format off
d_setTSIGKeyQuery_stmt->
bind("key_name", name)->
bind("algorithm", algorithm)->
bind("content", content)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to store TSIG key with name '" + name.toLogString() + "' and algorithm '" + algorithm.toString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_deleteTSIGKeyQuery_stmt->
bind("key_name", name)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to delete TSIG key with name '" + name.toLogString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_getTSIGKeysQuery_stmt->
execute();
+ // clang-format on
SSqlStatement::row_t row;
try {
reconnectIfNeeded();
+ // clang-format off
d_ListDomainKeysQuery_stmt->
bind("domain", name)->
execute();
+ // clang-format on
SSqlStatement::row_t row;
KeyData kd;
try {
reconnectIfNeeded();
+ // clang-format off
d_GetAllDomainMetadataQuery_stmt->
bind("domain", name)->
execute();
+ // clang-format on
SSqlStatement::row_t row;
try {
reconnectIfNeeded();
+ // clang-format off
d_GetDomainMetadataQuery_stmt->
bind("domain", name)->
bind("kind", kind)->
execute();
+ // clang-format on
SSqlStatement::row_t row;
try {
reconnectIfNeeded();
+ // clang-format off
d_ClearDomainMetadataQuery_stmt->
bind("domain", name)->
bind("kind", kind)->
execute()->
reset();
+ // clang-format on
if(!meta.empty()) {
for(const auto& value: meta) {
+ // clang-format off
d_SetDomainMetadataQuery_stmt->
bind("kind", kind)->
bind("content", value)->
bind("domain", name)->
execute()->
reset();
+ // clang-format on
}
}
}
if(domain_id < 0) {
d_query_name = "basic-query";
d_query_stmt = &d_NoIdQuery_stmt;
+ // clang-format off
(*d_query_stmt)->
bind("qtype", qtype.toString())->
bind("qname", qname);
+ // clang-format on
} else {
d_query_name = "id-query";
d_query_stmt = &d_IdQuery_stmt;
+ // clang-format off
(*d_query_stmt)->
bind("qtype", qtype.toString())->
bind("qname", qname)->
bind("domain_id", domain_id);
+ // clang-format on
}
} else {
// qtype==ANY
if(domain_id < 0) {
d_query_name = "any-query";
d_query_stmt = &d_ANYNoIdQuery_stmt;
+ // clang-format off
(*d_query_stmt)->
bind("qname", qname);
+ // clang-format on
} else {
d_query_name = "any-id-query";
d_query_stmt = &d_ANYIdQuery_stmt;
+ // clang-format off
(*d_query_stmt)->
bind("qname", qname)->
bind("domain_id", domain_id);
+ // clang-format on
}
}
d_query_name = "list-query";
d_query_stmt = &d_listQuery_stmt;
+ // clang-format off
(*d_query_stmt)->
bind("include_disabled", (int)include_disabled)->
bind("domain_id", domain_id)->
execute();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to list domain '" + target.toLogString() + "': "+e.txtReason());
d_query_name = "list-subzone-query";
d_query_stmt = &d_listSubZoneQuery_stmt;
+ // clang-format off
(*d_query_stmt)->
bind("zone", zone)->
bind("wildzone", wildzone)->
bind("domain_id", domain_id)->
execute();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to list SubZones for domain '" + zone.toLogString() + "': "+e.txtReason());
return false;
}
-bool GSQLBackend::superMasterAdd(const AutoPrimary& primary)
+bool GSQLBackend::autoPrimaryAdd(const AutoPrimary& primary)
{
try{
reconnectIfNeeded();
- d_AddSuperMaster_stmt ->
+ // clang-format off
+ d_AddAutoPrimary_stmt ->
bind("ip",primary.ip)->
bind("nameserver",primary.nameserver)->
bind("account",primary.account)->
execute()->
reset();
-
+ // clang-format on
}
catch (SSqlException &e){
throw PDNSException("GSQLBackend unable to insert an autoprimary with IP " + primary.ip + " and nameserver name '" + primary.nameserver + "' and account '" + primary.account + "': " + e.txtReason());
try{
reconnectIfNeeded();
+ // clang-format off
d_RemoveAutoPrimary_stmt ->
bind("ip",primary.ip)->
bind("nameserver",primary.nameserver)->
execute()->
reset();
-
+ // clang-format on
}
catch (SSqlException &e){
throw PDNSException("GSQLBackend unable to remove an autoprimary with IP " + primary.ip + " and nameserver name '" + primary.nameserver + "': " + e.txtReason());
try{
reconnectIfNeeded();
+ // clang-format off
d_ListAutoPrimaries_stmt->
execute()->
getResult(d_result)->
reset();
+ // clang-format on
}
catch (SSqlException &e){
throw PDNSException("GSQLBackend unable to list autoprimaries: " + e.txtReason());
return true;
}
-bool GSQLBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **ddb)
+bool GSQLBackend::autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
{
// check if we know the ip/ns couple in the database
for(const auto & i : nsset) {
try {
reconnectIfNeeded();
- d_SuperMasterInfoQuery_stmt->
+ // clang-format off
+ d_AutoPrimaryInfoQuery_stmt->
bind("ip", ip)->
bind("nameserver", i.content)->
execute()->
getResult(d_result)->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
- throw PDNSException("GSQLBackend unable to search for a supermaster with IP " + ip + " and nameserver name '" + i.content + "' for domain '" + domain.toLogString() + "': "+e.txtReason());
+ throw PDNSException("GSQLBackend unable to search for a autoprimary with IP " + ip + " and nameserver name '" + i.content + "' for domain '" + domain.toLogString() + "': " + e.txtReason());
}
if(!d_result.empty()) {
- ASSERT_ROW_COLUMNS("supermaster-query", d_result[0], 1);
+ ASSERT_ROW_COLUMNS("autoprimary-query", d_result[0], 1);
*nameserver=i.content;
*account=d_result[0][0];
*ddb=this;
return false;
}
-bool GSQLBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& masters, const string& account)
+bool GSQLBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
{
- vector<string> masters_s;
- masters_s.reserve(masters.size());
- for (const auto& master : masters) {
- masters_s.push_back(master.toStringWithPortExcept(53));
+ vector<string> primaries_s;
+ primaries_s.reserve(primaries.size());
+ for (const auto& primary : primaries) {
+ primaries_s.push_back(primary.toStringWithPortExcept(53));
}
try {
d_InsertZoneQuery_stmt->
bind("type", toUpper(DomainInfo::getKindString(kind)))->
bind("domain", domain)->
- bind("masters", boost::join(masters_s, ", "))->
+ bind("primaries", boost::join(primaries_s, ", "))->
bind("account", account)->
execute()->
reset();
return true;
}
-bool GSQLBackend::createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
+bool GSQLBackend::createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
{
string name;
- vector<ComboAddress> masters({ComboAddress(ip, 53)});
+ vector<ComboAddress> primaries({ComboAddress(ip, 53)});
try {
if (!nameserver.empty()) {
- // figure out all IP addresses for the master
+ // figure out all IP addresses for the primary
reconnectIfNeeded();
- d_GetSuperMasterIPs_stmt->
+ // clang-format off
+ d_GetAutoPrimaryIPs_stmt->
bind("nameserver", nameserver)->
bind("account", account)->
execute()->
getResult(d_result)->
reset();
+ // clang-format on
if (!d_result.empty()) {
// collect all IP addresses
vector<ComboAddress> tmp;
- for(const auto& row: d_result) {
- if (account == row[1])
+ for (const auto& row: d_result) {
+ if (account == row[1]) {
tmp.emplace_back(row[0], 53);
+ }
}
- // set them as domain's masters, comma separated
- masters = tmp;
+ // set them as domain's primaries, comma separated
+ primaries = std::move(tmp);
}
}
- createDomain(domain, DomainInfo::Slave, masters, account);
+ createDomain(domain, DomainInfo::Secondary, primaries, account);
}
catch(SSqlException &e) {
- throw PDNSException("Database error trying to insert new slave domain '"+domain.toLogString()+"': "+ e.txtReason());
+ throw PDNSException("Database error trying to insert new secondary domain '" + domain.toLogString() + "': " + e.txtReason());
}
return true;
}
try {
reconnectIfNeeded();
+ // clang-format off
d_DeleteZoneQuery_stmt->
bind("domain_id", di.id)->
execute()->
bind("domain", domain)->
execute()->
reset();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("Database error trying to delete domain '"+domain.toLogString()+"': "+ e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_getAllDomainsQuery_stmt->
bind("include_disabled", (int)include_disabled)->
execute();
+ // clang-format on
SSqlStatement::row_t row;
while (d_getAllDomainsQuery_stmt->hasNextRow()) {
d_getAllDomainsQuery_stmt->nextRow(row);
- ASSERT_ROW_COLUMNS("get-all-domains-query", row, 8);
+ ASSERT_ROW_COLUMNS("get-all-domains-query", row, 9);
DomainInfo di;
pdns::checked_stoi_into(di.id, row[0]);
try {
di.zone = DNSName(row[1]);
+ if (!row[8].empty()) {
+ di.catalog = DNSName(row[8]);
+ }
} catch (...) {
continue;
}
if (pdns_iequals(row[3], "MASTER")) {
- di.kind = DomainInfo::Master;
+ di.kind = DomainInfo::Primary;
} else if (pdns_iequals(row[3], "SLAVE")) {
- di.kind = DomainInfo::Slave;
+ di.kind = DomainInfo::Secondary;
} else if (pdns_iequals(row[3], "NATIVE")) {
di.kind = DomainInfo::Native;
}
}
if (!row[4].empty()) {
- vector<string> masters;
- stringtok(masters, row[4], " ,\t");
- for(const auto& m : masters) {
+ vector<string> primaries;
+ stringtok(primaries, row[4], " ,\t");
+ for (const auto& m : primaries) {
try {
- di.masters.emplace_back(m, 53);
+ di.primaries.emplace_back(m, 53);
} catch(const PDNSException &e) {
- g_log<<Logger::Warning<<"Could not parse master address ("<<m<<") for zone '"<<di.zone<<"': "<<e.reason;
+ g_log << Logger::Warning << "Could not parse primary address (" << m << ") for zone '" << di.zone << "': " << e.reason;
}
}
}
if (qt != QType::ANY) {
if (d_upgradeContent) {
+ // clang-format off
d_DeleteRRSetQuery_stmt->
bind("domain_id", domain_id)->
bind("qname", qname)->
bind("qtype", "TYPE"+std::to_string(qt.getCode()))->
execute()->
reset();
+ // clang-format on
}
+ // clang-format off
d_DeleteRRSetQuery_stmt->
bind("domain_id", domain_id)->
bind("qname", qname)->
bind("qtype", qt.toString())->
execute()->
reset();
+ // clang-format on
} else {
+ // clang-format off
d_DeleteNamesQuery_stmt->
bind("domain_id", domain_id)->
bind("qname", qname)->
execute()->
reset();
+ // clang-format on
}
}
catch (SSqlException &e) {
try {
reconnectIfNeeded();
+ // clang-format off
d_DeleteCommentRRsetQuery_stmt->
bind("domain_id", domain_id)->
bind("qname", qname)->
bind("qtype", qt.toString())->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to delete comment for RRSet " + qname.toLogString() + "|" + qt.toString() + ": "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_InsertRecordQuery_stmt->
- bind("content",content)->
- bind("ttl",r.ttl)->
- bind("priority",prio)->
- bind("qtype",r.qtype.toString())->
- bind("domain_id",r.domain_id)->
- bind("disabled",r.disabled)->
- bind("qname",r.qname);
+ bind("content", content)->
+ bind("ttl", r.ttl)->
+ bind("priority", prio)->
+ bind("qtype", r.qtype.toString())->
+ bind("domain_id", r.domain_id)->
+ bind("disabled", r.disabled)->
+ bind("qname", r.qname);
+ // clang-format on
if (!ordername.empty())
d_InsertRecordQuery_stmt->bind("ordername", ordername.labelReverse().makeLowerCase().toString(" ", false));
try {
reconnectIfNeeded();
+ // clang-format off
d_InsertEmptyNonTerminalOrderQuery_stmt->
- bind("domain_id",domain_id)->
+ bind("domain_id", domain_id)->
bind("qname", nt.first)->
bindNull("ordername")->
- bind("auth",(nt.second || !d_dnssecQueries))->
+ bind("auth", (nt.second || !d_dnssecQueries))->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to feed empty non-terminal with name '" + nt.first.toLogString() + "': "+e.txtReason());
try {
reconnectIfNeeded();
+ // clang-format off
d_InsertEmptyNonTerminalOrderQuery_stmt->
- bind("domain_id",domain_id)->
+ bind("domain_id", domain_id)->
bind("qname", nt.first);
+ // clang-format on
if (narrow || !nt.second) {
+ // clang-format off
d_InsertEmptyNonTerminalOrderQuery_stmt->
bindNull("ordername");
+ // clang-format on
} else {
ordername=toBase32Hex(hashQNameWithSalt(ns3prc, nt.first));
+ // clang-format off
d_InsertEmptyNonTerminalOrderQuery_stmt->
bind("ordername", ordername);
+ // clang-format on
}
+ // clang-format off
d_InsertEmptyNonTerminalOrderQuery_stmt->
- bind("auth",nt.second)->
+ bind("auth", nt.second)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to feed empty non-terminal with name '" + nt.first.toLogString() + "' (hashed name '"+ toBase32Hex(hashQNameWithSalt(ns3prc, nt.first)) + "') : "+e.txtReason());
d_db->startTransaction();
d_inTransaction = true;
if(domain_id >= 0) {
+ // clang-format off
d_DeleteZoneQuery_stmt->
bind("domain_id", domain_id)->
execute()->
reset();
+ // clang-format on
}
}
catch (SSqlException &e) {
d_query_name = "list-comments-query";
d_query_stmt = &d_ListCommentsQuery_stmt;
+ // clang-format off
(*d_query_stmt)->
bind("domain_id", domain_id)->
execute();
+ // clang-format on
}
catch(SSqlException &e) {
throw PDNSException("GSQLBackend unable to list comments for domain id " + std::to_string(domain_id) + ": "+e.txtReason());
}
}
-void GSQLBackend::feedComment(const Comment& comment)
+bool GSQLBackend::feedComment(const Comment& comment)
{
try {
reconnectIfNeeded();
+ // clang-format off
d_InsertCommentQuery_stmt->
- bind("domain_id",comment.domain_id)->
- bind("qname",comment.qname)->
- bind("qtype",comment.qtype.toString())->
- bind("modified_at",comment.modified_at)->
- bind("account",comment.account)->
- bind("content",comment.content)->
+ bind("domain_id", comment.domain_id)->
+ bind("qname", comment.qname)->
+ bind("qtype", comment.qtype.toString())->
+ bind("modified_at", comment.modified_at)->
+ bind("account", comment.account)->
+ bind("content", comment.content)->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to feed comment for RRSet '" + comment.qname.toLogString() + "|" + comment.qtype.toString() + "': "+e.txtReason());
}
+
+ return true;
}
bool GSQLBackend::replaceComments(const uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<Comment>& comments)
throw PDNSException("replaceComments called outside of transaction");
}
+ // clang-format off
d_DeleteCommentRRsetQuery_stmt->
- bind("domain_id",domain_id)->
+ bind("domain_id", domain_id)->
bind("qname", qname)->
- bind("qtype",qt.toString())->
+ bind("qtype", qt.toString())->
execute()->
reset();
+ // clang-format on
}
catch (SSqlException &e) {
throw PDNSException("GSQLBackend unable to delete comment for RRSet '" + qname.toLogString() + "|" + qt.toString() + "': "+e.txtReason());
return escaped_pattern;
}
-bool GSQLBackend::searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result)
+bool GSQLBackend::searchRecords(const string &pattern, size_t maxResults, vector<DNSResourceRecord>& result)
{
d_qname.clear();
string escaped_pattern = pattern2SQLPattern(pattern);
try {
reconnectIfNeeded();
+ // clang-format off
d_SearchRecordsQuery_stmt->
bind("value", escaped_pattern)->
bind("value2", escaped_pattern)->
bind("limit", maxResults)->
execute();
+ // clang-format on
while(d_SearchRecordsQuery_stmt->hasNextRow())
{
}
}
-bool GSQLBackend::searchComments(const string &pattern, int maxResults, vector<Comment>& result)
+bool GSQLBackend::searchComments(const string &pattern, size_t maxResults, vector<Comment>& result)
{
Comment c;
string escaped_pattern = pattern2SQLPattern(pattern);
try {
reconnectIfNeeded();
+ // clang-format off
d_SearchCommentsQuery_stmt->
bind("value", escaped_pattern)->
bind("value2", escaped_pattern)->
bind("limit", maxResults)->
execute();
+ // clang-format on
while(d_SearchCommentsQuery_stmt->hasNextRow()) {
SSqlStatement::row_t row;
r.qtype=row[3];
- if (d_upgradeContent && DNSRecordContent::isUnknownType(row[3]) && r.qtype.isSupportedType()) {
+ if (d_upgradeContent && DNSRecordContent::isUnknownType(row[3]) && DNSRecordContent::isRegisteredType(r.qtype, r.qclass)) {
r.content = DNSRecordContent::upgradeContent(r.qname, r.qtype, row[0]);
}
else if (r.qtype==QType::MX || r.qtype==QType::SRV) {
comment.content = std::move(row[5]);
}
-SSqlStatement::~SSqlStatement() {
// make sure vtable won't break
-}
+SSqlStatement::~SSqlStatement() = default;
bool isDnssecDomainMetadata (const string& name);
-/*
+/*
GSQLBackend is a generic backend used by other sql backends
*/
class GSQLBackend : public DNSBackend
{
public:
GSQLBackend(const string &mode, const string &suffix); //!< Makes our connection to the database. Throws an exception if it fails.
- virtual ~GSQLBackend()
+ ~GSQLBackend() override
{
freeStatements();
d_db.reset();
}
-
- void setDB(SSql *db)
+
+ void setDB(std::unique_ptr<SSql>&& database)
{
freeStatements();
- d_db=std::unique_ptr<SSql>(db);
+ d_db = std::move(database);
if (d_db) {
d_db->setLog(::arg().mustDo("query-logging"));
}
d_ANYIdQuery_stmt = d_db->prepare(d_ANYIdQuery, 2);
d_listQuery_stmt = d_db->prepare(d_listQuery, 2);
d_listSubZoneQuery_stmt = d_db->prepare(d_listSubZoneQuery, 3);
- d_MasterOfDomainsZoneQuery_stmt = d_db->prepare(d_MasterOfDomainsZoneQuery, 1);
+ d_PrimaryOfDomainsZoneQuery_stmt = d_db->prepare(d_PrimaryOfDomainsZoneQuery, 1);
d_InfoOfDomainsZoneQuery_stmt = d_db->prepare(d_InfoOfDomainsZoneQuery, 1);
- d_InfoOfAllSlaveDomainsQuery_stmt = d_db->prepare(d_InfoOfAllSlaveDomainsQuery, 0);
- d_SuperMasterInfoQuery_stmt = d_db->prepare(d_SuperMasterInfoQuery, 2);
- d_GetSuperMasterIPs_stmt = d_db->prepare(d_GetSuperMasterIPs, 2);
- d_AddSuperMaster_stmt = d_db->prepare(d_AddSuperMaster, 3);
+ d_InfoOfAllSecondaryDomainsQuery_stmt = d_db->prepare(d_InfoOfAllSecondaryDomainsQuery, 0);
+ d_AutoPrimaryInfoQuery_stmt = d_db->prepare(d_AutoPrimaryInfoQuery, 2);
+ d_GetAutoPrimaryIPs_stmt = d_db->prepare(d_GetAutoPrimaryIPs, 2);
+ d_AddAutoPrimary_stmt = d_db->prepare(d_AddAutoPrimary, 3);
d_RemoveAutoPrimary_stmt = d_db->prepare(d_RemoveAutoPrimaryQuery, 2);
d_ListAutoPrimaries_stmt = d_db->prepare(d_ListAutoPrimariesQuery, 0);
d_InsertZoneQuery_stmt = d_db->prepare(d_InsertZoneQuery, 4);
d_InsertRecordQuery_stmt = d_db->prepare(d_InsertRecordQuery, 9);
d_InsertEmptyNonTerminalOrderQuery_stmt = d_db->prepare(d_InsertEmptyNonTerminalOrderQuery, 4);
- d_UpdateMasterOfZoneQuery_stmt = d_db->prepare(d_UpdateMasterOfZoneQuery, 2);
+ d_UpdatePrimaryOfZoneQuery_stmt = d_db->prepare(d_UpdatePrimaryOfZoneQuery, 2);
d_UpdateKindOfZoneQuery_stmt = d_db->prepare(d_UpdateKindOfZoneQuery, 2);
d_UpdateOptionsOfZoneQuery_stmt = d_db->prepare(d_UpdateOptionsOfZoneQuery, 2);
d_UpdateCatalogOfZoneQuery_stmt = d_db->prepare(d_UpdateCatalogOfZoneQuery, 2);
d_UpdateAccountOfZoneQuery_stmt = d_db->prepare(d_UpdateAccountOfZoneQuery, 2);
d_UpdateSerialOfZoneQuery_stmt = d_db->prepare(d_UpdateSerialOfZoneQuery, 2);
d_UpdateLastCheckOfZoneQuery_stmt = d_db->prepare(d_UpdateLastCheckOfZoneQuery, 2);
- d_InfoOfAllMasterDomainsQuery_stmt = d_db->prepare(d_InfoOfAllMasterDomainsQuery, 0);
+ d_InfoOfAllPrimaryDomainsQuery_stmt = d_db->prepare(d_InfoOfAllPrimaryDomainsQuery, 0);
d_InfoProducerMembersQuery_stmt = d_db->prepare(d_InfoProducerMembersQuery, 1);
d_InfoConsumerMembersQuery_stmt = d_db->prepare(d_InfoConsumerMembersQuery, 1);
d_DeleteDomainQuery_stmt = d_db->prepare(d_DeleteDomainQuery, 1);
d_ANYIdQuery_stmt.reset();
d_listQuery_stmt.reset();
d_listSubZoneQuery_stmt.reset();
- d_MasterOfDomainsZoneQuery_stmt.reset();
+ d_PrimaryOfDomainsZoneQuery_stmt.reset();
d_InfoOfDomainsZoneQuery_stmt.reset();
- d_InfoOfAllSlaveDomainsQuery_stmt.reset();
- d_SuperMasterInfoQuery_stmt.reset();
- d_GetSuperMasterIPs_stmt.reset();
- d_AddSuperMaster_stmt.reset();
+ d_InfoOfAllSecondaryDomainsQuery_stmt.reset();
+ d_AutoPrimaryInfoQuery_stmt.reset();
+ d_GetAutoPrimaryIPs_stmt.reset();
+ d_AddAutoPrimary_stmt.reset();
d_RemoveAutoPrimary_stmt.reset();
d_ListAutoPrimaries_stmt.reset();
d_InsertZoneQuery_stmt.reset();
d_InsertRecordQuery_stmt.reset();
d_InsertEmptyNonTerminalOrderQuery_stmt.reset();
- d_UpdateMasterOfZoneQuery_stmt.reset();
+ d_UpdatePrimaryOfZoneQuery_stmt.reset();
d_UpdateKindOfZoneQuery_stmt.reset();
d_UpdateOptionsOfZoneQuery_stmt.reset();
d_UpdateCatalogOfZoneQuery_stmt.reset();
d_UpdateAccountOfZoneQuery_stmt.reset();
d_UpdateSerialOfZoneQuery_stmt.reset();
d_UpdateLastCheckOfZoneQuery_stmt.reset();
- d_InfoOfAllMasterDomainsQuery_stmt.reset();
+ d_InfoOfAllPrimaryDomainsQuery_stmt.reset();
d_InfoProducerMembersQuery_stmt.reset();
d_InfoConsumerMembersQuery_stmt.reset();
d_DeleteDomainQuery_stmt.reset();
bool feedRecord(const DNSResourceRecord &r, const DNSName &ordername, bool ordernameIsNSEC3=false) override;
bool feedEnts(int domain_id, map<DNSName,bool>& nonterm) override;
bool feedEnts3(int domain_id, const DNSName &domain, map<DNSName,bool> &nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow) override;
- bool createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& masters, const string& account) override;
- bool createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
+ bool createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account) override;
+ bool createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
bool deleteDomain(const DNSName &domain) override;
- bool superMasterAdd(const AutoPrimary& primary) override;
+ bool autoPrimaryAdd(const AutoPrimary& primary) override;
bool autoPrimaryRemove(const AutoPrimary& primary) override;
bool autoPrimariesList(std::vector<AutoPrimary>& primaries) override;
- bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db) override;
+ bool autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** db) override;
void setStale(uint32_t domain_id) override;
void setFresh(uint32_t domain_id) override;
- void getUnfreshSlaveInfos(vector<DomainInfo> *domains) override;
- void getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+ void getUnfreshSecondaryInfos(vector<DomainInfo>* domains) override;
+ void getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
bool getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type) override;
bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true) override;
void setNotified(uint32_t domain_id, uint32_t serial) override;
- bool setMasters(const DNSName &domain, const vector<ComboAddress> &masters) override;
+ bool setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries) override;
bool setKind(const DNSName &domain, const DomainInfo::DomainKind kind) override;
bool setOptions(const DNSName& domain, const string& options) override;
bool setCatalog(const DNSName& domain, const DNSName& catalog) override;
bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta) override;
bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override;
bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override;
-
+
bool removeDomainKey(const DNSName& name, unsigned int id) override;
bool activateDomainKey(const DNSName& name, unsigned int id) override;
bool deactivateDomainKey(const DNSName& name, unsigned int id) override;
bool listComments(const uint32_t domain_id) override;
bool getComment(Comment& comment) override;
- void feedComment(const Comment& comment) override;
+ bool feedComment(const Comment& comment) override;
bool replaceComments(const uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<Comment>& comments) override;
string directBackendCmd(const string &query) override;
- bool searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result) override;
- bool searchComments(const string &pattern, int maxResults, vector<Comment>& result) override;
+ bool searchRecords(const string &pattern, size_t maxResults, vector<DNSResourceRecord>& result) override;
+ bool searchComments(const string &pattern, size_t maxResults, vector<Comment>& result) override;
protected:
string pattern2SQLPattern(const string& pattern);
reconnect();
}
virtual void reconnect() { }
- virtual bool inTransaction() override
+ bool inTransaction() override
{
return d_inTransaction;
}
string d_listSubZoneQuery;
string d_logprefix;
- string d_MasterOfDomainsZoneQuery;
+ string d_PrimaryOfDomainsZoneQuery;
string d_InfoOfDomainsZoneQuery;
- string d_InfoOfAllSlaveDomainsQuery;
- string d_SuperMasterInfoQuery;
- string d_GetSuperMasterName;
- string d_GetSuperMasterIPs;
- string d_AddSuperMaster;
+ string d_InfoOfAllSecondaryDomainsQuery;
+ string d_AutoPrimaryInfoQuery;
+ string d_GetAutoPrimaryName;
+ string d_GetAutoPrimaryIPs;
+ string d_AddAutoPrimary;
string d_RemoveAutoPrimaryQuery;
string d_ListAutoPrimariesQuery;
string d_InsertZoneQuery;
string d_InsertRecordQuery;
string d_InsertEmptyNonTerminalOrderQuery;
- string d_UpdateMasterOfZoneQuery;
+ string d_UpdatePrimaryOfZoneQuery;
string d_UpdateKindOfZoneQuery;
string d_UpdateOptionsOfZoneQuery;
string d_UpdateCatalogOfZoneQuery;
string d_UpdateAccountOfZoneQuery;
string d_UpdateSerialOfZoneQuery;
string d_UpdateLastCheckOfZoneQuery;
- string d_InfoOfAllMasterDomainsQuery;
+ string d_InfoOfAllPrimaryDomainsQuery;
string d_InfoProducerMembersQuery;
string d_InfoConsumerMembersQuery;
string d_DeleteDomainQuery;
unique_ptr<SSqlStatement> d_ANYIdQuery_stmt;
unique_ptr<SSqlStatement> d_listQuery_stmt;
unique_ptr<SSqlStatement> d_listSubZoneQuery_stmt;
- unique_ptr<SSqlStatement> d_MasterOfDomainsZoneQuery_stmt;
+ unique_ptr<SSqlStatement> d_PrimaryOfDomainsZoneQuery_stmt;
unique_ptr<SSqlStatement> d_InfoOfDomainsZoneQuery_stmt;
- unique_ptr<SSqlStatement> d_InfoOfAllSlaveDomainsQuery_stmt;
- unique_ptr<SSqlStatement> d_SuperMasterInfoQuery_stmt;
- unique_ptr<SSqlStatement> d_GetSuperMasterIPs_stmt;
- unique_ptr<SSqlStatement> d_AddSuperMaster_stmt;
+ unique_ptr<SSqlStatement> d_InfoOfAllSecondaryDomainsQuery_stmt;
+ unique_ptr<SSqlStatement> d_AutoPrimaryInfoQuery_stmt;
+ unique_ptr<SSqlStatement> d_GetAutoPrimaryIPs_stmt;
+ unique_ptr<SSqlStatement> d_AddAutoPrimary_stmt;
unique_ptr<SSqlStatement> d_RemoveAutoPrimary_stmt;
unique_ptr<SSqlStatement> d_ListAutoPrimaries_stmt;
unique_ptr<SSqlStatement> d_InsertZoneQuery_stmt;
unique_ptr<SSqlStatement> d_InsertRecordQuery_stmt;
unique_ptr<SSqlStatement> d_InsertEmptyNonTerminalOrderQuery_stmt;
- unique_ptr<SSqlStatement> d_UpdateMasterOfZoneQuery_stmt;
+ unique_ptr<SSqlStatement> d_UpdatePrimaryOfZoneQuery_stmt;
unique_ptr<SSqlStatement> d_UpdateKindOfZoneQuery_stmt;
unique_ptr<SSqlStatement> d_UpdateOptionsOfZoneQuery_stmt;
unique_ptr<SSqlStatement> d_UpdateCatalogOfZoneQuery_stmt;
unique_ptr<SSqlStatement> d_UpdateAccountOfZoneQuery_stmt;
unique_ptr<SSqlStatement> d_UpdateSerialOfZoneQuery_stmt;
unique_ptr<SSqlStatement> d_UpdateLastCheckOfZoneQuery_stmt;
- unique_ptr<SSqlStatement> d_InfoOfAllMasterDomainsQuery_stmt;
+ unique_ptr<SSqlStatement> d_InfoOfAllPrimaryDomainsQuery_stmt;
unique_ptr<SSqlStatement> d_InfoProducerMembersQuery_stmt;
unique_ptr<SSqlStatement> d_InfoConsumerMembersQuery_stmt;
unique_ptr<SSqlStatement> d_DeleteDomainQuery_stmt;
*/
#pragma once
#include <string>
+#include <utility>
#include <vector>
-#include <inttypes.h>
+#include <cinttypes>
#include "../../dnsname.hh"
#include "../../namespaces.hh"
#include "../../misc.hh"
class SSqlException
{
public:
- SSqlException(const string &reason) : d_reason(reason)
+ SSqlException(string reason) :
+ d_reason(std::move(reason))
{
}
{
return d_reason;
}
+
private:
string d_reason;
};
class SSqlStatement
{
public:
- typedef vector<string> row_t;
- typedef vector<row_t> result_t;
+ using row_t = vector<string>;
+ using result_t = vector<row_t>;
- virtual SSqlStatement* bind(const string& name, bool value)=0;
- virtual SSqlStatement* bind(const string& name, int value)=0;
- virtual SSqlStatement* bind(const string& name, uint32_t value)=0;
- virtual SSqlStatement* bind(const string& name, long value)=0;
- virtual SSqlStatement* bind(const string& name, unsigned long value)=0;
- virtual SSqlStatement* bind(const string& name, long long value)=0;;
- virtual SSqlStatement* bind(const string& name, unsigned long long value)=0;
- virtual SSqlStatement* bind(const string& name, const std::string& value)=0;
- SSqlStatement* bind(const string& name, const DNSName& value) {
+ virtual SSqlStatement* bind(const string& name, bool value) = 0;
+ virtual SSqlStatement* bind(const string& name, int value) = 0;
+ virtual SSqlStatement* bind(const string& name, uint32_t value) = 0;
+ virtual SSqlStatement* bind(const string& name, long value) = 0;
+ virtual SSqlStatement* bind(const string& name, unsigned long value) = 0;
+ virtual SSqlStatement* bind(const string& name, long long value) = 0;
+ ;
+ virtual SSqlStatement* bind(const string& name, unsigned long long value) = 0;
+ virtual SSqlStatement* bind(const string& name, const std::string& value) = 0;
+ SSqlStatement* bind(const string& name, const DNSName& value)
+ {
if (!value.empty()) {
return bind(name, value.makeLowerCase().toStringRootDot());
}
return bind(name, string(""));
}
- virtual SSqlStatement* bindNull(const string& name)=0;
- virtual SSqlStatement* execute()=0;;
- virtual bool hasNextRow()=0;
- virtual SSqlStatement* nextRow(row_t& row)=0;
- virtual SSqlStatement* getResult(result_t& result)=0;
- virtual SSqlStatement* reset()=0;
- virtual const std::string& getQuery()=0;
+ virtual SSqlStatement* bindNull(const string& name) = 0;
+ virtual SSqlStatement* execute() = 0;
+ ;
+ virtual bool hasNextRow() = 0;
+ virtual SSqlStatement* nextRow(row_t& row) = 0;
+ virtual SSqlStatement* getResult(result_t& result) = 0;
+ virtual SSqlStatement* reset() = 0;
+ virtual const std::string& getQuery() = 0;
virtual ~SSqlStatement();
};
class SSql
{
public:
- virtual SSqlException sPerrorException(const string &reason)=0;
- virtual std::unique_ptr<SSqlStatement> prepare(const string& query, int nparams)=0;
- virtual void execute(const string& query)=0;
- virtual void startTransaction()=0;
- virtual void rollback()=0;
- virtual void commit()=0;
- virtual void setLog(bool /* state */){}
+ virtual SSqlException sPerrorException(const string& reason) = 0;
+ virtual std::unique_ptr<SSqlStatement> prepare(const string& query, int nparams) = 0;
+ virtual void execute(const string& query) = 0;
+ virtual void startTransaction() = 0;
+ virtual void rollback() = 0;
+ virtual void commit() = 0;
+ virtual void setLog(bool /* state */) {}
virtual bool isConnectionUsable()
{
return true;
}
- virtual void reconnect() {};
- virtual ~SSql(){};
+ virtual void reconnect(){};
+ virtual ~SSql() = default;
};
#pragma once
#include <string>
-template<typename Container> int B64Decode(const std::string& src, Container& dst);
+template<typename Container> int B64Decode(const std::string& strInput, Container& strOutput);
std::string Base64Encode (const std::string& src);
#define YY_NO_INPUT 1
#define YYSTYPE char *
-#include "bindparser.h"
+#include "bindparser.hh"
int linenumber;
#define MAX_INCLUDE_DEPTH 10
%}
-%x comment
+%x comment
%x incl
%x quoted
%option stack
-zone return ZONETOK;
+zone return ZONETOK;
file return FILETOK;
options return OPTIONSTOK;
acl return ACLTOK;
logging return LOGGINGTOK;
directory return DIRECTORYTOK;
-masters return MASTERTOK;
+masters return PRIMARYTOK;
+primaries return PRIMARYTOK;
type return TYPETOK;
\" yy_push_state(quoted);
<quoted>[^\"]* yylval=strdup(yytext); return QUOTEDWORD;
%}
%token AWORD QUOTEDWORD OBRACE EBRACE SEMICOLON ZONETOK FILETOK OPTIONSTOK
-%token DIRECTORYTOK ACLTOK LOGGINGTOK CLASSTOK TYPETOK MASTERTOK ALSONOTIFYTOK
+%token DIRECTORYTOK ACLTOK LOGGINGTOK CLASSTOK TYPETOK PRIMARYTOK ALSONOTIFYTOK
%%
;
/* zone commands that also are available at global scope */
-global_zone_command: zone_file_command | zone_type_command | zone_masters_command
+global_zone_command: zone_file_command | zone_type_command | zone_primaries_command
;
-zone_masters_command: MASTERTOK OBRACE masters EBRACE
+zone_primaries_command: PRIMARYTOK OBRACE primaries EBRACE
;
zone_also_notify_command: ALSONOTIFYTOK OBRACE zone_also_notify_list EBRACE
}
;
-masters: /* empty */
+primaries: /* empty */
|
- masters master SEMICOLON
+ primaries primary SEMICOLON
;
-master: AWORD
+primary: AWORD
{
- s_di.masters.push_back(ComboAddress($1, 53));
+ s_di.primaries.push_back(ComboAddress($1, 53));
free($1);
}
;
{
name=DNSName();
filename=type="";
- masters.clear();
+ primaries.clear();
alsoNotify.clear();
d_dev=0;
d_ino=0;
DNSName name;
string viewName;
string filename;
- vector<ComboAddress> masters;
+ vector<ComboAddress> primaries;
set<string> alsoNotify;
string type;
bool hadFileDirective;
#include "misc.hh"
-static __u64 ptr_to_u64(void *ptr)
+static __u64 ptr_to_u64(const void *ptr)
{
return (__u64) (unsigned long) ptr;
}
/* these can be static as they are not declared in libbpf.h: */
-static int bpf_pin_map(int fd, const std::string& path)
+static int bpf_pin_map(int descriptor, const std::string& path)
{
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
- attr.bpf_fd = fd;
- attr.pathname = ptr_to_u64(const_cast<char*>(path.c_str()));
+ attr.bpf_fd = descriptor;
+ attr.pathname = ptr_to_u64(path.c_str());
return syscall(SYS_bpf, BPF_OBJ_PIN, &attr, sizeof(attr));
}
{
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
- attr.pathname = ptr_to_u64(const_cast<char*>(path.c_str()));
+ attr.pathname = ptr_to_u64(path.c_str());
return syscall(SYS_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
}
-static void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expectedValueSize)
+static void bpf_check_map_sizes(int descriptor, uint32_t expectedKeySize, uint32_t expectedValueSize)
{
struct bpf_map_info info;
uint32_t info_len = sizeof(info);
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
- attr.info.bpf_fd = fd;
+ attr.info.bpf_fd = descriptor;
attr.info.info_len = info_len;
attr.info.info = ptr_to_u64(&info);
}
}
-int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
- int max_entries, int map_flags)
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
+ int max_entries, int map_flags)
{
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}
-int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags)
+static int bpf_update_elem(int descriptor, void *key, void *value, unsigned long long flags)
{
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
- attr.map_fd = fd;
+ attr.map_fd = descriptor;
attr.key = ptr_to_u64(key);
attr.value = ptr_to_u64(value);
attr.flags = flags;
return syscall(SYS_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
}
-int bpf_lookup_elem(int fd, void *key, void *value)
+static int bpf_lookup_elem(int descriptor, void *key, void *value)
{
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
- attr.map_fd = fd;
+ attr.map_fd = descriptor;
attr.key = ptr_to_u64(key);
attr.value = ptr_to_u64(value);
return syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}
-int bpf_delete_elem(int fd, void *key)
+static int bpf_delete_elem(int descriptor, void *key)
{
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
- attr.map_fd = fd;
+ attr.map_fd = descriptor;
attr.key = ptr_to_u64(key);
return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
}
-int bpf_get_next_key(int fd, void *key, void *next_key)
+static int bpf_get_next_key(int descriptor, void *key, void *next_key)
{
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
- attr.map_fd = fd;
+ attr.map_fd = descriptor;
attr.key = ptr_to_u64(key);
attr.next_key = ptr_to_u64(next_key);
return syscall(SYS_bpf, BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
}
-int bpf_prog_load(enum bpf_prog_type prog_type,
- const struct bpf_insn *insns, int prog_len,
- const char *license, int kern_version)
+static int bpf_prog_load(enum bpf_prog_type prog_type,
+ const struct bpf_insn *insns, size_t prog_len,
+ const char *license, int kern_version)
{
char log_buf[65535];
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.prog_type = prog_type;
attr.insns = ptr_to_u64((void *) insns);
- attr.insn_cnt = prog_len / sizeof(struct bpf_insn);
+ attr.insn_cnt = static_cast<int>(prog_len / sizeof(struct bpf_insn));
attr.license = ptr_to_u64((void *) license);
attr.log_buf = ptr_to_u64(log_buf);
attr.log_size = sizeof(log_buf);
static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize)
{
- auto fd = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
- filter,
- filterSize,
- "GPL",
- 0));
- if (fd.getHandle() == -1) {
+ auto descriptor = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
+ filter,
+ filterSize,
+ "GPL",
+ 0));
+ if (descriptor.getHandle() == -1) {
throw std::runtime_error("error loading BPF filter: " + stringerror());
}
- return fd;
+ return descriptor;
}
void BPFFilter::addSocket(int sock)
{
- int fd = d_mainfilter.getHandle();
- int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &fd, sizeof(fd));
+ int descriptor = d_mainfilter.getHandle();
+ int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &descriptor, sizeof(descriptor));
if (res != 0) {
throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror());
void BPFFilter::removeSocket(int sock)
{
- int fd = d_mainfilter.getHandle();
- int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &fd, sizeof(fd));
+ int descriptor = d_mainfilter.getHandle();
+ int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &descriptor, sizeof(descriptor));
if (res != 0) {
throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror());
result.reserve(maps->d_v4.d_count + maps->d_v6.d_count);
}
- sockaddr_in v4Addr;
+ sockaddr_in v4Addr{};
memset(&v4Addr, 0, sizeof(v4Addr));
v4Addr.sin_family = AF_INET;
uint32_t v4Key = 0;
- uint32_t nextV4Key;
- CounterAndActionValue value;
+ uint32_t nextV4Key{};
+ CounterAndActionValue value{};
- uint8_t v6Key[16];
- uint8_t nextV6Key[16];
- sockaddr_in6 v6Addr;
+ std::array<uint8_t, 16> v6Key{};
+ std::array<uint8_t, 16> nextV6Key{};
+ sockaddr_in6 v6Addr{};
memset(&v6Addr, 0, sizeof(v6Addr));
v6Addr.sin6_family = AF_INET6;
- static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == sizeof(v6Key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
+ static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == v6Key.size(), "POSIX mandates s6_addr to be an array of 16 uint8_t");
memset(&v6Key, 0, sizeof(v6Key));
auto maps = d_maps.lock();
{
auto& map = maps->d_v6;
- int res = bpf_get_next_key(map.d_fd.getHandle(), &v6Key, &nextV6Key);
+ int res = bpf_get_next_key(map.d_fd.getHandle(), v6Key.data(), nextV6Key.data());
while (res == 0) {
- if (bpf_lookup_elem(map.d_fd.getHandle(), &nextV6Key, &value) == 0) {
- memcpy(&v6Addr.sin6_addr.s6_addr, &nextV6Key, sizeof(nextV6Key));
+ if (bpf_lookup_elem(map.d_fd.getHandle(), nextV6Key.data(), &value) == 0) {
+ memcpy(&v6Addr.sin6_addr.s6_addr, nextV6Key.data(), nextV6Key.size());
result.emplace_back(ComboAddress(&v6Addr), value.counter);
}
- res = bpf_get_next_key(map.d_fd.getHandle(), &nextV6Key, &nextV6Key);
+ res = bpf_get_next_key(map.d_fd.getHandle(), nextV6Key.data(), nextV6Key.data());
}
}
while (res == 0) {
if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
- result.push_back(std::make_tuple(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter));
+ result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter);
}
res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
while (res == 0) {
if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
- result.push_back(std::make_tuple(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter));
+ result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter);
}
res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
return totErased;
}
-template <typename S, typename C, typename T>
-uint64_t pruneMutexCollectionsVector(C& container, std::vector<T>& maps, uint64_t maxCached, uint64_t cacheSize)
+template <typename S, typename T>
+uint64_t pruneMutexCollectionsVector(time_t now, std::vector<T>& maps, uint64_t maxCached, uint64_t cacheSize)
{
- const time_t now = time(nullptr);
uint64_t totErased = 0;
uint64_t toTrim = 0;
uint64_t lookAt = 0;
uint64_t lookedAt = 0;
for (auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
if (i->isStale(now)) {
- container.preRemoval(*shard, *i);
+ shard->preRemoval(*i);
i = sidx.erase(i);
erased++;
- --content.d_entriesCount;
+ content.decEntriesCount();
}
else {
++i;
auto& sidx = boost::multi_index::get<S>(shard->d_map);
size_t removed = 0;
for (auto i = sidx.begin(); i != sidx.end() && removed < toTrimForThisShard; removed++) {
- container.preRemoval(*shard, *i);
+ shard->preRemoval(*i);
i = sidx.erase(i);
- --content.d_entriesCount;
+ content.decEntriesCount();
++totErased;
if (--toTrim == 0) {
return totErased;
static bool g_quiet;
-static void* recvThread(const vector<std::unique_ptr<Socket>>* sockets)
+//NOLINTNEXTLINE(performance-unnecessary-value-param): we do want a copy to increase the reference count, thank you very much
+static void recvThread(const std::shared_ptr<std::vector<std::unique_ptr<Socket>>> sockets)
{
vector<pollfd> rfds, fds;
- for(const auto& s : *sockets) {
+ for (const auto& s : *sockets) {
+ if (s == nullptr) {
+ continue;
+ }
struct pollfd pfd;
pfd.fd = s->getHandle();
pfd.events = POLLIN;
int err;
-#if HAVE_RECVMMSG
+#ifdef HAVE_RECVMMSG
vector<struct mmsghdr> buf(100);
for(auto& m : buf) {
cmsgbuf_aligned *cbuf = new cmsgbuf_aligned;
for(auto &pfd : fds) {
if (pfd.revents & POLLIN) {
-#if HAVE_RECVMMSG
+#ifdef HAVE_RECVMMSG
if ((err=recvmmsg(pfd.fd, &buf[0], buf.size(), MSG_WAITFORONE, 0)) < 0 ) {
if(errno != EAGAIN)
unixDie("recvmmsg");
}
}
}
- return 0;
}
static ComboAddress getRandomAddressFromRange(const Netmask& ecsRange)
{
ComboAddress result = ecsRange.getMaskedNetwork();
uint8_t bits = ecsRange.getBits();
- uint32_t mod = 1 << (32 - bits);
- result.sin4.sin_addr.s_addr = result.sin4.sin_addr.s_addr + ntohl(dns_random(mod));
+ if (bits > 0) {
+ uint32_t mod = 1 << (32 - bits);
+ result.sin4.sin_addr.s_addr = result.sin4.sin_addr.s_addr + htonl(dns_random(mod));
+ }
+ else {
+ result.sin4.sin_addr.s_addr = dns_random_uint32();
+ }
+
return result;
}
memcpy(&packet->at(packetSize - sizeof(addr)), &addr, sizeof(addr));
}
-static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const vector<vector<uint8_t>* >& packets, int qps, ComboAddress dest, const Netmask& ecsRange)
+static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const vector<vector<uint8_t>* >& packets, uint32_t qps, ComboAddress dest, const Netmask& ecsRange)
{
unsigned int burst=100;
const auto nsecPerBurst=1*(unsigned long)(burst*1000000000.0/qps);
cmsgbuf_aligned cbuf;
};
vector<unique_ptr<Unit> > units;
- int ret;
for(const auto& p : packets) {
count++;
}
fillMSGHdr(&u.msgh, &u.iov, nullptr, 0, (char*)&(*p)[0], p->size(), &dest);
- if((ret=sendmsg(sockets[count % sockets.size()]->getHandle(),
- &u.msgh, 0)))
- if(ret < 0)
- unixDie("sendmsg");
-
-
+
+ auto socketHandle = sockets[count % sockets.size()]->getHandle();
+ ssize_t sendmsgRet = sendmsg(socketHandle, &u.msgh, 0);
+ if (sendmsgRet != 0) {
+ if (sendmsgRet < 0) {
+ unixDie("sendmsg");
+ }
+ }
+
if(!(count%burst)) {
nBursts++;
// Calculate the time in nsec we need to sleep to the next burst.
cerr<<desc<<endl;
}
+namespace {
+void parseQueryFile(const std::string& queryFile, vector<std::shared_ptr<vector<uint8_t>>>& unknown, bool useECSFromFile, bool wantRecursion, bool addECS)
+{
+ ifstream ifs(queryFile);
+ string line;
+ std::vector<std::string> fields;
+ fields.reserve(3);
+
+ while (getline(ifs, line)) {
+ vector<uint8_t> packet;
+ DNSPacketWriter::optvect_t ednsOptions;
+ boost::trim(line);
+ if (line.empty() || line.at(0) == '#') {
+ continue;
+ }
+
+ fields.clear();
+ stringtok(fields, line, "\t ");
+ if ((useECSFromFile && fields.size() < 3) || fields.size() < 2) {
+ cerr<<"Skipping invalid line '"<<line<<", it does not contain enough values"<<endl;
+ continue;
+ }
+
+ const std::string& qname = fields.at(0);
+ const std::string& qtype = fields.at(1);
+ std::string subnet;
+
+ if (useECSFromFile) {
+ subnet = fields.at(2);
+ }
+
+ DNSPacketWriter packetWriter(packet, DNSName(qname), DNSRecordContent::TypeToNumber(qtype));
+ packetWriter.getHeader()->rd = wantRecursion;
+ packetWriter.getHeader()->id = dns_random_uint16();
+
+ if (!subnet.empty() || addECS) {
+ EDNSSubnetOpts opt;
+ opt.source = Netmask(subnet.empty() ? "0.0.0.0/32" : subnet);
+ ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
+ }
+
+ if (!ednsOptions.empty() || (packetWriter.getHeader()->id % 2) != 0) {
+ packetWriter.addOpt(1500, 0, EDNSOpts::DNSSECOK, ednsOptions);
+ packetWriter.commit();
+ }
+ unknown.push_back(std::make_shared<vector<uint8_t>>(packet));
+ }
+
+ shuffle(unknown.begin(), unknown.end(), pdns::dns_random_engine());
+}
+}
+
/*
New plan. Set cache hit percentage, which we achieve on a per second basis.
So we start with 10000 qps for example, and for 90% cache hit ratio means
We then move the 1000 unique queries to the 'known' pool.
For the next second, say 20000 qps, we know we are going to need 2000 new queries,
- so we take 2000 from the unknown pool. Then we need 18000 cache hits. We can get 1000 from
+ so we take 2000 from the unknown pool. Then we need 18000 cache hits. We can get 1000 from
the known pool, leaving us down 17000. Or, we have 3000 in total now and we need 2000. We simply
repeat the 3000 mix we have ~7 times. The 2000 can now go to the known pool too.
- For the next second, say 30000 qps, we'll need 3000 cache misses, which we get from
+ For the next second, say 30000 qps, we'll need 3000 cache misses, which we get from
the unknown pool. To this we add 3000 queries from the known pool. Next up we repeat this batch 5
times.
Netmask ecsRange;
if (g_vm.count("ecs")) {
- dns_random_init("0123456789abcdef");
try {
ecsRange = Netmask(g_vm["ecs"].as<string>());
struct sched_param param;
param.sched_priority=99;
-#if HAVE_SCHED_SETSCHEDULER
+#ifdef HAVE_SCHED_SETSCHEDULER
if(sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) {
if (!g_quiet) {
cerr<<"Unable to set SCHED_FIFO: "<<stringerror()<<endl;
}
#endif
- ifstream ifs(g_vm["query-file"].as<string>());
- string line;
reportAllTypes();
- vector<std::shared_ptr<vector<uint8_t> > > unknown, known;
- std::vector<std::string> fields;
- fields.reserve(3);
-
- while(getline(ifs, line)) {
- vector<uint8_t> packet;
- DNSPacketWriter::optvect_t ednsOptions;
- boost::trim(line);
- if (line.empty() || line.at(0) == '#') {
- continue;
- }
+ vector<std::shared_ptr<vector<uint8_t>>> unknown;
+ vector<std::shared_ptr<vector<uint8_t>>> known;
+ parseQueryFile(g_vm["query-file"].as<string>(), unknown, useECSFromFile, wantRecursion, !ecsRange.empty());
- fields.clear();
- stringtok(fields, line, "\t ");
- if ((useECSFromFile && fields.size() < 3) || fields.size() < 2) {
- cerr<<"Skipping invalid line '"<<line<<", it does not contain enough values"<<endl;
- continue;
- }
-
- const std::string& qname = fields.at(0);
- const std::string& qtype = fields.at(1);
- std::string subnet;
-
- if (useECSFromFile) {
- subnet = fields.at(2);
- }
-
- DNSPacketWriter pw(packet, DNSName(qname), DNSRecordContent::TypeToNumber(qtype));
- pw.getHeader()->rd=wantRecursion;
- pw.getHeader()->id=dns_random_uint16();
-
- if(!subnet.empty() || !ecsRange.empty()) {
- EDNSSubnetOpts opt;
- opt.source = Netmask(subnet.empty() ? "0.0.0.0/32" : subnet);
- ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
- }
-
- if(!ednsOptions.empty() || pw.getHeader()->id % 2) {
- pw.addOpt(1500, 0, EDNSOpts::DNSSECOK, ednsOptions);
- pw.commit();
- }
- unknown.push_back(std::make_shared<vector<uint8_t>>(packet));
- }
-
- shuffle(unknown.begin(), unknown.end(), pdns::dns_random_engine());
if (!g_quiet) {
cout<<"Generated "<<unknown.size()<<" ready to use queries"<<endl;
}
-
- vector<std::unique_ptr<Socket>> sockets;
+
+ auto sockets = std::make_shared<std::vector<std::unique_ptr<Socket>>>();
ComboAddress dest;
try {
dest = ComboAddress(g_vm["destination"].as<string>(), 53);
}
}
- sockets.push_back(std::move(sock));
+ sockets->push_back(std::move(sock));
}
- new thread(recvThread, &sockets);
+
+ {
+ std::thread receiver(recvThread, sockets);
+ receiver.detach();
+ }
+
uint32_t qps;
ofstream plot;
DTime dt;
dt.set();
- sendPackets(sockets, toSend, qps, dest, ecsRange);
-
+ sendPackets(*sockets, toSend, qps, dest, ecsRange);
+
const auto udiff = dt.udiffNoReset();
const auto realqps=toSend.size()/(udiff/1000000.0);
if (!g_quiet) {
cout<<"Achieved "<<realqps<<" qps over "<< udiff/1000000.0<<" seconds"<<endl;
}
-
+
usleep(50000);
const auto received = g_recvcounter.load();
const auto udiffReceived = dt.udiff();
// t1.detach();
}
- catch(std::exception& e)
+catch (const std::exception& exp)
+{
+ cerr<<"Fatal error: "<<exp.what()<<endl;
+ return EXIT_FAILURE;
+}
+catch (const NetmaskException& exp)
{
- cerr<<"Fatal error: "<<e.what()<<endl;
+ cerr<<"Fatal error: "<<exp.reason<<endl;
return EXIT_FAILURE;
}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "channel.hh"
+
+namespace pdns::channel
+{
+
+Notifier::Notifier(FDWrapper&& descriptor) :
+ d_fd(std::move(descriptor))
+{
+}
+
+bool Notifier::notify() const
+{
+ char data = 'a';
+ while (true) {
+ auto sent = write(d_fd.getHandle(), &data, sizeof(data));
+ if (sent == 0) {
+ throw std::runtime_error("Unable to write to channel notifier pipe: remote end has been closed");
+ }
+ if (sent != sizeof(data)) {
+ if (errno == EINTR) {
+ continue;
+ }
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return false;
+ }
+ throw std::runtime_error("Unable to write to channel notifier pipe: " + stringerror());
+ }
+ return true;
+ }
+}
+
+Waiter::Waiter(FDWrapper&& descriptor, bool throwOnEOF) :
+ d_fd(std::move(descriptor)), d_throwOnEOF(throwOnEOF)
+{
+}
+
+void Waiter::clear()
+{
+ ssize_t got{0};
+ do {
+ char data{0};
+ got = read(d_fd.getHandle(), &data, sizeof(data));
+ if (got == 0) {
+ d_closed = true;
+ if (!d_throwOnEOF) {
+ return;
+ }
+ throw std::runtime_error("EOF while clearing channel notifier pipe");
+ }
+ if (got == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ break;
+ }
+ throw std::runtime_error("Error while clearing channel notifier pipe: " + stringerror());
+ }
+ } while (got > 0);
+}
+
+int Waiter::getDescriptor() const
+{
+ return d_fd.getHandle();
+}
+
+std::pair<Notifier, Waiter> createNotificationQueue(bool nonBlocking, size_t pipeBufferSize, bool throwOnEOF)
+{
+ std::array<int, 2> fds = {-1, -1};
+ if (pipe(fds.data()) < 0) {
+ throw std::runtime_error("Error creating notification channel pipe: " + stringerror());
+ }
+
+ FDWrapper sender(fds[1]);
+ FDWrapper receiver(fds[0]);
+
+ if (nonBlocking && !setNonBlocking(receiver.getHandle())) {
+ int err = errno;
+ throw std::runtime_error("Error making notification channel pipe non-blocking: " + stringerror(err));
+ }
+
+ if (nonBlocking && !setNonBlocking(sender.getHandle())) {
+ int err = errno;
+ throw std::runtime_error("Error making notification channel pipe non-blocking: " + stringerror(err));
+ }
+
+ if (pipeBufferSize > 0 && getPipeBufferSize(receiver.getHandle()) < pipeBufferSize) {
+ setPipeBufferSize(receiver.getHandle(), pipeBufferSize);
+ }
+
+ return {Notifier(std::move(sender)), Waiter(std::move(receiver), throwOnEOF)};
+}
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#include <memory>
+#include <optional>
+
+#include "misc.hh"
+
+/* g++ defines __SANITIZE_THREAD__
+ clang++ supports the nice __has_feature(thread_sanitizer),
+ let's merge them */
+#if defined(__has_feature)
+#if __has_feature(thread_sanitizer)
+#define __SANITIZE_THREAD__ 1
+#endif
+#endif
+
+#if __SANITIZE_THREAD__
+#if defined __has_include
+#if __has_include(<sanitizer/tsan_interface.h>)
+#include <sanitizer/tsan_interface.h>
+#else /* __has_include(<sanitizer/tsan_interface.h>) */
+extern "C" void __tsan_acquire(void* addr);
+extern "C" void __tsan_release(void* addr);
+#endif /* __has_include(<sanitizer/tsan_interface.h>) */
+#else /* defined __has_include */
+extern "C" void __tsan_acquire(void* addr);
+extern "C" void __tsan_release(void* addr);
+#endif /* defined __has_include */
+#endif /* __SANITIZE_THREAD__ */
+
+namespace pdns
+{
+namespace channel
+{
+ enum class SenderBlockingMode
+ {
+ SenderNonBlocking,
+ SenderBlocking
+ };
+ enum class ReceiverBlockingMode
+ {
+ ReceiverNonBlocking,
+ ReceiverBlocking
+ };
+
+ /**
+ * The sender's end of a channel used to pass objects between threads.
+ *
+ * A sender can be used by several threads in a safe way.
+ */
+ template <typename T, typename D = std::default_delete<T>>
+ class Sender
+ {
+ public:
+ Sender() = default;
+ Sender(FDWrapper&& descriptor) :
+ d_fd(std::move(descriptor))
+ {
+ }
+ Sender(const Sender&) = delete;
+ Sender& operator=(const Sender&) = delete;
+ Sender(Sender&&) = default;
+ Sender& operator=(Sender&&) = default;
+ ~Sender() = default;
+ /**
+ * \brief Try to send the supplied object to the other end of that channel. Might block if the channel was created in blocking mode.
+ *
+ * \return True if the object was properly sent, False if the channel is full.
+ *
+ * \throw runtime_error if the channel is broken, for example if the other end has been closed.
+ */
+ bool send(std::unique_ptr<T, D>&&) const;
+ void close();
+
+ private:
+ FDWrapper d_fd;
+ };
+
+ /**
+ * The receiver's end of a channel used to pass objects between threads.
+ *
+ * A receiver can be used by several threads in a safe way, but in that case spurious wake up might happen.
+ */
+ template <typename T, typename D = std::default_delete<T>>
+ class Receiver
+ {
+ public:
+ Receiver() = default;
+ Receiver(FDWrapper&& descriptor, bool throwOnEOF = true) :
+ d_fd(std::move(descriptor)), d_throwOnEOF(throwOnEOF)
+ {
+ }
+ Receiver(const Receiver&) = delete;
+ Receiver& operator=(const Receiver&) = delete;
+ Receiver(Receiver&&) = default;
+ Receiver& operator=(Receiver&&) = default;
+ ~Receiver() = default;
+ /**
+ * \brief Try to read an object sent by the other end of that channel. Might block if the channel was created in blocking mode.
+ *
+ * \return An object if one was available, and std::nullopt otherwise.
+ *
+ * \throw runtime_error if the channel is broken, for example if the other end has been closed.
+ */
+ std::optional<std::unique_ptr<T, D>> receive();
+ std::optional<std::unique_ptr<T, D>> receive(D deleter);
+
+ /**
+ * \brief Get a descriptor that can be used with an I/O multiplexer to wait for an object to become available.
+ *
+ * \return A valid descriptor or -1 if the Receiver was not properly initialized.
+ */
+ int getDescriptor() const
+ {
+ return d_fd.getHandle();
+ }
+ /**
+ * \brief Whether the remote end has closed the channel.
+ */
+ bool isClosed() const
+ {
+ return d_closed;
+ }
+
+ private:
+ FDWrapper d_fd;
+ bool d_closed{false};
+ bool d_throwOnEOF{true};
+ };
+
+ /**
+ * \brief Create a channel to pass objects between threads, accepting multiple senders and receivers.
+ *
+ * \return A pair of Sender and Receiver objects.
+ *
+ * \throw runtime_error if the channel creation failed.
+ */
+ template <typename T, typename D = std::default_delete<T>>
+ std::pair<Sender<T, D>, Receiver<T, D>> createObjectQueue(SenderBlockingMode senderBlockingMode = SenderBlockingMode::SenderNonBlocking, ReceiverBlockingMode receiverBlockingMode = ReceiverBlockingMode::ReceiverNonBlocking, size_t pipeBufferSize = 0, bool throwOnEOF = true);
+
+ /**
+ * The notifier's end of a channel used to communicate between threads.
+ *
+ * A notifier can be used by several threads in a safe way.
+ */
+ class Notifier
+ {
+ public:
+ Notifier() = default;
+ Notifier(FDWrapper&&);
+ Notifier(const Notifier&) = delete;
+ Notifier& operator=(const Notifier&) = delete;
+ Notifier(Notifier&&) = default;
+ Notifier& operator=(Notifier&&) = default;
+ ~Notifier() = default;
+
+ /**
+ * \brief Queue a notification to wake up the other end of the channel.
+ *
+ * \return True if the notification was properly sent, False if the channel is full.
+ *
+ * \throw runtime_error if the channel is broken, for example if the other end has been closed.
+ */
+ bool notify() const;
+
+ private:
+ FDWrapper d_fd;
+ };
+
+ /**
+ * The waiter's end of a channel used to communicate between threads.
+ *
+ * A waiter can be used by several threads in a safe way, but in that case spurious wake up might happen.
+ */
+ class Waiter
+ {
+ public:
+ Waiter() = default;
+ Waiter(FDWrapper&&, bool throwOnEOF = true);
+ Waiter(const Waiter&) = delete;
+ Waiter& operator=(const Waiter&) = delete;
+ Waiter(Waiter&&) = default;
+ Waiter& operator=(Waiter&&) = default;
+ ~Waiter() = default;
+
+ /**
+ * \brief Clear all notifications queued on that channel, if any.
+ */
+ void clear();
+ /**
+ * \brief Get a descriptor that can be used with an I/O multiplexer to wait for a notification to arrive.
+ *
+ * \return A valid descriptor or -1 if the Waiter was not properly initialized.
+ */
+ int getDescriptor() const;
+ /**
+ * \brief Whether the remote end has closed the channel.
+ */
+ bool isClosed() const
+ {
+ return d_closed;
+ }
+
+ private:
+ FDWrapper d_fd;
+ bool d_closed{false};
+ bool d_throwOnEOF{true};
+ };
+
+ /**
+ * \brief Create a channel to notify one thread from another one, accepting multiple senders and receivers.
+ *
+ * \return A pair of Notifier and Sender objects.
+ *
+ * \throw runtime_error if the channel creation failed.
+ */
+ std::pair<Notifier, Waiter> createNotificationQueue(bool nonBlocking = true, size_t pipeBufferSize = 0, bool throwOnEOF = true);
+
+ template <typename T, typename D>
+ bool Sender<T, D>::send(std::unique_ptr<T, D>&& object) const
+ {
+ /* we cannot touch the initial unique pointer after writing to the pipe,
+ not even to release it, so let's transfer it to a local object */
+ auto localObj = std::move(object);
+ auto ptr = localObj.get();
+ static_assert(sizeof(ptr) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranted not to interleaved and to either fully succeed or fail");
+ while (true) {
+#if __SANITIZE_THREAD__
+ __tsan_release(ptr);
+#endif /* __SANITIZE_THREAD__ */
+ ssize_t sent = write(d_fd.getHandle(), &ptr, sizeof(ptr));
+
+ if (sent == sizeof(ptr)) {
+ // coverity[leaked_storage]
+ localObj.release();
+ return true;
+ }
+ else if (sent == 0) {
+#if __SANITIZE_THREAD__
+ __tsan_acquire(ptr);
+#endif /* __SANITIZE_THREAD__ */
+ throw std::runtime_error("Unable to write to channel: remote end has been closed");
+ }
+ else {
+#if __SANITIZE_THREAD__
+ __tsan_acquire(ptr);
+#endif /* __SANITIZE_THREAD__ */
+ if (errno == EINTR) {
+ continue;
+ }
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ object = std::move(localObj);
+ return false;
+ }
+ else {
+ throw std::runtime_error("Unable to write to channel:" + stringerror());
+ }
+ }
+ }
+ }
+
+ template <typename T, typename D>
+ void Sender<T, D>::close()
+ {
+ d_fd.reset();
+ }
+
+ template <typename T, typename D>
+ std::optional<std::unique_ptr<T, D>> Receiver<T, D>::receive()
+ {
+ return receive(D());
+ }
+
+ template <typename T, typename D>
+ std::optional<std::unique_ptr<T, D>> Receiver<T, D>::receive(D deleter)
+ {
+ while (true) {
+ std::optional<std::unique_ptr<T, D>> result;
+ T* objPtr{nullptr};
+ ssize_t got = read(d_fd.getHandle(), &objPtr, sizeof(objPtr));
+ if (got == sizeof(objPtr)) {
+#if __SANITIZE_THREAD__
+ __tsan_acquire(objPtr);
+#endif /* __SANITIZE_THREAD__ */
+ return std::unique_ptr<T, D>(objPtr, deleter);
+ }
+ else if (got == 0) {
+ d_closed = true;
+ if (!d_throwOnEOF) {
+ return result;
+ }
+ throw std::runtime_error("EOF while reading from Channel receiver");
+ }
+ else if (got == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return result;
+ }
+ throw std::runtime_error("Error while reading from Channel receiver: " + stringerror());
+ }
+ else {
+ throw std::runtime_error("Partial read from Channel receiver");
+ }
+ }
+ }
+
+ template <typename T, typename D>
+ std::pair<Sender<T, D>, Receiver<T, D>> createObjectQueue(SenderBlockingMode senderBlockingMode, ReceiverBlockingMode receiverBlockingMode, size_t pipeBufferSize, bool throwOnEOF)
+ {
+ int fds[2] = {-1, -1};
+ if (pipe(fds) < 0) {
+ throw std::runtime_error("Error creating channel pipe: " + stringerror());
+ }
+
+ FDWrapper sender(fds[1]);
+ FDWrapper receiver(fds[0]);
+ if (receiverBlockingMode == ReceiverBlockingMode::ReceiverNonBlocking && !setNonBlocking(receiver.getHandle())) {
+ int err = errno;
+ throw std::runtime_error("Error making channel pipe non-blocking: " + stringerror(err));
+ }
+
+ if (senderBlockingMode == SenderBlockingMode::SenderNonBlocking && !setNonBlocking(sender.getHandle())) {
+ int err = errno;
+ throw std::runtime_error("Error making channel pipe non-blocking: " + stringerror(err));
+ }
+
+ if (pipeBufferSize > 0 && getPipeBufferSize(receiver.getHandle()) < pipeBufferSize) {
+ setPipeBufferSize(receiver.getHandle(), pipeBufferSize);
+ }
+
+ return {Sender<T, D>(std::move(sender)), Receiver<T, D>(std::move(receiver), throwOnEOF)};
+ }
+}
+}
{
public:
Comment() : modified_at(0), domain_id(0) {};
- ~Comment() {};
+ ~Comment() = default;
// data
DNSName qname; //!< the name of the associated RRset, for example: www.powerdns.com
void CommunicatorClass::retrievalLoopThread()
{
setThreadName("pdns/comm-retre");
- for(;;) {
+ for (;;) {
d_suck_sem.wait();
SuckRequest sr;
{
}
auto firstItem = data->d_suckdomains.begin();
-
- sr=*firstItem;
+
+ sr = *firstItem;
data->d_suckdomains.erase(firstItem);
if (data->d_suckdomains.empty()) {
data->d_sorthelper = 0;
}
}
- suck(sr.domain, sr.master, sr.force);
+ suck(sr.domain, sr.primary, sr.force);
}
}
-void CommunicatorClass::loadArgsIntoSet(const char *listname, set<string> &listset)
+void CommunicatorClass::loadArgsIntoSet(const char* listname, set<string>& listset)
{
vector<string> parts;
stringtok(parts, ::arg()[listname], ", \t");
- for (const auto & part : parts) {
+ for (const auto& part : parts) {
try {
ComboAddress caIp(part, 53);
listset.insert(caIp.toStringWithPort());
}
- catch(PDNSException &e) {
- g_log<<Logger::Error<<"Unparseable IP in "<<listname<<". Error: "<<e.reason<<endl;
+ catch (PDNSException& e) {
+ g_log << Logger::Error << "Unparseable IP in " << listname << ". Error: " << e.reason << endl;
_exit(1);
}
}
void CommunicatorClass::go()
{
try {
- PacketHandler::s_allowNotifyFrom.toMasks(::arg()["allow-notify-from"] );
+ PacketHandler::s_allowNotifyFrom.toMasks(::arg()["allow-notify-from"]);
}
- catch(PDNSException &e) {
- g_log<<Logger::Error<<"Unparseable IP in allow-notify-from. Error: "<<e.reason<<endl;
+ catch (PDNSException& e) {
+ g_log << Logger::Error << "Unparseable IP in allow-notify-from. Error: " << e.reason << endl;
_exit(1);
}
- std::thread mainT([this](){mainloop();});
+ std::thread mainT([this]() { mainloop(); });
mainT.detach();
- for(int n=0; n < ::arg().asNum("retrieval-threads", 1); ++n) {
- std::thread retrieve([this](){retrievalLoopThread();});
+ for (int nthreads = 0; nthreads < ::arg().asNum("retrieval-threads", 1); ++nthreads) {
+ std::thread retrieve([this]() { retrievalLoopThread(); });
retrieve.detach();
}
d_preventSelfNotification = ::arg().mustDo("prevent-self-notification");
+ auto delay = ::arg().asNum("delay-notifications");
+ if (delay > 0) {
+ d_delayNotifications = static_cast<time_t>(delay);
+ }
+
try {
d_onlyNotify.toMasks(::arg()["only-notify"]);
}
- catch(PDNSException &e) {
- g_log<<Logger::Error<<"Unparseable IP in only-notify. Error: "<<e.reason<<endl;
+ catch (PDNSException& e) {
+ g_log << Logger::Error << "Unparseable IP in only-notify. Error: " << e.reason << endl;
_exit(1);
}
{
try {
setThreadName("pdns/comm-main");
- signal(SIGPIPE,SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
g_log << Logger::Warning << "Primary/secondary communicator launching" << endl;
d_tickinterval = ::arg().asNum("xfr-cycle-interval");
makeNotifySockets();
- for(;;) {
- slaveRefresh(&P);
- masterUpdateCheck(&P);
+ for (;;) {
+ secondaryRefresh(&P);
+ primaryUpdateCheck(&P);
doNotifications(&P); // this processes any notification acknowledgements and actually send out our own notifications
next = time(nullptr) + d_tickinterval;
- while(time(nullptr) < next) {
- rc=d_any_sem.tryWait();
+ while (time(nullptr) < next) {
+ rc = d_any_sem.tryWait();
- if(rc) {
- bool extraSlaveRefresh = false;
+ if (rc != 0) {
+ bool extraSecondaryRefresh = false;
Utility::sleep(1);
{
auto data = d_data.lock();
if (data->d_tocheck.size()) {
- extraSlaveRefresh = true;
+ extraSecondaryRefresh = true;
}
}
- if (extraSlaveRefresh)
- slaveRefresh(&P);
+ if (extraSecondaryRefresh)
+ secondaryRefresh(&P);
}
else {
// eat up extra posts to avoid busy looping if many posts were done
}
}
}
- catch(PDNSException &ae) {
- g_log<<Logger::Error<<"Exiting because communicator thread died with error: "<<ae.reason<<endl;
+ catch (PDNSException& ae) {
+ g_log << Logger::Error << "Exiting because communicator thread died with error: " << ae.reason << endl;
Utility::sleep(1);
_exit(1);
}
- catch(std::exception &e) {
- g_log<<Logger::Error<<"Exiting because communicator thread died with STL error: "<<e.what()<<endl;
+ catch (std::exception& e) {
+ g_log << Logger::Error << "Exiting because communicator thread died with STL error: " << e.what() << endl;
_exit(1);
}
- catch( ... )
- {
+ catch (...) {
g_log << Logger::Error << "Exiting because communicator caught unknown exception." << endl;
_exit(1);
}
struct SuckRequest
{
DNSName domain;
- ComboAddress master;
+ ComboAddress primary;
bool force;
- enum RequestPriority : uint8_t { PdnsControl, Api, Notify, SerialRefresh, SignaturesRefresh };
+ enum RequestPriority : uint8_t
+ {
+ PdnsControl,
+ Api,
+ Notify,
+ SerialRefresh,
+ SignaturesRefresh
+ };
std::pair<RequestPriority, uint64_t> priorityAndOrder;
bool operator<(const SuckRequest& b) const
{
- return std::tie(domain, master) < std::tie(b.domain, b.master);
+ return std::tie(domain, primary) < std::tie(b.domain, b.primary);
}
};
-struct IDTag{};
+struct IDTag
+{
+};
-typedef multi_index_container<
+using UniQueue = multi_index_container<
SuckRequest,
indexed_by<
- ordered_unique<member<SuckRequest,std::pair<SuckRequest::RequestPriority,uint64_t>,&SuckRequest::priorityAndOrder>>,
- ordered_unique<tag<IDTag>, identity<SuckRequest> >
- >
-> UniQueue;
-typedef UniQueue::index<IDTag>::type domains_by_name_t;
+ ordered_unique<member<SuckRequest, std::pair<SuckRequest::RequestPriority, uint64_t>, &SuckRequest::priorityAndOrder>>,
+ ordered_unique<tag<IDTag>, identity<SuckRequest>>>>;
+using domains_by_name_t = UniQueue::index<IDTag>::type;
class NotificationQueue
{
public:
- void add(const DNSName &domain, const string &ip)
+ void add(const DNSName& domain, const string& ipstring, time_t delay = 0)
{
- const ComboAddress caIp(ip);
+ const ComboAddress ipaddress(ipstring);
+ add(domain, ipaddress, delay);
+ }
+ void add(const DNSName& domain, const ComboAddress& ipaddress, time_t delay = 0)
+ {
NotificationRequest nr;
- nr.domain = domain;
- nr.ip = caIp.toStringWithPort();
+ nr.domain = domain;
+ nr.ip = ipaddress.toStringWithPort();
nr.attempts = 0;
- nr.id = dns_random_uint16();
- nr.next = time(0);
+ nr.id = dns_random_uint16();
+ nr.next = time(nullptr) + delay;
d_nqueue.push_back(nr);
}
return false;
}
- bool getOne(DNSName &domain, string &ip, uint16_t *id, bool &purged)
+ bool getOne(DNSName& domain, string& ip, uint16_t* id, bool& purged)
{
- for(d_nqueue_t::iterator i=d_nqueue.begin();i!=d_nqueue.end();++i)
- if(i->next <= time(0)) {
+ for (d_nqueue_t::iterator i = d_nqueue.begin(); i != d_nqueue.end(); ++i)
+ if (i->next <= time(nullptr)) {
i->attempts++;
- purged=false;
- i->next=time(0)+1+(1<<i->attempts);
- domain=i->domain;
- ip=i->ip;
- *id=i->id;
- purged=false;
- if(i->attempts>4) {
- purged=true;
+ purged = false;
+ i->next = time(nullptr) + 1 + (1 << i->attempts);
+ domain = i->domain;
+ ip = i->ip;
+ *id = i->id;
+ purged = false;
+ if (i->attempts > 4) {
+ purged = true;
d_nqueue.erase(i);
}
return true;
time_t earliest()
{
- time_t early=std::numeric_limits<time_t>::max() - 1;
- for(d_nqueue_t::const_iterator i=d_nqueue.begin();i!=d_nqueue.end();++i)
- early=min(early,i->next);
- return early-time(0);
+ time_t early = std::numeric_limits<time_t>::max() - 1;
+ for (d_nqueue_t::const_iterator i = d_nqueue.begin(); i != d_nqueue.end(); ++i)
+ early = min(early, i->next);
+ return early - time(nullptr);
}
void dump();
uint16_t id;
};
- typedef std::list<NotificationRequest> d_nqueue_t;
+ using d_nqueue_t = std::list<NotificationRequest>;
d_nqueue_t d_nqueue;
-
};
struct ZoneStatus;
public:
CommunicatorClass()
{
- d_tickinterval=60;
- d_slaveschanged = true;
+ d_tickinterval = 60;
+ d_secondarieschanged = true;
d_nsock4 = -1;
d_nsock6 = -1;
d_preventSelfNotification = false;
}
- time_t doNotifications(PacketHandler *P);
+ time_t doNotifications(PacketHandler* P);
void go();
-
- void drillHole(const DNSName &domain, const string &ip);
- bool justNotified(const DNSName &domain, const string &ip);
- void addSuckRequest(const DNSName &domain, const ComboAddress& master, SuckRequest::RequestPriority, bool force=false);
- void addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote);
- void addTrySuperMasterRequest(const DNSPacket& p);
- void notify(const DNSName &domain, const string &ip);
+ void drillHole(const DNSName& domain, const string& ip);
+ bool justNotified(const DNSName& domain, const string& ip);
+ void addSuckRequest(const DNSName& domain, const ComboAddress& primary, SuckRequest::RequestPriority, bool force = false);
+ void addSecondaryCheckRequest(const DomainInfo& di, const ComboAddress& remote);
+ void addTryAutoPrimaryRequest(const DNSPacket& p);
+ void notify(const DNSName& domain, const string& ip);
void mainloop();
void retrievalLoopThread();
- void sendNotification(int sock, const DNSName &domain, const ComboAddress& remote, uint16_t id, UeberBackend* B);
- bool notifyDomain(const DNSName &domain, UeberBackend* B);
- vector<pair<DNSName, ComboAddress> > getSuckRequests();
+ void sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackend* B);
+ bool notifyDomain(const DNSName& domain, UeberBackend* B);
+ vector<pair<DNSName, ComboAddress>> getSuckRequests();
size_t getSuckRequestsWaiting();
+
private:
- void loadArgsIntoSet(const char *listname, set<string> &listset);
+ static void loadArgsIntoSet(const char* listname, set<string>& listset);
void makeNotifySockets();
void queueNotifyDomain(const DomainInfo& di, UeberBackend* B);
int d_nsock4, d_nsock6;
- LockGuarded<map<pair<DNSName,string>,time_t>> d_holes;
+ LockGuarded<map<pair<DNSName, string>, time_t>> d_holes;
- void suck(const DNSName &domain, const ComboAddress& remote, bool force=false);
+ void suck(const DNSName& domain, const ComboAddress& remote, bool force = false);
void ixfrSuck(const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, ZoneStatus& zs, vector<DNSRecord>* axfr);
- void slaveRefresh(PacketHandler *P);
- void masterUpdateCheck(PacketHandler *P);
+ void secondaryRefresh(PacketHandler* P);
+ void primaryUpdateCheck(PacketHandler* P);
void getUpdatedProducers(UeberBackend* B, vector<DomainInfo>& domains, const std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes);
Semaphore d_suck_sem;
NotificationQueue d_nq;
time_t d_tickinterval;
- bool d_slaveschanged;
+ bool d_secondarieschanged;
bool d_preventSelfNotification;
+ time_t d_delayNotifications{0};
struct Data
{
set<DNSName> d_inprogress;
set<DomainInfo> d_tocheck;
- struct cmp {
- bool operator()(const DNSPacket& a, const DNSPacket& b) const {
+ struct cmp
+ {
+ bool operator()(const DNSPacket& a, const DNSPacket& b) const
+ {
return a.qdomain < b.qdomain;
};
};
- std::set<DNSPacket, cmp> d_potentialsupermasters;
+ std::set<DNSPacket, cmp> d_potentialautoprimaries;
// Used to keep some state on domains that failed their freshness checks.
// uint64_t == counter of the number of failures (increased by 1 every consecutive slave-cycle-interval that the domain fails)
// time_t == wait at least until this time before attempting a new check
- map<DNSName, pair<uint64_t, time_t> > d_failedSlaveRefresh;
+ map<DNSName, pair<uint64_t, time_t>> d_failedSecondaryRefresh;
};
LockGuarded<Data> d_data;
struct RemoveSentinel
{
- explicit RemoveSentinel(const DNSName& dn, CommunicatorClass* cc) : d_dn(dn), d_cc(cc)
+ explicit RemoveSentinel(const DNSName& dn, CommunicatorClass* cc) :
+ d_dn(dn), d_cc(cc)
{}
~RemoveSentinel()
try {
d_cc->d_data.lock()->d_inprogress.erase(d_dn);
}
- catch(...) {
+ catch (...) {
}
}
DNSName d_dn;
CommunicatorClass* d_cc;
-};
-
+ };
};
// class that one day might be more than a function to help you get IP addresses for a nameserver
class FindNS
{
public:
- vector<string> lookup(const DNSName &name, UeberBackend *b)
+ vector<string> lookup(const DNSName& name, UeberBackend* b)
{
vector<string> addresses;
this->resolve_name(&addresses, name);
- if(b) {
- b->lookup(QType(QType::ANY),name,-1);
- DNSZoneRecord rr;
- while(b->get(rr))
- if(rr.dr.d_type == QType::A || rr.dr.d_type==QType::AAAA)
- addresses.push_back(rr.dr.getContent()->getZoneRepresentation()); // SOL if you have a CNAME for an NS
+ if (b) {
+ b->lookup(QType(QType::ANY), name, -1);
+ DNSZoneRecord rr;
+ while (b->get(rr))
+ if (rr.dr.d_type == QType::A || rr.dr.d_type == QType::AAAA)
+ addresses.push_back(rr.dr.getContent()->getZoneRepresentation()); // SOL if you have a CNAME for an NS
}
return addresses;
}
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_DGRAM; // otherwise we get everything in triplicate (!)
- for(int n = 0; n < 2; ++n) {
+ for (int n = 0; n < 2; ++n) {
hints.ai_family = n ? AF_INET : AF_INET6;
ComboAddress remote;
remote.sin4.sin_family = AF_INET6;
- if(!getaddrinfo(name.toString().c_str(), 0, &hints, &res)) {
+ if (!getaddrinfo(name.toString().c_str(), 0, &hints, &res)) {
struct addrinfo* address = res;
do {
if (address->ai_addrlen <= sizeof(remote)) {
remote.setSockaddr(address->ai_addr, address->ai_addrlen);
addresses->push_back(remote.toString());
}
- } while((address = address->ai_next));
+ } while ((address = address->ai_next));
freeaddrinfo(res);
}
}
-import sys, json, yaml
+"""Convert a YAML file to JSON."""
-with open(sys.argv[1], mode='r', encoding='utf-8') as f_in:
- with open(sys.argv[2], mode='w', encoding='utf-8') as f_out:
- json.dump(yaml.safe_load(f_in.read()), f_out, indent=2, separators=(',', ': '))
+import json
+import sys
+
+import yaml
+
+yaml_filename = sys.argv[1]
+json_filename = sys.argv[2]
+
+with open(yaml_filename, mode="r", encoding="utf-8") as f_in:
+ with open(json_filename, mode="w", encoding="utf-8") as f_out:
+ contents = yaml.safe_load(f_in.read())
+ json.dump(contents, f_out, indent=2, separators=(",", ": "))
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "config.h"
+#include "coverage.hh"
+
+#ifdef COVERAGE
+extern "C"
+{
+#ifdef CLANG_COVERAGE
+ // NOLINTNEXTLINE(bugprone-reserved-identifier): not ours
+ int __llvm_profile_write_file(void);
+#else /* CLANG_COVERAGE */
+ // NOLINTNEXTLINE(bugprone-reserved-identifier): not ours
+ void __gcov_dump(void);
+#endif /* CLANG_COVERAGE */
+}
+#endif /* COVERAGE */
+
+namespace pdns::coverage
+{
+void dumpCoverageData()
+{
+#ifdef COVERAGE
+#ifdef CLANG_COVERAGE
+ __llvm_profile_write_file();
+#else /* CLANG_COVERAGE */
+ __gcov_dump();
+#endif /* CLANG_COVERAGE */
+#endif /* COVERAGE */
+}
+}
*/
#pragma once
-#include "dnsdist.hh"
-
-bool addXPF(DNSQuestion& dq, uint16_t optionCode);
-
+namespace pdns::coverage
+{
+void dumpCoverageData();
+}
#include <unistd.h>
#include "base64.hh"
+#include "dns_random.hh"
#include "credentials.hh"
#include "misc.hh"
#endif
}
-SensitiveData& SensitiveData::operator=(SensitiveData&& rhs)
+SensitiveData& SensitiveData::operator=(SensitiveData&& rhs) noexcept
{
d_data = std::move(rhs.d_data);
rhs.clear();
d_data.clear();
}
-static std::string hashPasswordInternal(const std::string& password, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize)
+static std::string hashPasswordInternal([[maybe_unused]] const std::string& password, [[maybe_unused]] const std::string& salt, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize)
{
#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
auto pctx = std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)>(EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, nullptr), EVP_PKEY_CTX_free);
#endif
}
-std::string hashPassword(const std::string& password, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize)
+std::string hashPassword([[maybe_unused]] const std::string& password, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize)
{
#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
if (workFactor == 0) {
#endif
}
-std::string hashPassword(const std::string& password)
+std::string hashPassword([[maybe_unused]] const std::string& password)
{
#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
return hashPassword(password, CredentialsHolder::s_defaultWorkFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
#endif
}
-bool verifyPassword(const std::string& binaryHash, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize, const std::string& binaryPassword)
+bool verifyPassword([[maybe_unused]] const std::string& binaryHash, [[maybe_unused]] const std::string& salt, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize, [[maybe_unused]] const std::string& binaryPassword)
{
#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
auto expected = hashPasswordInternal(binaryPassword, salt, workFactor, parallelFactor, blockSize);
}
/* parse a hashed password in PHC string format */
-static void parseHashed(const std::string& hash, std::string& salt, std::string& hashedPassword, uint64_t& workFactor, uint64_t& parallelFactor, uint64_t& blockSize)
+static void parseHashed([[maybe_unused]] const std::string& hash, [[maybe_unused]] std::string& salt, [[maybe_unused]] std::string& hashedPassword, [[maybe_unused]] uint64_t& workFactor, [[maybe_unused]] uint64_t& parallelFactor, [[maybe_unused]] uint64_t& blockSize)
{
#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
auto parametersEnd = hash.find('$', pwhash_prefix.size());
#endif
}
-bool verifyPassword(const std::string& hash, const std::string& password)
+bool verifyPassword(const std::string& hash, [[maybe_unused]] const std::string& password)
{
if (!isPasswordHashed(hash)) {
return false;
#endif
}
-bool isPasswordHashed(const std::string& password)
+bool isPasswordHashed([[maybe_unused]] const std::string& password)
{
#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
if (password.size() < pwhash_prefix_size || password.size() > pwhash_max_size) {
}
if (!d_isHashed) {
- d_fallbackHashPerturb = random();
+ d_fallbackHashPerturb = dns_random_uint32();
d_fallbackHash = burtle(reinterpret_cast<const unsigned char*>(d_credentials.getString().data()), d_credentials.getString().size(), d_fallbackHashPerturb);
}
}
public:
SensitiveData(size_t bytes);
SensitiveData(std::string&& data);
- SensitiveData& operator=(SensitiveData&&);
+ SensitiveData& operator=(SensitiveData&&) noexcept;
~SensitiveData();
void clear();
DNSSECKeeper::keyset_t DNSSECKeeper::getKeys(const DNSName& zone, bool useCache)
{
static int ttl = ::arg().asNum("dnssec-key-cache-ttl");
+ // coverity[store_truncates_time_t]
unsigned int now = time(nullptr);
if(!((++s_ops) % 100000)) {
return false;
}
-bool DNSSECKeeper::getTSIGForAccess(const DNSName& zone, const ComboAddress& /* master */, DNSName* keyname)
+bool DNSSECKeeper::getTSIGForAccess(const DNSName& zone, const ComboAddress& /* primary */, DNSName* keyname)
{
vector<string> keynames;
d_keymetadb->getDomainMetadata(zone, "AXFR-MASTER-TSIG", keynames);
keyname->trimToLabels(0);
- // XXX FIXME this should check for a specific master!
+ // XXX FIXME this should check for a specific primary!
for(const string& dbkey : keynames) {
*keyname=DNSName(dbkey);
return true;
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-copy"
#include <decaf.hxx>
+#pragma GCC diagnostic pop
#include <decaf/eddsa.hxx>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
#include <decaf/spongerng.hxx>
+#pragma GCC diagnostic pop
#include "dnsseckeeper.hh"
#include "dnssecinfra.hh"
template<class T>
ObjectPipe<T>::ObjectPipe()
{
- if(pipe(d_fds))
- unixDie("pipe");
-}
-
-template<class T>
-ObjectPipe<T>::~ObjectPipe()
-{
- ::close(d_fds[0]);
- if(d_fds[1] >= 0)
- ::close(d_fds[1]);
+ auto [sender, receiver] = pdns::channel::createObjectQueue<T>(pdns::channel::SenderBlockingMode::SenderBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, 0, false);
+ d_sender = std::move(sender);
+ d_receiver = std::move(receiver);
}
template<class T>
void ObjectPipe<T>::close()
{
- if(d_fds[1] < 0)
- return;
- ::close(d_fds[1]); // the writing side
- d_fds[1]=-1;
+ d_sender.close();
}
template<class T>
void ObjectPipe<T>::write(T& t)
{
- auto ptr = new T(t);
- if(::write(d_fds[1], &ptr, sizeof(ptr)) != sizeof(ptr)) {
- delete ptr;
- unixDie("write");
+ auto ptr = std::make_unique<T>(t);
+ if (!d_sender.send(std::move(ptr))) {
+ unixDie("writing to the DelayPipe");
}
}
int ObjectPipe<T>::readTimeout(T* t, double msec)
{
while (true) {
- int ret = waitForData(d_fds[0], 0, 1000*msec);
+ int ret = waitForData(d_receiver.getDescriptor(), 0, 1000*msec);
if (ret < 0) {
if (errno == EINTR) {
continue;
return -1;
}
- T* ptr = nullptr;
- ret = ::read(d_fds[0], &ptr, sizeof(ptr)); // this is BLOCKING!
-
- if (ret < 0) {
- if (errno == EINTR) {
+ try {
+ auto tmp = d_receiver.receive();
+ if (!tmp) {
+ if (d_receiver.isClosed()) {
+ return 0;
+ }
continue;
}
- unixDie("read");
- }
- else if (ret == 0) {
- return false;
- }
- if (ret != sizeof(ptr)) {
- throw std::runtime_error("Partial read, should not happen 2");
+ *t = **tmp;
+ return 1;
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error("reading from the delay pipe: " + std::string(e.what()));
}
-
- *t = *ptr;
- delete ptr;
- return 1;
}
}
The other special case is that the first we have to do.. is in the past, so we need to do it
immediately. */
-
+
double delay=-1; // infinite
struct timespec now;
if(!d_work.empty()) {
}
}
if(delay != 0 ) {
- int ret = d_pipe.readTimeout(&c, delay);
+ int ret = d_pipe.readTimeout(&c, delay);
if(ret > 0) { // we got an object
d_work.emplace(c.when, c.what);
}
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
-#include <map>
#include <time.h>
#include <thread>
+#include "channel.hh"
+
/**
General idea: many threads submit work to this class, but only one executes it. The work should therefore be entirely trivial.
The implementation is that submitter threads create an object that represents the work, and it gets sent over a pipe
{
public:
ObjectPipe();
- ~ObjectPipe();
void write(T& t);
int readTimeout(T* t, double msec); //!< -1 is timeout, 0 is no data, 1 is data. msec<0 waits infinitely long. msec==0 = undefined
void close();
private:
- int d_fds[2];
+ pdns::channel::Sender<T> d_sender;
+ pdns::channel::Receiver<T> d_receiver;
};
template<class T>
*/
#pragma once
+#include "config.h"
+#include <memory>
#include <stdexcept>
#include <string>
#include <openssl/evp.h>
-inline std::string pdns_hash(const EVP_MD * md, const std::string& input)
+namespace pdns
+{
+inline std::string hash(const EVP_MD* messageDigest, const std::string& input)
{
#if defined(HAVE_EVP_MD_CTX_NEW) && defined(HAVE_EVP_MD_CTX_FREE)
- auto mdctx = std::unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+ auto mdctx = std::unique_ptr<EVP_MD_CTX, void (*)(EVP_MD_CTX*)>(EVP_MD_CTX_new(), EVP_MD_CTX_free);
#else
- auto mdctx = std::unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>(EVP_MD_CTX_create(), EVP_MD_CTX_destroy);
+ auto mdctx = std::unique_ptr<EVP_MD_CTX, void (*)(EVP_MD_CTX*)>(EVP_MD_CTX_create(), EVP_MD_CTX_destroy);
#endif
if (!mdctx) {
- throw std::runtime_error(std::string(EVP_MD_name(md)) + " context initialization failed");
+ throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " context initialization failed");
}
- if (EVP_DigestInit_ex(mdctx.get(), md, nullptr) != 1) {
- throw std::runtime_error(std::string(EVP_MD_name(md)) + " EVP initialization failed");
+ if (EVP_DigestInit_ex(mdctx.get(), messageDigest, nullptr) != 1) {
+ throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " EVP initialization failed");
}
if (EVP_DigestUpdate(mdctx.get(), input.data(), input.size()) != 1) {
- throw std::runtime_error(std::string(EVP_MD_name(md)) + " EVP update failed");
+ throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " EVP update failed");
}
- unsigned int written;
+ unsigned int written = 0;
std::string result;
- result.resize(EVP_MD_size(md));
+ result.resize(EVP_MD_size(messageDigest));
- if (EVP_DigestFinal_ex(mdctx.get(), const_cast<unsigned char *>(reinterpret_cast<const unsigned char*>(result.c_str())), &written) != 1) {
- throw std::runtime_error(std::string(EVP_MD_name(md)) + " EVP final failed");
+ // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+ if (EVP_DigestFinal_ex(mdctx.get(), const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(result.c_str())), &written) != 1) {
+ throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " EVP final failed");
}
if (written != result.size()) {
- throw std::runtime_error(std::string(EVP_MD_name(md)) + " EVP final wrote " + std::to_string(written) + ", expected " + std::to_string(result.size()));
+ throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " EVP final wrote " + std::to_string(written) + ", expected " + std::to_string(result.size()));
}
return result;
}
-inline std::string pdns_md5(const std::string& input)
+inline std::string md5(const std::string& input)
{
- const auto md = EVP_md5();
- if (md == nullptr) {
+ const auto* const messageDigest = EVP_md5();
+ if (messageDigest == nullptr) {
throw std::runtime_error("The MD5 digest is not available via the OpenSSL EVP interface");
}
- return pdns_hash(md, input);
+ return pdns::hash(messageDigest, input);
}
-inline std::string pdns_sha1(const std::string& input)
+inline std::string sha1(const std::string& input)
{
- const auto md = EVP_sha1();
- if (md == nullptr) {
+ const auto* const messageDigest = EVP_sha1();
+ if (messageDigest == nullptr) {
throw std::runtime_error("The SHA1 digest is not available via the OpenSSL EVP interface");
}
- return pdns_hash(md, input);
+ return pdns::hash(messageDigest, input);
+}
}
#include <queue>
#include <vector>
#include <thread>
-#include <pthread.h>
#include "threadname.hh"
#include <unistd.h>
+
+#include "channel.hh"
#include "logger.hh"
#include "dns.hh"
#include "dnsbackend.hh"
}
private:
- int nextid;
- time_t d_last_started;
- unsigned int d_overloadQueueLength, d_maxQueueLength;
- int d_num_threads;
+ std::vector<pdns::channel::Sender<QuestionData>> d_senders;
+ std::vector<pdns::channel::Receiver<QuestionData>> d_receivers;
+ time_t d_last_started{0};
std::atomic<unsigned int> d_queued{0};
- std::vector<std::pair<int,int>> d_pipes;
+ unsigned int d_overloadQueueLength{0};
+ unsigned int d_maxQueueLength{0};
+ int d_nextid{0};
+ int d_num_threads{0};
};
-//template<class Answer, class Question, class Backend>::nextid;
template<class Answer, class Question, class Backend> Distributor<Answer,Question,Backend>* Distributor<Answer,Question,Backend>::Create(int n)
{
if( n == 1 )
}
}
-template<class Answer, class Question, class Backend>MultiThreadDistributor<Answer,Question,Backend>::MultiThreadDistributor(int n)
+template<class Answer, class Question, class Backend>MultiThreadDistributor<Answer,Question,Backend>::MultiThreadDistributor(int numberOfThreads) :
+ d_last_started(time(nullptr)), d_overloadQueueLength(::arg().asNum("overload-queue-length")), d_maxQueueLength(::arg().asNum("max-queue-length")), d_num_threads(numberOfThreads)
{
- d_num_threads=n;
- d_overloadQueueLength=::arg().asNum("overload-queue-length");
- d_maxQueueLength=::arg().asNum("max-queue-length");
- nextid=0;
- d_last_started=time(0);
-
- for(int i=0; i < n; ++i) {
- int fds[2];
- if(pipe(fds) < 0)
- unixDie("Creating pipe");
- d_pipes.emplace_back(fds[0], fds[1]);
- }
-
- if (n<1) {
+ if (numberOfThreads < 1) {
g_log<<Logger::Error<<"Asked for fewer than 1 threads, nothing to do"<<endl;
_exit(1);
}
- g_log<<Logger::Warning<<"About to create "<<n<<" backend threads for UDP"<<endl;
- for(int i=0;i<n;i++) {
- std::thread t([=](){distribute(i);});
+ for (int distributorIdx = 0; distributorIdx < numberOfThreads; distributorIdx++) {
+ auto [sender, receiver] = pdns::channel::createObjectQueue<QuestionData>(pdns::channel::SenderBlockingMode::SenderBlocking, pdns::channel::ReceiverBlockingMode::ReceiverBlocking);
+ d_senders.push_back(std::move(sender));
+ d_receivers.push_back(std::move(receiver));
+ }
+
+ g_log<<Logger::Warning<<"About to create "<<numberOfThreads<<" backend threads for UDP"<<endl;
+
+ for (int distributorIdx = 0; distributorIdx < numberOfThreads; distributorIdx++) {
+ std::thread t([=](){distribute(distributorIdx);});
t.detach();
Utility::usleep(50000); // we've overloaded mysql in the past :-)
}
setThreadName("pdns/distributo");
try {
- std::unique_ptr<Backend> b= make_unique<Backend>(); // this will answer our questions
- int queuetimeout=::arg().asNum("queue-limit");
+ auto b = make_unique<Backend>(); // this will answer our questions
+ int queuetimeout = ::arg().asNum("queue-limit");
+ auto& receiver = d_receivers.at(ournum);
- for(;;) {
-
- QuestionData* tempQD = nullptr;
- if(read(d_pipes.at(ournum).first, &tempQD, sizeof(tempQD)) != sizeof(tempQD))
+ for (;;) {
+ auto tempQD = receiver.receive();
+ if (!tempQD) {
unixDie("read");
+ }
--d_queued;
- std::unique_ptr<QuestionData> QD = std::unique_ptr<QuestionData>(tempQD);
- tempQD = nullptr;
+ auto questionData = std::move(*tempQD);
std::unique_ptr<Answer> a = nullptr;
-
- if(queuetimeout && QD->Q.d_dt.udiff()>queuetimeout*1000) {
+ if (queuetimeout && questionData->Q.d_dt.udiff() > queuetimeout * 1000) {
S.inc("timedout-packets");
continue;
- }
+ }
- bool allowRetry=true;
+ bool allowRetry = true;
retry:
// this is the only point where we interact with the backend (synchronous)
try {
if (!b) {
- allowRetry=false;
- b=make_unique<Backend>();
+ allowRetry = false;
+ b = make_unique<Backend>();
}
- a=b->question(QD->Q);
+ a = b->question(questionData->Q);
}
- catch(const PDNSException &e) {
+ catch (const PDNSException &e) {
b.reset();
if (!allowRetry) {
g_log<<Logger::Error<<"Backend error: "<<e.reason<<endl;
- a=QD->Q.replyPacket();
+ a = questionData->Q.replyPacket();
a->setRcode(RCode::ServFail);
S.inc("servfail-packets");
- S.ringAccount("servfail-queries", QD->Q.qdomain, QD->Q.qtype);
+ S.ringAccount("servfail-queries", questionData->Q.qdomain, questionData->Q.qtype);
} else {
g_log<<Logger::Notice<<"Backend error (retry once): "<<e.reason<<endl;
goto retry;
}
}
- catch(...) {
+ catch (...) {
b.reset();
if (!allowRetry) {
- g_log<<Logger::Error<<"Caught unknown exception in Distributor thread "<<(long)pthread_self()<<endl;
- a=QD->Q.replyPacket();
+ g_log<<Logger::Error<<"Caught unknown exception in Distributor thread "<<std::this_thread::get_id()<<endl;
+ a = questionData->Q.replyPacket();
a->setRcode(RCode::ServFail);
S.inc("servfail-packets");
- S.ringAccount("servfail-queries", QD->Q.qdomain, QD->Q.qtype);
+ S.ringAccount("servfail-queries", questionData->Q.qdomain, questionData->Q.qtype);
} else {
- g_log<<Logger::Warning<<"Caught unknown exception in Distributor thread "<<(long)pthread_self()<<" (retry once)"<<endl;
+ g_log<<Logger::Warning<<"Caught unknown exception in Distributor thread "<<std::this_thread::get_id()<<" (retry once)"<<endl;
goto retry;
}
}
- QD->callback(a, QD->start);
+ questionData->callback(a, questionData->start);
#ifdef ENABLE_GSS_TSIG
if (g_doGssTSIG && a != nullptr) {
- QD->Q.cleanupGSS(a->d.rcode);
+ questionData->Q.cleanupGSS(a->d.rcode);
}
#endif
- QD.reset();
+ questionData.reset();
}
b.reset();
}
- catch(const PDNSException &AE) {
+ catch (const PDNSException &AE) {
g_log<<Logger::Error<<"Distributor caught fatal exception: "<<AE.reason<<endl;
_exit(1);
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
g_log<<Logger::Error<<"Distributor caught fatal exception: "<<e.what()<<endl;
_exit(1);
}
- catch(...) {
+ catch (...) {
g_log<<Logger::Error<<"Caught an unknown exception when creating backend, probably"<<endl;
_exit(1);
}
catch(...) {
b.reset();
if (!allowRetry) {
- g_log<<Logger::Error<<"Caught unknown exception in Distributor thread "<<(unsigned long)pthread_self()<<endl;
+ g_log<<Logger::Error<<"Caught unknown exception in Distributor thread "<<std::this_thread::get_id()<<endl;
a=q.replyPacket();
a->setRcode(RCode::ServFail);
S.inc("servfail-packets");
S.ringAccount("servfail-queries", q.qdomain, q.qtype);
} else {
- g_log<<Logger::Warning<<"Caught unknown exception in Distributor thread "<<(unsigned long)pthread_self()<<" (retry once)"<<endl;
+ g_log<<Logger::Warning<<"Caught unknown exception in Distributor thread "<<std::this_thread::get_id()<<" (retry once)"<<endl;
goto retry;
}
}
template<class Answer, class Question, class Backend>int MultiThreadDistributor<Answer,Question,Backend>::question(Question& q, callback_t callback)
{
// this is passed to other process over pipe and released there
- auto QD=new QuestionData(q);
- auto ret = QD->id = nextid++; // might be deleted after write!
- QD->callback=callback;
+ auto questionData = std::make_unique<QuestionData>(q);
+ auto ret = questionData->id = d_nextid++; // might be deleted after write!
+ questionData->callback = callback;
++d_queued;
- if(write(d_pipes.at(QD->id % d_pipes.size()).second, &QD, sizeof(QD)) != sizeof(QD)) {
+ if (!d_senders.at(questionData->id % d_senders.size()).send(std::move(questionData))) {
--d_queued;
- delete QD;
+ questionData.reset();
unixDie("write");
}
- if(d_queued > d_maxQueueLength) {
+ if (d_queued > d_maxQueueLength) {
g_log<<Logger::Error<< d_queued <<" questions waiting for database/backend attention. Limit is "<<::arg().asNum("max-queue-length")<<", respawning"<<endl;
// this will leak the entire contents of all pipes, nothing will be freed. Respawn when this happens!
throw DistributorFatal();
class Opcode
{
public:
- enum { Query=0, IQuery=1, Status=2, Notify=4, Update=5 };
+ enum opcodes_ : uint8_t { Query=0, IQuery=1, Status=2, Notify=4, Update=5 };
static std::string to_s(uint8_t opcode);
};
class DNSResourceRecord
{
public:
- DNSResourceRecord() : last_modified(0), ttl(0), signttl(0), domain_id(-1), qclass(1), scopeMask(0), auth(1), disabled(0) {};
- static DNSResourceRecord fromWire(const DNSRecord& d);
+ static DNSResourceRecord fromWire(const DNSRecord& wire);
- enum Place : uint8_t {QUESTION=0, ANSWER=1, AUTHORITY=2, ADDITIONAL=3}; //!< Type describing the positioning within, say, a DNSPacket
+ enum Place : uint8_t
+ {
+ QUESTION = 0,
+ ANSWER = 1,
+ AUTHORITY = 2,
+ ADDITIONAL = 3
+ }; //!< Type describing the positioning within, say, a DNSPacket
void setContent(const string& content);
- string getZoneRepresentation(bool noDot=false) const;
+ [[nodiscard]] string getZoneRepresentation(bool noDot = false) const;
// data
DNSName qname; //!< the name of this record, for example: www.powerdns.com
// Aligned on 8-byte boundaries on systems where time_t is 8 bytes and int
// is 4 bytes, aka modern linux on x86_64
- time_t last_modified; //!< For autocalculating SOA serial numbers - the backend needs to fill this in
+ time_t last_modified{}; //!< For autocalculating SOA serial numbers - the backend needs to fill this in
- uint32_t ttl; //!< Time To Live of this record
- uint32_t signttl; //!< If non-zero, use this TTL as original TTL in the RRSIG
+ uint32_t ttl{}; //!< Time To Live of this record
+ uint32_t signttl{}; //!< If non-zero, use this TTL as original TTL in the RRSIG
- int domain_id; //!< If a backend implements this, the domain_id of the zone this record is in
+ int domain_id{-1}; //!< If a backend implements this, the domain_id of the zone this record is in
QType qtype; //!< qtype of this record, ie A, CNAME, MX etc
- uint16_t qclass; //!< class of this record
+ uint16_t qclass{1}; //!< class of this record
- uint8_t scopeMask;
- bool auth;
- bool disabled;
+ uint8_t scopeMask{};
+ bool auth{true};
+ bool disabled{};
bool operator==(const DNSResourceRecord& rhs);
- bool operator<(const DNSResourceRecord &b) const
+ bool operator<(const DNSResourceRecord& other) const
{
- if(qname < b.qname)
+ if (qname < other.qname) {
return true;
- if(qname == b.qname)
- return(content < b.content);
+ }
+ if (qname == other.qname) {
+ return (content < other.content);
+ }
return false;
}
};
#elif __linux__ || __GNU__
# include <endian.h>
-#else // with thanks to <arpa/nameser.h>
+#else // with thanks to <arpa/nameser.h>
# define LITTLE_ENDIAN 1234 /* least-significant byte first (vax, pc) */
# define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */
#endif
struct dnsheader {
- unsigned id :16; /* query identification number */
+ uint16_t id; /* query identification number */
#if BYTE_ORDER == BIG_ENDIAN
/* fields in third byte */
unsigned qr: 1; /* response flag */
unsigned ra :1; /* recursion available */
#endif
/* remaining bytes */
- unsigned qdcount :16; /* number of question entries */
- unsigned ancount :16; /* number of answer entries */
- unsigned nscount :16; /* number of authority entries */
- unsigned arcount :16; /* number of resource entries */
+ uint16_t qdcount; /* number of question entries */
+ uint16_t ancount; /* number of answer entries */
+ uint16_t nscount; /* number of authority entries */
+ uint16_t arcount; /* number of resource entries */
};
static_assert(sizeof(dnsheader) == 12, "dnsheader size must be 12");
class dnsheader_aligned
{
public:
+ static bool isMemoryAligned(const void* mem)
+ {
+ return reinterpret_cast<uintptr_t>(mem) % sizeof(uint32_t) == 0; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ }
+
dnsheader_aligned(const void* mem)
{
- if (reinterpret_cast<uintptr_t>(mem) % sizeof(uint32_t) == 0) {
- d_p = reinterpret_cast<const dnsheader*>(mem);
+ if (isMemoryAligned(mem)) {
+ d_p = reinterpret_cast<const dnsheader*>(mem); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
else {
memcpy(&d_h, mem, sizeof(dnsheader));
}
}
- const dnsheader* get() const
+ [[nodiscard]] const dnsheader* get() const
+ {
+ return d_p;
+ }
+
+ [[nodiscard]] const dnsheader& operator*() const
+ {
+ return *d_p;
+ }
+
+ [[nodiscard]] const dnsheader* operator->() const
{
return d_p;
}
private:
- dnsheader d_h;
- const dnsheader *d_p;
+ dnsheader d_h{};
+ const dnsheader* d_p{};
};
-inline uint16_t * getFlagsFromDNSHeader(struct dnsheader * dh)
+inline uint16_t* getFlagsFromDNSHeader(dnsheader* dh)
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ return reinterpret_cast<uint16_t*>(reinterpret_cast<char*>(dh) + sizeof(uint16_t));
+}
+
+inline const uint16_t * getFlagsFromDNSHeader(const dnsheader* dh)
{
- return (uint16_t*) (((char *) dh) + sizeof(uint16_t));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ return reinterpret_cast<const uint16_t*>(reinterpret_cast<const char*>(dh) + sizeof(uint16_t));
}
#define DNS_TYPE_SIZE (2)
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string>
-#include "dns_random.hh"
-#include "arguments.hh"
-#include "logger.hh"
-#include "boost/lexical_cast.hpp"
-
-#if defined(HAVE_RANDOMBYTES_STIR)
-#include <sodium.h>
-#endif
-#if defined(HAVE_RAND_BYTES)
-#include <openssl/rand.h>
-#endif
-#if defined(HAVE_GETRANDOM)
-#include <sys/random.h>
-#endif
-
-static enum DNS_RNG {
- RNG_UNINITIALIZED = 0,
- RNG_SODIUM,
- RNG_OPENSSL,
- RNG_GETRANDOM,
- RNG_ARC4RANDOM,
- RNG_URANDOM,
- RNG_KISS,
-} chosen_rng = RNG_UNINITIALIZED;
-
-static int urandom_fd = -1;
-
-#if defined(HAVE_KISS_RNG)
-/* KISS is intended for development use only */
-static unsigned int kiss_seed;
-static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong;
-
-static void
-kiss_init(unsigned int seed)
-{
- kiss_seed = seed;
- kiss_jsr = 0x5eed5eed; /* simply mustn't be 0 */
- kiss_z = 1 ^ (kiss_w = kiss_jcong = seed); /* w=z=0 is bad, see Rose */
-}
-
-static unsigned int
-kiss_rand(void)
-{
- kiss_z = 36969 * (kiss_z&65535) + (kiss_z>>16);
- kiss_w = 18000 * (kiss_w&65535) + (kiss_w>>16);
- kiss_jcong = 69069 * kiss_jcong + 1234567;
- kiss_jsr^=(kiss_jsr<<13); /* <<17, >>13 gives cycle length 2^28.2 max */
- kiss_jsr^=(kiss_jsr>>17); /* <<13, >>17 gives maximal cycle length */
- kiss_jsr^=(kiss_jsr<<5);
- return (((kiss_z<<16) + kiss_w) ^ kiss_jcong) + kiss_jsr;
-}
-#endif
-
-static void dns_random_setup(bool force=false)
-{
- string rdev;
- string rng;
- /* check if selection has been done */
- if (chosen_rng > RNG_UNINITIALIZED && !force)
- return;
-
-/* XXX: A horrible hack to allow using dns_random in places where arguments are not available.
- Forces /dev/urandom usage
-*/
-#if defined(USE_URANDOM_ONLY)
- chosen_rng = RNG_URANDOM;
- rdev = "/dev/urandom";
-#else
- rng = ::arg()["rng"];
- rdev = ::arg()["entropy-source"];
- if (rng == "auto") {
-# if defined(HAVE_GETRANDOM)
- chosen_rng = RNG_GETRANDOM;
-# elif defined(HAVE_ARC4RANDOM)
- chosen_rng = RNG_ARC4RANDOM;
-# elif defined(HAVE_RANDOMBYTES_STIR)
- chosen_rng = RNG_SODIUM;
-# elif defined(HAVE_RAND_BYTES)
- chosen_rng = RNG_OPENSSL;
-# else
- chosen_rng = RNG_URANDOM;
-# endif
-# if defined(HAVE_RANDOMBYTES_STIR)
- } else if (rng == "sodium") {
- chosen_rng = RNG_SODIUM;
-# endif
-# if defined(HAVE_RAND_BYTES)
- } else if (rng == "openssl") {
- chosen_rng = RNG_OPENSSL;
-# endif
-# if defined(HAVE_GETRANDOM)
- } else if (rng == "getrandom") {
- chosen_rng = RNG_GETRANDOM;
-# endif
-# if defined(HAVE_ARC4RANDOM)
- } else if (rng == "arc4random") {
- chosen_rng = RNG_ARC4RANDOM;
-# endif
- } else if (rng == "urandom") {
- chosen_rng = RNG_URANDOM;
-#if defined(HAVE_KISS_RNG)
- } else if (rng == "kiss") {
- chosen_rng = RNG_KISS;
- g_log<<Logger::Warning<<"kiss rng should not be used in production environment"<<std::endl;
-#endif
- } else {
- throw std::runtime_error("Unsupported rng '" + rng + "'");
- }
-
-# if defined(HAVE_RANDOMBYTES_STIR)
- if (chosen_rng == RNG_SODIUM) {
- if (sodium_init() == -1)
- throw std::runtime_error("Unable to initialize sodium crypto library");
- /* make sure it's set up */
- randombytes_stir();
- }
-# endif
-
-# if defined(HAVE_GETRANDOM)
- if (chosen_rng == RNG_GETRANDOM) {
- char buf[1];
- // some systems define getrandom but it does not really work, e.g. because it's
- // not present in kernel.
- if (getrandom(buf, sizeof(buf), 0) == -1 && errno != EINTR) {
- g_log<<Logger::Warning<<"getrandom() failed: "<<stringerror()<<", falling back to " + rdev<<std::endl;
- chosen_rng = RNG_URANDOM;
- }
- }
-# endif
-
-# if defined(HAVE_RAND_BYTES)
- if (chosen_rng == RNG_OPENSSL) {
- int ret;
- unsigned char buf[1];
- if ((ret = RAND_bytes(buf, sizeof(buf))) == -1)
- throw std::runtime_error("RAND_bytes not supported by current SSL engine");
- if (ret == 0)
- throw std::runtime_error("Openssl RNG was not seeded");
- }
-# endif
-#endif /* USE_URANDOM_ONLY */
- if (chosen_rng == RNG_URANDOM) {
- urandom_fd = open(rdev.c_str(), O_RDONLY);
- if (urandom_fd == -1)
- throw std::runtime_error("Cannot open " + rdev + ": " + stringerror());
- }
-#if defined(HAVE_KISS_RNG)
- if (chosen_rng == RNG_KISS) {
- unsigned int seed;
- urandom_fd = open(rdev.c_str(), O_RDONLY);
- if (urandom_fd == -1)
- throw std::runtime_error("Cannot open " + rdev + ": " + stringerror());
- if (read(urandom_fd, &seed, sizeof(seed)) < 0) {
- (void)close(urandom_fd);
- throw std::runtime_error("Cannot read random device");
- }
- kiss_init(seed);
- (void)close(urandom_fd);
- }
-#endif
-}
-
-void dns_random_init(const string& data __attribute__((unused)), bool force) {
- dns_random_setup(force);
- (void)dns_random(1);
- // init should occur already in dns_random_setup
- // this interface is only for KISS
-#if defined(HAVE_KISS_RNG)
- unsigned int seed;
- if (chosen_rng != RNG_KISS)
- return;
- if (data.size() != 16)
- throw std::runtime_error("invalid seed");
- seed = (data[0] + (data[1]<<8) + (data[2]<<16) + (data[3]<<24)) ^
- (data[4] + (data[5]<<8) + (data[6]<<16) + (data[7]<<24)) ^
- (data[8] + (data[9]<<8) + (data[10]<<16) + (data[11]<<24)) ^
- (data[12] + (data[13]<<8) + (data[14]<<16) + (data[15]<<24));
- kiss_init(seed);
-#endif
-}
-
-uint32_t dns_random(uint32_t upper_bound) {
- if (chosen_rng == RNG_UNINITIALIZED)
- dns_random_setup();
-
- if (upper_bound < 2)
- return 0;
-
- unsigned int min = pdns::random_minimum_acceptable_value(upper_bound);
-
- switch(chosen_rng) {
- case RNG_UNINITIALIZED:
- throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
- case RNG_SODIUM:
-#if defined(HAVE_RANDOMBYTES_STIR) && !defined(USE_URANDOM_ONLY)
- return randombytes_uniform(upper_bound);
-#else
- throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-#endif /* RND_SODIUM */
- case RNG_OPENSSL: {
-#if defined(HAVE_RAND_BYTES) && !defined(USE_URANDOM_ONLY)
- uint32_t num = 0;
- do {
- if (RAND_bytes(reinterpret_cast<unsigned char*>(&num), sizeof(num)) < 1)
- throw std::runtime_error("Openssl RNG was not seeded");
- }
- while(num < min);
-
- return num % upper_bound;
-#else
- throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-#endif /* RNG_OPENSSL */
- }
- case RNG_GETRANDOM: {
-#if defined(HAVE_GETRANDOM) && !defined(USE_URANDOM_ONLY)
- uint32_t num = 0;
- do {
- auto got = getrandom(&num, sizeof(num), 0);
- if (got == -1 && errno == EINTR) {
- continue;
- }
- if (got != sizeof(num)) {
- throw std::runtime_error("getrandom() failed: " + stringerror());
- }
- }
- while(num < min);
-
- return num % upper_bound;
-#else
- throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-#endif
- }
- case RNG_ARC4RANDOM:
-#if defined(HAVE_ARC4RANDOM) && !defined(USE_URANDOM_ONLY)
- return arc4random_uniform(upper_bound);
-#else
- throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-#endif
- case RNG_URANDOM: {
- uint32_t num = 0;
- size_t attempts = 5;
- do {
- ssize_t got = read(urandom_fd, &num, sizeof(num));
- if (got < 0) {
- if (errno == EINTR) {
- continue;
- }
-
- (void)close(urandom_fd);
- throw std::runtime_error("Cannot read random device");
- }
- else if (static_cast<size_t>(got) != sizeof(num)) {
- /* short read, let's retry */
- if (attempts == 0) {
- throw std::runtime_error("Too many short reads on random device");
- }
- attempts--;
- continue;
- }
- }
- while(num < min);
-
- return num % upper_bound;
- }
-#if defined(HAVE_KISS_RNG)
- case RNG_KISS: {
- uint32_t num = 0;
- do {
- num = kiss_rand();
- }
- while(num < min);
-
- return num % upper_bound;
- }
-#endif
- default:
- throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
- };
-}
-
-uint16_t dns_random_uint16()
-{
- return dns_random(0x10000);
-}
#include <limits>
#include <string>
-void dns_random_init(const std::string& data = "", bool force_reinit = false);
-uint32_t dns_random(uint32_t n);
-uint16_t dns_random_uint16();
+#include <ext/arc4random/arc4random.hh>
-namespace pdns {
- struct dns_random_engine {
+inline uint32_t dns_random(uint32_t upper_bound)
+{
+ return arc4random_uniform(upper_bound);
+}
+
+inline uint32_t dns_random_uint32()
+{
+ return arc4random();
+}
- typedef uint32_t result_type;
+inline uint16_t dns_random_uint16()
+{
+ return arc4random() & 0xffff;
+}
- static constexpr result_type min()
- {
- return 0;
- }
+namespace pdns
+{
+struct dns_random_engine
+{
- static constexpr result_type max()
- {
- return std::numeric_limits<result_type>::max() - 1;
- }
+ using result_type = uint32_t;
+
+ static constexpr result_type min()
+ {
+ return 0;
+ }
- result_type operator()()
- {
- return dns_random(std::numeric_limits<result_type>::max());
- }
- };
+ static constexpr result_type max()
+ {
+ return std::numeric_limits<result_type>::max();
+ }
- /* minimum value that a PRNG should return for this upper bound to avoid a modulo bias */
- inline unsigned int random_minimum_acceptable_value(uint32_t upper_bound)
+ result_type operator()()
{
- /* Parts of this code come from arc4random_uniform */
- /* To avoid "modulo bias" for some methods, calculate
- minimum acceptable value for random number to improve
- uniformity.
+ return dns_random_uint32();
+ }
+};
+
+/* minimum value that a PRNG should return for this upper bound to avoid a modulo bias */
+inline unsigned int random_minimum_acceptable_value(uint32_t upper_bound)
+{
+ /* Parts of this code come from arc4random_uniform */
+ /* To avoid "modulo bias" for some methods, calculate
+ minimum acceptable value for random number to improve
+ uniformity.
- On applicable rngs, we loop until the rng spews out
- value larger than min, and then take modulo out of that.
- */
- unsigned int min;
+ On applicable rngs, we loop until the rng spews out
+ value larger than min, and then take modulo out of that.
+ */
+ unsigned int min = 0;
#if (ULONG_MAX > 0xffffffffUL)
- min = 0x100000000UL % upper_bound;
+ min = 0x100000000UL % upper_bound;
#else
- /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
- if (upper_bound > 0x80000000)
- min = 1 + ~upper_bound; /* 2**32 - upper_bound */
- else {
- /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
- min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound;
- }
-#endif
- return min;
+ /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
+ if (upper_bound > 0x80000000)
+ min = 1 + ~upper_bound; /* 2**32 - upper_bound */
+ else {
+ /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
+ min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound;
}
+#endif
+ return min;
+}
}
+++ /dev/null
-#define USE_URANDOM_ONLY
-#include "dns_random.cc"
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <memory>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
// this is so the geoipbackend can set this pointer if loaded for lua-record.cc
std::function<std::string(const std::string&, int)> g_getGeo;
-bool DNSBackend::getAuth(const DNSName &target, SOAData *sd)
+bool DNSBackend::getAuth(const DNSName& target, SOAData* soaData)
{
- return this->getSOA(target, *sd);
+ return this->getSOA(target, *soaData);
}
-void DNSBackend::setArgPrefix(const string &prefix)
+void DNSBackend::setArgPrefix(const string& prefix)
{
- d_prefix=prefix;
+ d_prefix = prefix;
}
-bool DNSBackend::mustDo(const string &key)
+bool DNSBackend::mustDo(const string& key)
{
- return arg().mustDo(d_prefix+"-"+key);
+ return arg().mustDo(d_prefix + "-" + key);
}
-const string &DNSBackend::getArg(const string &key)
+const string& DNSBackend::getArg(const string& key)
{
- return arg()[d_prefix+"-"+key];
+ return arg()[d_prefix + "-" + key];
}
-int DNSBackend::getArgAsNum(const string &key)
+int DNSBackend::getArgAsNum(const string& key)
{
- return arg().asNum(d_prefix+"-"+key);
+ return arg().asNum(d_prefix + "-" + key);
}
-void BackendFactory::declare(const string &suffix, const string ¶m, const string &help, const string &value)
+void BackendFactory::declare(const string& suffix, const string& param, const string& explanation, const string& value)
{
- string fullname=d_name+suffix+"-"+param;
- arg().set(fullname,help)=value;
- arg().setDefault(fullname,value);
+ string fullname = d_name + suffix + "-" + param;
+ arg().set(fullname, explanation) = value;
+ arg().setDefault(fullname, value);
}
-const string &BackendFactory::getName() const
+const string& BackendFactory::getName() const
{
return d_name;
}
-BackendMakerClass &BackendMakers()
+BackendMakerClass& BackendMakers()
{
static BackendMakerClass bmc;
return bmc;
}
-void BackendMakerClass::report(BackendFactory *bf)
+void BackendMakerClass::report(BackendFactory* backendFactory)
{
- d_repository[bf->getName()]=bf;
+ d_repository[backendFactory->getName()] = backendFactory;
}
void BackendMakerClass::clear()
load_all();
vector<string> ret;
// copy(d_repository.begin(), d_repository.end(),back_inserter(ret));
- for(d_repository_t::const_iterator i=d_repository.begin();i!=d_repository.end();++i)
- ret.push_back(i->first);
+ for (auto& repo : d_repository) {
+ ret.push_back(repo.first);
+ }
return ret;
}
void BackendMakerClass::load_all()
{
- // TODO: Implement this?
- DIR *dir=opendir(arg()["module-dir"].c_str());
- if(!dir) {
- g_log<<Logger::Error<<"Unable to open module directory '"<<arg()["module-dir"]<<"'"<<endl;
- return;
- }
- struct dirent *entry;
- while((entry=readdir(dir))) {
- if(!strncmp(entry->d_name,"lib",3) &&
- strlen(entry->d_name)>13 &&
- !strcmp(entry->d_name+strlen(entry->d_name)-10,"backend.so"))
- load(entry->d_name);
+ auto directoryError = pdns::visit_directory(arg()["module-dir"], []([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+ if (boost::starts_with(name, "lib") && name.size() > 13 && boost::ends_with(name, "backend.so")) {
+ load(std::string(name));
+ }
+ return true;
+ });
+ if (directoryError) {
+ g_log << Logger::Error << "Unable to open module directory '" << arg()["module-dir"] << "': " << *directoryError << endl;
}
- closedir(dir);
}
-void BackendMakerClass::load(const string &module)
+void BackendMakerClass::load(const string& module)
{
- bool res;
+ bool res = false;
- if(module.find('.')==string::npos)
- res=UeberBackend::loadmodule(arg()["module-dir"]+"/lib"+module+"backend.so");
- else if(module[0]=='/' || (module[0]=='.' && module[1]=='/') || (module[0]=='.' && module[1]=='.')) // absolute or current path
- res=UeberBackend::loadmodule(module);
- else
- res=UeberBackend::loadmodule(arg()["module-dir"]+"/"+module);
+ if (module.find('.') == string::npos) {
+ res = UeberBackend::loadmodule(arg()["module-dir"] + "/lib" + module + "backend.so");
+ }
+ else if (module[0] == '/' || (module[0] == '.' && module[1] == '/') || (module[0] == '.' && module[1] == '.')) { // absolute or current path
+ res = UeberBackend::loadmodule(module);
+ }
+ else {
+ res = UeberBackend::loadmodule(arg()["module-dir"] + "/" + module);
+ }
- if(res==false) {
- g_log<<Logger::Error<<"DNSBackend unable to load module in "<<module<<endl;
+ if (!res) {
+ g_log << Logger::Error << "DNSBackend unable to load module in " << module << endl;
exit(1);
}
}
-void BackendMakerClass::launch(const string &instr)
+void BackendMakerClass::launch(const string& instr)
{
// if(instr.empty())
// throw ArgException("Not launching any backends - nameserver won't function");
vector<string> parts;
- stringtok(parts,instr,", ");
+ stringtok(parts, instr, ", ");
- for (const auto& part : parts)
- if (count(parts.begin(), parts.end(), part) > 1)
+ for (const auto& part : parts) {
+ if (count(parts.begin(), parts.end(), part) > 1) {
throw ArgException("Refusing to launch multiple backends with the same name '" + part + "', verify all 'launch' statements in your configuration");
+ }
+ }
- for(const auto & part : parts) {
- string module, name;
- vector<string>pparts;
- stringtok(pparts,part,": ");
- module=pparts[0];
- if(pparts.size()>1)
- name="-"+pparts[1];
+ for (const auto& part : parts) {
+ string module;
+ string name;
+ vector<string> pparts;
+ stringtok(pparts, part, ": ");
+ module = pparts[0];
+ if (pparts.size() > 1) {
+ name = "-" + pparts[1];
+ }
- if(d_repository.find(module)==d_repository.end()) {
+ if (d_repository.find(module) == d_repository.end()) {
// this is *so* userfriendly
load(module);
- if(d_repository.find(module)==d_repository.end())
- throw ArgException("Trying to launch unknown backend '"+module+"'");
+ if (d_repository.find(module) == d_repository.end()) {
+ throw ArgException("Trying to launch unknown backend '" + module + "'");
+ }
}
d_repository[module]->declareArguments(name);
d_instances.emplace_back(module, name);
return d_instances.size();
}
-vector<DNSBackend *> BackendMakerClass::all(bool metadataOnly)
+vector<std::unique_ptr<DNSBackend>> BackendMakerClass::all(bool metadataOnly)
{
- vector<DNSBackend *> ret;
- if(d_instances.empty())
+ if (d_instances.empty()) {
throw PDNSException("No database backends configured for launch, unable to function");
+ }
+ vector<unique_ptr<DNSBackend>> ret;
ret.reserve(d_instances.size());
+ std::string current; // to make the exception text more useful
+
try {
for (const auto& instance : d_instances) {
- DNSBackend *made = nullptr;
-
- if (metadataOnly) {
- made = d_repository[instance.first]->makeMetadataOnly(instance.second);
- }
- else {
- made = d_repository[instance.first]->make(instance.second);
- }
-
- if (!made) {
+ current = instance.first + instance.second;
+ auto* repo = d_repository[instance.first];
+ std::unique_ptr<DNSBackend> made{metadataOnly ? repo->makeMetadataOnly(instance.second) : repo->make(instance.second)};
+ if (made == nullptr) {
throw PDNSException("Unable to launch backend '" + instance.first + "'");
}
-
- ret.push_back(made);
+ ret.push_back(std::move(made));
}
}
- catch(const PDNSException &ae) {
- g_log<<Logger::Error<<"Caught an exception instantiating a backend: "<<ae.reason<<endl;
- g_log<<Logger::Error<<"Cleaning up"<<endl;
- for (auto i : ret) {
- delete i;
- }
+ catch (const PDNSException& ae) {
+ g_log << Logger::Error << "Caught an exception instantiating a backend (" << current << "): " << ae.reason << endl;
+ g_log << Logger::Error << "Cleaning up" << endl;
+ ret.clear();
throw;
- } catch(...) {
+ }
+ catch (...) {
// and cleanup
- g_log<<Logger::Error<<"Caught an exception instantiating a backend, cleaning up"<<endl;
- for (auto i : ret) {
- delete i;
- }
+ g_log << Logger::Error << "Caught an exception instantiating a backend (" << current << "), cleaning up" << endl;
+ ret.clear();
throw;
}
\param sd SOAData which is filled with the SOA details
\param unmodifiedSerial bool if set, serial will be returned as stored in the backend (maybe 0)
*/
-bool DNSBackend::getSOA(const DNSName &domain, SOAData &sd)
+bool DNSBackend::getSOA(const DNSName& domain, SOAData& soaData)
{
- this->lookup(QType(QType::SOA),domain,-1);
+ this->lookup(QType(QType::SOA), domain, -1);
S.inc("backend-queries");
- DNSResourceRecord rr;
- int hits=0;
+ DNSResourceRecord resourceRecord;
+ int hits = 0;
- sd.db = nullptr;
+ soaData.db = nullptr;
try {
- while (this->get(rr)) {
- if (rr.qtype != QType::SOA) {
+ while (this->get(resourceRecord)) {
+ if (resourceRecord.qtype != QType::SOA) {
throw PDNSException("Got non-SOA record when asking for SOA, zone: '" + domain.toLogString() + "'");
}
hits++;
- sd.qname = domain;
- sd.ttl = rr.ttl;
- sd.db = this;
- sd.domain_id = rr.domain_id;
- fillSOAData(rr.content, sd);
+ soaData.qname = domain;
+ soaData.ttl = resourceRecord.ttl;
+ soaData.db = this;
+ soaData.domain_id = resourceRecord.domain_id;
+ fillSOAData(resourceRecord.content, soaData);
}
}
catch (...) {
- while (this->get(rr)) {
+ while (this->get(resourceRecord)) {
;
}
throw;
}
- return hits;
+ return hits != 0;
}
-bool DNSBackend::get(DNSZoneRecord& dzr)
+bool DNSBackend::get(DNSZoneRecord& zoneRecord)
{
// cout<<"DNSBackend::get(DNSZoneRecord&) called - translating into DNSResourceRecord query"<<endl;
- DNSResourceRecord rr;
- if(!this->get(rr))
+ DNSResourceRecord resourceRecord;
+ if (!this->get(resourceRecord)) {
return false;
- dzr.auth = rr.auth;
- dzr.domain_id = rr.domain_id;
- dzr.scopeMask = rr.scopeMask;
- if(rr.qtype.getCode() == QType::TXT && !rr.content.empty() && rr.content[0]!='"')
- rr.content = "\""+ rr.content + "\"";
+ }
+ zoneRecord.auth = resourceRecord.auth;
+ zoneRecord.domain_id = resourceRecord.domain_id;
+ zoneRecord.scopeMask = resourceRecord.scopeMask;
+ if (resourceRecord.qtype.getCode() == QType::TXT && !resourceRecord.content.empty() && resourceRecord.content[0] != '"') {
+ resourceRecord.content = "\"" + resourceRecord.content + "\"";
+ }
try {
- dzr.dr = DNSRecord(rr);
+ zoneRecord.dr = DNSRecord(resourceRecord);
}
- catch(...) {
- while(this->get(rr));
+ catch (...) {
+ while (this->get(resourceRecord)) {
+ ;
+ }
throw;
}
return true;
}
}
-void fillSOAData(const DNSZoneRecord& in, SOAData& sd)
+void fillSOAData(const DNSZoneRecord& inZoneRecord, SOAData& soaData)
{
- sd.domain_id = in.domain_id;
- sd.ttl = in.dr.d_ttl;
-
- auto src=getRR<SOARecordContent>(in.dr);
- sd.nameserver = src->d_mname;
- sd.hostmaster = src->d_rname;
- sd.serial = src->d_st.serial;
- sd.refresh = src->d_st.refresh;
- sd.retry = src->d_st.retry;
- sd.expire = src->d_st.expire;
- sd.minimum = src->d_st.minimum;
+ soaData.domain_id = inZoneRecord.domain_id;
+ soaData.ttl = inZoneRecord.dr.d_ttl;
+
+ auto src = getRR<SOARecordContent>(inZoneRecord.dr);
+ soaData.nameserver = src->d_mname;
+ soaData.rname = src->d_rname;
+ soaData.serial = src->d_st.serial;
+ soaData.refresh = src->d_st.refresh;
+ soaData.retry = src->d_st.retry;
+ soaData.expire = src->d_st.expire;
+ soaData.minimum = src->d_st.minimum;
}
-std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& sd)
+std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& soaData)
{
- struct soatimes st;
- st.serial = sd.serial;
- st.refresh = sd.refresh;
- st.retry = sd.retry;
- st.expire = sd.expire;
- st.minimum = sd.minimum;
- return std::make_shared<SOARecordContent>(sd.nameserver, sd.hostmaster, st);
+ struct soatimes soaTimes
+ {
+ .serial = soaData.serial,
+ .refresh = soaData.refresh,
+ .retry = soaData.retry,
+ .expire = soaData.expire,
+ .minimum = soaData.minimum,
+ };
+ return std::make_shared<SOARecordContent>(soaData.nameserver, soaData.rname, soaTimes);
}
-void fillSOAData(const string &content, SOAData &data)
+void fillSOAData(const string& content, SOAData& soaData)
{
- vector<string>parts;
+ vector<string> parts;
parts.reserve(7);
stringtok(parts, content);
try {
- data.nameserver = DNSName(parts.at(0));
- data.hostmaster = DNSName(parts.at(1));
- pdns::checked_stoi_into(data.serial, parts.at(2));
- pdns::checked_stoi_into(data.refresh, parts.at(3));
- pdns::checked_stoi_into(data.retry, parts.at(4));
- pdns::checked_stoi_into(data.expire, parts.at(5));
- pdns::checked_stoi_into(data.minimum, parts.at(6));
+ soaData.nameserver = DNSName(parts.at(0));
+ soaData.rname = DNSName(parts.at(1));
+ pdns::checked_stoi_into(soaData.serial, parts.at(2));
+ pdns::checked_stoi_into(soaData.refresh, parts.at(3));
+ pdns::checked_stoi_into(soaData.retry, parts.at(4));
+ pdns::checked_stoi_into(soaData.expire, parts.at(5));
+ pdns::checked_stoi_into(soaData.minimum, parts.at(6));
}
- catch(const std::out_of_range& oor) {
+ catch (const std::out_of_range& oor) {
throw PDNSException("Out of range exception parsing '" + content + "'");
}
}
#pragma once
#include <algorithm>
+#include <cstddef>
class DNSPacket;
#include "utility.hh"
#include <string>
+#include <utility>
#include <vector>
#include <map>
#include <sys/types.h>
struct DomainInfo
{
- DomainInfo() : last_check(0), backend(nullptr), id(0), notified_serial(0), receivedNotify(false), serial(0), kind(DomainInfo::Native) {}
+ DomainInfo() = default;
DNSName zone;
DNSName catalog;
time_t last_check{};
string options;
string account;
- vector<ComboAddress> masters;
- DNSBackend *backend{};
+ vector<ComboAddress> primaries;
+ DNSBackend* backend{};
uint32_t id{};
uint32_t notified_serial{};
// Do not reorder (lmdbbackend)!!! One exception 'All' is always last.
enum DomainKind : uint8_t
{
- Master,
- Slave,
+ Primary,
+ Secondary,
Native,
Producer,
Consumer,
All
- } kind;
+ } kind{DomainInfo::Native};
- [[nodiscard]] const char *getKindString() const
+ [[nodiscard]] const char* getKindString() const
{
return DomainInfo::getKindString(kind);
}
- static const char *getKindString(enum DomainKind kind)
+ static const char* getKindString(enum DomainKind kind)
{
- const char* kinds[] = {"Master", "Slave", "Native", "Producer", "Consumer", "All"};
- return kinds[kind];
+ std::array<const char*, 6> kinds{"Master", "Slave", "Native", "Producer", "Consumer", "All"};
+ return kinds.at(kind);
}
static DomainKind stringToKind(const string& kind)
{
if (pdns_iequals(kind, "SECONDARY") || pdns_iequals(kind, "SLAVE")) {
- return DomainInfo::Slave;
+ return DomainInfo::Secondary;
}
if (pdns_iequals(kind, "PRIMARY") || pdns_iequals(kind, "MASTER")) {
- return DomainInfo::Master;
+ return DomainInfo::Primary;
}
if (pdns_iequals(kind, "PRODUCER")) {
return DomainInfo::Producer;
return DomainInfo::Native;
}
- [[nodiscard]] bool isPrimaryType() const { return (kind == DomainInfo::Master || kind == DomainInfo::Producer); }
- [[nodiscard]] bool isSecondaryType() const { return (kind == DomainInfo::Slave || kind == DomainInfo::Consumer); }
+ [[nodiscard]] bool isPrimaryType() const { return (kind == DomainInfo::Primary || kind == DomainInfo::Producer); }
+ [[nodiscard]] bool isSecondaryType() const { return (kind == DomainInfo::Secondary || kind == DomainInfo::Consumer); }
[[nodiscard]] bool isCatalogType() const { return (kind == DomainInfo::Producer || kind == DomainInfo::Consumer); }
- [[nodiscard]] bool isMaster(const ComboAddress& ipAddress) const
+ [[nodiscard]] bool isPrimary(const ComboAddress& ipAddress) const
{
- return std::any_of(masters.begin(), masters.end(), [ipAddress](auto master) { return ComboAddress::addressOnlyEqual()(ipAddress, master); });
+ return std::any_of(primaries.begin(), primaries.end(), [ipAddress](auto primary) { return ComboAddress::addressOnlyEqual()(ipAddress, primary); });
}
};
-struct TSIGKey {
- DNSName name;
- DNSName algorithm;
- std::string key;
+struct TSIGKey
+{
+ DNSName name;
+ DNSName algorithm;
+ std::string key;
};
-struct AutoPrimary {
- AutoPrimary(const string& new_ip, const string& new_nameserver, const string& new_account) :
- ip(new_ip), nameserver(new_nameserver), account(new_account){};
- std::string ip;
- std::string nameserver;
- std::string account;
+struct AutoPrimary
+{
+ AutoPrimary(string new_ip, string new_nameserver, string new_account) :
+ ip(std::move(new_ip)), nameserver(std::move(new_nameserver)), account(std::move(new_account)){};
+ std::string ip;
+ std::string nameserver;
+ std::string account;
};
class DNSPacket;
{
public:
//! lookup() initiates a lookup. A lookup without results should not throw!
- virtual void lookup(const QType &qtype, const DNSName &qdomain, int zoneId=-1, DNSPacket *pkt_p=nullptr)=0;
- virtual bool get(DNSResourceRecord &)=0; //!< retrieves one DNSResource record, returns false if no more were available
- virtual bool get(DNSZoneRecord &r);
+ virtual void lookup(const QType& qtype, const DNSName& qdomain, int zoneId = -1, DNSPacket* pkt_p = nullptr) = 0;
+ virtual bool get(DNSResourceRecord&) = 0; //!< retrieves one DNSResource record, returns false if no more were available
+ virtual bool get(DNSZoneRecord& zoneRecord);
//! Initiates a list of the specified domain
/** Once initiated, DNSResourceRecord objects can be retrieved using get(). Should return false
if the backend does not consider itself responsible for the id passed.
\param domain_id ID of which a list is requested
*/
- virtual bool list(const DNSName &target, int domain_id, bool include_disabled=false)=0;
+ virtual bool list(const DNSName& target, int domain_id, bool include_disabled = false) = 0;
- virtual ~DNSBackend(){};
+ virtual ~DNSBackend() = default;
//! fills the soadata struct with the SOA details. Returns false if there is no SOA.
- virtual bool getSOA(const DNSName &name, SOAData &soadata);
+ virtual bool getSOA(const DNSName& domain, SOAData& soaData);
virtual bool replaceRRSet(uint32_t /* domain_id */, const DNSName& /* qname */, const QType& /* qt */, const vector<DNSResourceRecord>& /* rrset */)
{
/** Determines if we are authoritative for a zone, and at what level */
virtual bool getAuth(const DNSName& target, SOAData* /* sd */);
- struct KeyData {
+ struct KeyData
+ {
std::string content;
- unsigned int id;
- unsigned int flags;
- bool active;
- bool published;
+ unsigned int id{0};
+ unsigned int flags{0};
+ bool active{false};
+ bool published{false};
};
virtual bool getDomainKeys(const DNSName& /* name */, std::vector<KeyData>& /* keys */) { return false; }
return false;
}
- virtual void feedComment(const Comment& /* comment */)
+ virtual bool feedComment(const Comment& /* comment */)
{
+ return false;
}
virtual bool replaceComments(const uint32_t /* domain_id */, const DNSName& /* qname */, const QType& /* qt */, const vector<Comment>& /* comments */)
return false;
}
- //! returns true if master ip is master for domain name.
+ //! returns true if primary ip is primary for domain name.
//! starts the transaction for updating domain qname (FIXME: what is id?)
virtual bool startTransaction(const DNSName& /* qname */, int /* id */ = -1)
{
{
return false;
}
- //! slave capable backends should return a list of slaves that should be rechecked for staleness
- virtual void getUnfreshSlaveInfos(vector<DomainInfo>* /* domains */)
+ //! secondary capable backends should return a list of secondaries that should be rechecked for staleness
+ virtual void getUnfreshSecondaryInfos(vector<DomainInfo>* /* domains */)
{
}
ips->insert(meta.begin(), meta.end());
}
- //! get list of domains that have been changed since their last notification to slaves
- virtual void getUpdatedMasters(vector<DomainInfo>& /* domains */, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
+ //! get list of domains that have been changed since their last notification to secondaries
+ virtual void getUpdatedPrimaries(vector<DomainInfo>& /* domains */, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
{
}
{
}
- //! Called by PowerDNS to inform a backend that the changes in the domain have been reported to slaves
+ //! Called by PowerDNS to inform a backend that the changes in the domain have been reported to secondaries
virtual void setNotified(uint32_t /* id */, uint32_t /* serial */)
{
}
- //! Called when the Master list of a domain should be changed
- virtual bool setMasters(const DNSName& /* domain */, const vector<ComboAddress>& /* masters */)
+ //! Called when the Primary list of a domain should be changed
+ virtual bool setPrimaries(const DNSName& /* domain */, const vector<ComboAddress>& /* primaries */)
{
return false;
}
- //! Called when the Kind of a domain should be changed (master -> native and similar)
+ //! Called when the Kind of a domain should be changed (primary -> native and similar)
virtual bool setKind(const DNSName& /* domain */, const DomainInfo::DomainKind /* kind */)
{
return false;
}
//! Can be called to seed the getArg() function with a prefix
- void setArgPrefix(const string &prefix);
+ void setArgPrefix(const string& prefix);
- //! Add an entry for a super master
- virtual bool superMasterAdd(const struct AutoPrimary& /* primary */)
+ //! Add an entry for a super primary
+ virtual bool autoPrimaryAdd(const struct AutoPrimary& /* primary */)
{
return false;
}
- //! Remove an entry for a super master
+ //! Remove an entry for a super primary
virtual bool autoPrimaryRemove(const struct AutoPrimary& /* primary */)
{
return false;
}
- //! List all SuperMasters, returns false if feature not supported.
+ //! List all AutoPrimaries, returns false if feature not supported.
virtual bool autoPrimariesList(std::vector<AutoPrimary>& /* primaries */)
{
return false;
}
- //! determine if ip is a supermaster or a domain
- virtual bool superMasterBackend(const string& /* ip */, const DNSName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* /* account */, DNSBackend** /* db */)
+ //! determine if ip is a autoprimary or a domain
+ virtual bool autoPrimaryBackend(const string& /* ip */, const DNSName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* /* account */, DNSBackend** /* db */)
{
return false;
}
//! called by PowerDNS to create a new domain
- virtual bool createDomain(const DNSName& /* domain */, const DomainInfo::DomainKind /* kind */, const vector<ComboAddress>& /* masters */, const string& /* account */)
+ virtual bool createDomain(const DNSName& /* domain */, const DomainInfo::DomainKind /* kind */, const vector<ComboAddress>& /* primaries */, const string& /* account */)
{
return false;
}
- //! called by PowerDNS to create a slave record for a superMaster
- virtual bool createSlaveDomain(const string& /* ip */, const DNSName& /* domain */, const string& /* nameserver */, const string& /* account */)
+ //! called by PowerDNS to create a secondary record for a autoPrimary
+ virtual bool createSecondaryDomain(const string& /* ip */, const DNSName& /* domain */, const string& /* nameserver */, const string& /* account */)
{
return false;
}
}
//! Search for records, returns true if search was done successfully.
- virtual bool searchRecords(const string& /* pattern */, int /* maxResults */, vector<DNSResourceRecord>& /* result */)
+ virtual bool searchRecords(const string& /* pattern */, size_t /* maxResults */, vector<DNSResourceRecord>& /* result */)
{
return false;
}
//! Search for comments, returns true if search was done successfully.
- virtual bool searchComments(const string& /* pattern */, int /* maxResults */, vector<Comment>& /* result */)
+ virtual bool searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
{
return false;
}
const string& getPrefix() { return d_prefix; };
+
protected:
- bool mustDo(const string &key);
- const string &getArg(const string &key);
- int getArgAsNum(const string &key);
+ bool mustDo(const string& key);
+ const string& getArg(const string& key);
+ int getArgAsNum(const string& key);
private:
string d_prefix;
class BackendFactory
{
public:
- BackendFactory(const string &name) : d_name(name) {}
- virtual ~BackendFactory(){}
- virtual DNSBackend *make(const string &suffix)=0;
- virtual DNSBackend *makeMetadataOnly(const string &suffix)
+ BackendFactory(const string& name) :
+ d_name(name) {}
+ virtual ~BackendFactory() = default;
+ virtual DNSBackend* make(const string& suffix) = 0;
+ virtual DNSBackend* makeMetadataOnly(const string& suffix)
{
return this->make(suffix);
}
[[nodiscard]] const string& getName() const;
protected:
- void declare(const string &suffix, const string ¶m, const string &explanation, const string &value);
+ void declare(const string& suffix, const string& param, const string& explanation, const string& value);
private:
- const string d_name;
+ string d_name;
};
class BackendMakerClass
{
public:
- void report(BackendFactory *bf);
- void launch(const string &instr);
- vector<DNSBackend *> all(bool skipBIND=false);
- void load(const string &module);
+ void report(BackendFactory* backendFactory);
+ void launch(const string& instr);
+ vector<std::unique_ptr<DNSBackend>> all(bool metadataOnly = false);
+ static void load(const string& module);
[[nodiscard]] size_t numLauncheable() const;
vector<string> getModules();
void clear();
private:
- void load_all();
- using d_repository_t = map<string, BackendFactory *>;
+ static void load_all();
+ using d_repository_t = map<string, BackendFactory*>;
d_repository_t d_repository;
- vector<pair<string,string> >d_instances;
+ vector<pair<string, string>> d_instances;
};
-extern BackendMakerClass &BackendMakers();
+extern BackendMakerClass& BackendMakers();
//! Exception that can be thrown by a DNSBackend to indicate a failure
class DBException : public PDNSException
{
public:
- DBException(const string &reason_) : PDNSException(reason_){}
+ DBException(const string& reason_) :
+ PDNSException(reason_) {}
};
-
struct SOAData
{
- SOAData() : domain_id(-1) {};
+ SOAData() :
+ domain_id(-1){};
DNSName qname;
DNSName nameserver;
- DNSName hostmaster;
+ DNSName rname;
uint32_t ttl{};
uint32_t serial{};
uint32_t refresh{};
uint32_t retry{};
uint32_t expire{};
uint32_t minimum{};
- DNSBackend *db{};
+ DNSBackend* db{};
int domain_id{};
[[nodiscard]] uint32_t getNegativeTTL() const { return min(ttl, minimum); }
};
/** helper function for both DNSPacket and addSOARecord() - converts a line into a struct, for easier parsing */
-void fillSOAData(const string &content, SOAData &data);
+void fillSOAData(const string& content, SOAData& soaData);
// same but more karmic
-void fillSOAData(const DNSZoneRecord& in, SOAData& data);
+void fillSOAData(const DNSZoneRecord& inZoneRecord, SOAData& soaData);
// the reverse
-std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& sd);
+std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& soaData);
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#if __clang_major__ >= 15
+#pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
+#endif
#include <boost/accumulators/accumulators.hpp>
#include <boost/array.hpp>
#include <boost/accumulators/statistics.hpp>
+#pragma GCC diagnostic pop
#include <boost/program_options.hpp>
#include "inflighter.cc"
#include <deque>
struct DNSResult
{
vector<ComboAddress> ips;
- int rcode;
+ int rcode{0};
bool seenauthsoa{false};
};
struct SendReceive
{
- typedef int Identifier;
- typedef DNSResult Answer; // ip
- int d_socket;
+ using Identifier = int;
+ using Answer = DNSResult; // ip
+ Socket d_socket;
std::deque<uint16_t> d_idqueue;
-
- typedef accumulator_set<
+
+ using acc_t = accumulator_set<
double
, stats<boost::accumulators::tag::extended_p_square,
boost::accumulators::tag::median(with_p_square_quantile),
boost::accumulators::tag::mean(immediate)
>
- > acc_t;
+ >;
unique_ptr<acc_t> d_acc;
-
- boost::array<double, 11> d_probs;
-
- SendReceive(const std::string& remoteAddr, uint16_t port) : d_probs({{0.001,0.01, 0.025, 0.1, 0.25,0.5,0.75,0.9,0.975, 0.99,0.9999}})
+
+ static constexpr std::array<double, 11> s_probs{{0.001,0.01, 0.025, 0.1, 0.25,0.5,0.75,0.9,0.975, 0.99,0.9999}};
+ unsigned int d_errors{0};
+ unsigned int d_nxdomains{0};
+ unsigned int d_nodatas{0};
+ unsigned int d_oks{0};
+ unsigned int d_unknowns{0};
+ unsigned int d_received{0};
+ unsigned int d_receiveerrors{0};
+ unsigned int d_senderrors{0};
+
+ SendReceive(const std::string& remoteAddr, uint16_t port) :
+ d_socket(AF_INET, SOCK_DGRAM),
+ d_acc(make_unique<acc_t>(acc_t(boost::accumulators::tag::extended_p_square::probabilities=s_probs)))
{
- d_acc = make_unique<acc_t>(acc_t(boost::accumulators::tag::extended_p_square::probabilities=d_probs));
- //
- //d_acc = acc_t
- d_socket = socket(AF_INET, SOCK_DGRAM, 0);
- int val=1;
- setsockopt(d_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
-
+ d_socket.setReuseAddr();
ComboAddress remote(remoteAddr, port);
- connect(d_socket, (struct sockaddr*)&remote, remote.getSocklen());
- d_oks = d_errors = d_nodatas = d_nxdomains = d_unknowns = 0;
- d_received = d_receiveerrors = d_senderrors = 0;
- for(unsigned int id =0 ; id < std::numeric_limits<uint16_t>::max(); ++id)
+ d_socket.connect(remote);
+ for (unsigned int id =0 ; id < std::numeric_limits<uint16_t>::max(); ++id) {
d_idqueue.push_back(id);
+ }
}
-
- ~SendReceive()
- {
- close(d_socket);
- }
-
+
Identifier send(TypedQuery& domain)
{
//cerr<<"Sending query for '"<<domain<<"'"<<endl;
-
+
// send it, copy code from 'sdig'
vector<uint8_t> packet;
-
+
DNSPacketWriter pw(packet, domain.name, domain.type);
- if(d_idqueue.empty()) {
+ if (d_idqueue.empty()) {
cerr<<"Exhausted ids!"<<endl;
exit(1);
- }
+ }
pw.getHeader()->id = d_idqueue.front();
d_idqueue.pop_front();
pw.getHeader()->rd = 1;
pw.getHeader()->qr = 0;
-
- if(::send(d_socket, &*packet.begin(), packet.size(), 0) < 0)
+
+ if (::send(d_socket.getHandle(), &*packet.begin(), packet.size(), 0) < 0) {
d_senderrors++;
-
- if(!g_quiet)
+ }
+
+ if (!g_quiet) {
cout<<"Sent out query for '"<<domain.name<<"' with id "<<pw.getHeader()->id<<endl;
+ }
return pw.getHeader()->id;
}
-
- bool receive(Identifier& id, DNSResult& dr)
+
+ bool receive(Identifier& iden, DNSResult& dnsResult)
{
- if(waitForData(d_socket, 0, 500000) > 0) {
- char buf[512];
-
- int len = recv(d_socket, buf, sizeof(buf), 0);
- if(len < 0) {
+ if (waitForData(d_socket.getHandle(), 0, 500000) > 0) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): no need to initialize the buffer
+ std::array<char, 512> buf;
+
+ auto len = recv(d_socket.getHandle(), buf.data(), buf.size(), 0);
+ if (len < 0) {
d_receiveerrors++;
- return 0;
- }
- else {
- d_received++;
+ return false;
}
- // parse packet, set 'id', fill out 'ip'
-
- MOADNSParser mdp(false, string(buf, len));
- if(!g_quiet) {
- cout<<"Reply to question for qname='"<<mdp.d_qname<<"', qtype="<<DNSRecordContent::NumberToType(mdp.d_qtype)<<endl;
- cout<<"Rcode: "<<mdp.d_header.rcode<<", RD: "<<mdp.d_header.rd<<", QR: "<<mdp.d_header.qr;
- cout<<", TC: "<<mdp.d_header.tc<<", AA: "<<mdp.d_header.aa<<", opcode: "<<mdp.d_header.opcode<<endl;
+ d_received++;
+ // parse packet, set 'id', fill out 'ip'
+
+ MOADNSParser mdp(false, string(buf.data(), static_cast<size_t>(len)));
+ if (!g_quiet) {
+ cout << "Reply to question for qname='" << mdp.d_qname << "', qtype=" << DNSRecordContent::NumberToType(mdp.d_qtype) << endl;
+ cout << "Rcode: " << mdp.d_header.rcode << ", RD: " << mdp.d_header.rd << ", QR: " << mdp.d_header.qr;
+ cout << ", TC: " << mdp.d_header.tc << ", AA: " << mdp.d_header.aa << ", opcode: " << mdp.d_header.opcode << endl;
}
- dr.rcode = mdp.d_header.rcode;
- for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {
- if(i->first.d_place == 1 && i->first.d_type == mdp.d_qtype)
- dr.ips.push_back(ComboAddress(i->first.getContent()->getZoneRepresentation()));
- if(i->first.d_place == 2 && i->first.d_type == QType::SOA) {
- dr.seenauthsoa = true;
+ dnsResult.rcode = mdp.d_header.rcode;
+ for (auto i = mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) {
+ if (i->first.d_place == 1 && i->first.d_type == mdp.d_qtype) {
+ dnsResult.ips.emplace_back(i->first.getContent()->getZoneRepresentation());
}
- if(!g_quiet)
- {
- cout<<i->first.d_place-1<<"\t"<<i->first.d_name<<"\tIN\t"<<DNSRecordContent::NumberToType(i->first.d_type);
- cout<<"\t"<<i->first.d_ttl<<"\t"<< i->first.getContent()->getZoneRepresentation()<<"\n";
+ if (i->first.d_place == 2 && i->first.d_type == QType::SOA) {
+ dnsResult.seenauthsoa = true;
+ }
+ if (!g_quiet) {
+ cout << i->first.d_place - 1 << "\t" << i->first.d_name << "\tIN\t" << DNSRecordContent::NumberToType(i->first.d_type);
+ cout << "\t" << i->first.d_ttl << "\t" << i->first.getContent()->getZoneRepresentation() << "\n";
}
}
-
- id = mdp.d_header.id;
- d_idqueue.push_back(id);
-
- return 1;
+
+ iden = mdp.d_header.id;
+ d_idqueue.push_back(iden);
+
+ return true;
}
- return 0;
+
+ return false;
}
-
+
void deliverTimeout(const Identifier& id)
{
if(!g_quiet) {
}
d_idqueue.push_back(id);
}
-
- void deliverAnswer(TypedQuery& domain, const DNSResult& dr, unsigned int usec)
+
+ void deliverAnswer(TypedQuery& domain, const DNSResult& dnsResult, unsigned int usec)
{
- (*d_acc)(usec/1000.0);
-// if(usec > 1000000)
- // cerr<<"Slow: "<<domain<<" ("<<usec/1000.0<<" ms)\n";
- if(!g_quiet) {
- cout<<domain.name<<"|"<<DNSRecordContent::NumberToType(domain.type)<<": ("<<usec/1000.0<<" ms) rcode: "<<dr.rcode;
- for(const ComboAddress& ca : dr.ips) {
- cout<<", "<<ca.toString();
+ (*d_acc)(usec / 1000.0);
+ // if(usec > 1000000)
+ // cerr<<"Slow: "<<domain<<" ("<<usec/1000.0<<" ms)\n";
+ if (!g_quiet) {
+ cout << domain.name << "|" << DNSRecordContent::NumberToType(domain.type) << ": (" << usec / 1000.0 << " ms) rcode: " << dnsResult.rcode;
+ for (const ComboAddress& comboAddress : dnsResult.ips) {
+ cout << ", " << comboAddress.toString();
}
- cout<<endl;
+ cout << endl;
}
- if(dr.rcode == RCode::NXDomain) {
+ if (dnsResult.rcode == RCode::NXDomain) {
d_nxdomains++;
}
- else if(dr.rcode) {
+ else if (dnsResult.rcode != 0) {
d_errors++;
}
- else if(dr.ips.empty() && dr.seenauthsoa)
+ else if (dnsResult.ips.empty() && dnsResult.seenauthsoa) {
d_nodatas++;
- else if(!dr.ips.empty())
+ }
+ else if (!dnsResult.ips.empty()) {
d_oks++;
+ }
else {
- if(!g_quiet) cout<<"UNKNOWN!! ^^"<<endl;
+ if (!g_quiet) {
+ cout << "UNKNOWN!! ^^" << endl;
+ }
d_unknowns++;
}
}
- unsigned int d_errors, d_nxdomains, d_nodatas, d_oks, d_unknowns;
- unsigned int d_received, d_receiveerrors, d_senderrors;
};
static void usage(po::options_description &desc) {
SendReceive sr(g_vm["ip-address"].as<string>(), g_vm["portnumber"].as<uint16_t>());
unsigned int limit = g_vm["limit"].as<unsigned int>();
-
+
vector<TypedQuery> domains;
-
+
Inflighter<vector<TypedQuery>, SendReceive> inflighter(domains, sr);
inflighter.d_maxInFlight = 1000;
inflighter.d_timeoutSeconds = 3;
inflighter.d_burst = 100;
string line;
-
+
pair<string, string> split;
string::size_type pos;
while(stringfgets(stdin, line)) {
cerr<< datafmt % " Queued " % domains.size() % " Received" % sr.d_received;
cerr<< datafmt % " Error -/-" % sr.d_senderrors % " Timeouts" % inflighter.getTimeouts();
cerr<< datafmt % " " % "" % " Unexpected" % inflighter.getUnexpecteds();
-
+
cerr<< datafmt % " Sent" % (domains.size() - sr.d_senderrors) % " Total" % (sr.d_received + inflighter.getTimeouts() + inflighter.getUnexpecteds());
-
- cerr<<endl;
+
+ cerr<<endl;
cerr<< datafmt % "DNS Status" % "" % "" % "";
cerr<< datafmt % " OK" % sr.d_oks % "" % "";
- cerr<< datafmt % " Error" % sr.d_errors % "" % "";
- cerr<< datafmt % " No Data" % sr.d_nodatas % "" % "";
+ cerr<< datafmt % " Error" % sr.d_errors % "" % "";
+ cerr<< datafmt % " No Data" % sr.d_nodatas % "" % "";
cerr<< datafmt % " NXDOMAIN" % sr.d_nxdomains % "" % "";
- cerr<< datafmt % " Unknowns" % sr.d_unknowns % "" % "";
+ cerr<< datafmt % " Unknowns" % sr.d_unknowns % "" % "";
cerr<< datafmt % "Answers" % (sr.d_oks + sr.d_errors + sr.d_nodatas + sr.d_nxdomains + sr.d_unknowns) % "" % "";
cerr<< datafmt % " Timeouts " % (inflighter.getTimeouts()) % "" % "";
cerr<< datafmt % "Total " % (sr.d_oks + sr.d_errors + sr.d_nodatas + sr.d_nxdomains + sr.d_unknowns + inflighter.getTimeouts()) % "" % "";
-
+
cerr<<"\n";
cerr<< "Mean response time: "<<mean(*sr.d_acc) << " ms"<<", median: "<<median(*sr.d_acc)<< " ms\n";
-
+
boost::format statfmt("Time < %6.03f ms %|30t|%6.03f%% cumulative\n");
-
- for (unsigned int i = 0; i < sr.d_probs.size(); ++i) {
- cerr << statfmt % extended_p_square(*sr.d_acc)[i] % (100*sr.d_probs[i]);
- }
- if(g_envoutput) {
+ for (unsigned int i = 0; i < SendReceive::s_probs.size(); ++i) {
+ cerr << statfmt % extended_p_square(*sr.d_acc)[i] % (100*SendReceive::s_probs.at(i));
+ }
+
+ if (g_envoutput) {
cout<<"DBT_QUEUED="<<domains.size()<<endl;
cout<<"DBT_SENDERRORS="<<sr.d_senderrors<<endl;
cout<<"DBT_RECEIVED="<<sr.d_received<<endl;
cout<<"DBT_OKPERCENTAGEINT="<<(int)((float)sr.d_oks/domains.size()*100)<<endl;
}
}
-catch(PDNSException& pe)
+catch (const PDNSException& exp)
{
- cerr<<"Fatal error: "<<pe.reason<<endl;
- exit(EXIT_FAILURE);
+ cerr<<"Fatal error: "<<exp.reason<<endl;
+ _exit(EXIT_FAILURE);
+}
+catch (const std::exception& exp) {
+ cerr<<"Fatal error: "<<exp.what()<<endl;
+ _exit(EXIT_FAILURE);
}
return false;
}
- const struct dnsheader * dh = reinterpret_cast<const struct dnsheader *>(packet.data());
- if (dh->qr || ntohs(dh->qdcount) != 1 || dh->ancount != 0 || dh->nscount != 0 || dh->opcode != Opcode::Query)
+ const dnsheader_aligned dh(packet.data());
+ if (dh->qr || ntohs(dh->qdcount) != 1 || dh->ancount != 0 || dh->nscount != 0 || static_cast<uint8_t>(dh->opcode) != Opcode::Query) {
return false;
+ }
unsigned int qnameWireLength;
uint16_t qtype, qclass;
return false;
}
- d_qname = qname;
+ d_qname = std::move(qname);
d_id = dh->id;
d_valid = true;
*/
#pragma once
#include "config.h"
+#include <memory>
#ifndef HAVE_DNSCRYPT
#else /* HAVE_DNSCRYPT */
-#include <memory>
#include <string>
#include <vector>
#include <arpa/inet.h>
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include <cinttypes>
-
-#include "dnsdist.hh"
-#include "dolog.hh"
-#include "dnsparser.hh"
-#include "dnsdist-cache.hh"
-#include "dnsdist-ecs.hh"
-#include "ednssubnet.hh"
-#include "packetcache.hh"
-
-DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t maxNegativeTTL, uint32_t staleTTL, bool dontAge, uint32_t shards, bool deferrableInsertLock, bool parseECS): d_maxEntries(maxEntries), d_shardCount(shards), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_maxNegativeTTL(maxNegativeTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge), d_deferrableInsertLock(deferrableInsertLock), d_parseECS(parseECS)
-{
- if (d_maxEntries == 0) {
- throw std::runtime_error("Trying to create a 0-sized packet-cache");
- }
-
- d_shards.resize(d_shardCount);
-
- /* we reserve maxEntries + 1 to avoid rehashing from occurring
- when we get to maxEntries, as it means a load factor of 1 */
- for (auto& shard : d_shards) {
- shard.setSize((maxEntries / d_shardCount) + 1);
- }
-}
-
-bool DNSDistPacketCache::getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet)
-{
- uint16_t optRDPosition;
- size_t remaining = 0;
-
- int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining);
-
- if (res == 0) {
- size_t ecsOptionStartPosition = 0;
- size_t ecsOptionSize = 0;
-
- res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize);
-
- if (res == 0 && ecsOptionSize > (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
-
- EDNSSubnetOpts eso;
- if (getEDNSSubnetOptsFromString(reinterpret_cast<const char*>(&packet.at(optRDPosition + ecsOptionStartPosition + (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))), ecsOptionSize - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), &eso) == true) {
- subnet = eso.source;
- return true;
- }
- }
- }
-
- return false;
-}
-
-bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const
-{
- if (cachedValue.queryFlags != queryFlags || cachedValue.dnssecOK != dnssecOK || cachedValue.receivedOverUDP != receivedOverUDP || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) {
- return false;
- }
-
- if (d_parseECS && cachedValue.subnet != subnet) {
- return false;
- }
-
- return true;
-}
-
-void DNSDistPacketCache::insertLocked(CacheShard& shard, std::unordered_map<uint32_t,CacheValue>& map, uint32_t key, CacheValue& newValue)
-{
- /* check again now that we hold the lock to prevent a race */
- if (map.size() >= (d_maxEntries / d_shardCount)) {
- return;
- }
-
- std::unordered_map<uint32_t,CacheValue>::iterator it;
- bool result;
- std::tie(it, result) = map.insert({key, newValue});
-
- if (result) {
- ++shard.d_entriesCount;
- return;
- }
-
- /* in case of collision, don't override the existing entry
- except if it has expired */
- CacheValue& value = it->second;
- bool wasExpired = value.validity <= newValue.added;
-
- if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.receivedOverUDP, newValue.dnssecOK, newValue.subnet)) {
- ++d_insertCollisions;
- return;
- }
-
- /* if the existing entry had a longer TTD, keep it */
- if (newValue.validity <= value.validity) {
- return;
- }
-
- value = newValue;
-}
-
-void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL)
-{
- if (response.size() < sizeof(dnsheader)) {
- return;
- }
- if (qtype == QType::AXFR || qtype == QType::IXFR) {
- return;
- }
-
- uint32_t minTTL;
-
- if (rcode == RCode::ServFail || rcode == RCode::Refused) {
- minTTL = tempFailureTTL == boost::none ? d_tempFailureTTL : *tempFailureTTL;
- if (minTTL == 0) {
- return;
- }
- }
- else {
- bool seenAuthSOA = false;
- minTTL = getMinTTL(reinterpret_cast<const char*>(response.data()), response.size(), &seenAuthSOA);
-
- /* no TTL found, we don't want to cache this */
- if (minTTL == std::numeric_limits<uint32_t>::max()) {
- return;
- }
-
- if (rcode == RCode::NXDomain || (rcode == RCode::NoError && seenAuthSOA)) {
- minTTL = std::min(minTTL, d_maxNegativeTTL);
- }
- else if (minTTL > d_maxTTL) {
- minTTL = d_maxTTL;
- }
-
- if (minTTL < d_minTTL) {
- ++d_ttlTooShorts;
- return;
- }
- }
-
- uint32_t shardIndex = getShardIndex(key);
-
- if (d_shards.at(shardIndex).d_entriesCount >= (d_maxEntries / d_shardCount)) {
- return;
- }
-
- const time_t now = time(nullptr);
- time_t newValidity = now + minTTL;
- CacheValue newValue;
- newValue.qname = qname;
- newValue.qtype = qtype;
- newValue.qclass = qclass;
- newValue.queryFlags = queryFlags;
- newValue.len = response.size();
- newValue.validity = newValidity;
- newValue.added = now;
- newValue.receivedOverUDP = receivedOverUDP;
- newValue.dnssecOK = dnssecOK;
- newValue.value = std::string(response.begin(), response.end());
- newValue.subnet = subnet;
-
- auto& shard = d_shards.at(shardIndex);
-
- if (d_deferrableInsertLock) {
- auto w = shard.d_map.try_write_lock();
-
- if (!w.owns_lock()) {
- ++d_deferredInserts;
- return;
- }
- insertLocked(shard, *w, key, newValue);
- }
- else {
- auto w = shard.d_map.write_lock();
-
- insertLocked(shard, *w, key, newValue);
- }
-}
-
-bool DNSDistPacketCache::get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired, bool skipAging, bool truncatedOK, bool recordMiss)
-{
- if (dq.ids.qtype == QType::AXFR || dq.ids.qtype == QType::IXFR) {
- ++d_misses;
- return false;
- }
-
- const auto& dnsQName = dq.ids.qname.getStorage();
- uint32_t key = getKey(dnsQName, dq.ids.qname.wirelength(), dq.getData(), receivedOverUDP);
-
- if (keyOut) {
- *keyOut = key;
- }
-
- if (d_parseECS) {
- getClientSubnet(dq.getData(), dq.ids.qname.wirelength(), subnet);
- }
-
- uint32_t shardIndex = getShardIndex(key);
- time_t now = time(nullptr);
- time_t age;
- bool stale = false;
- auto& response = dq.getMutableData();
- auto& shard = d_shards.at(shardIndex);
- {
- auto map = shard.d_map.try_read_lock();
- if (!map.owns_lock()) {
- ++d_deferredLookups;
- return false;
- }
-
- std::unordered_map<uint32_t,CacheValue>::const_iterator it = map->find(key);
- if (it == map->end()) {
- if (recordMiss) {
- ++d_misses;
- }
- return false;
- }
-
- const CacheValue& value = it->second;
- if (value.validity <= now) {
- if ((now - value.validity) >= static_cast<time_t>(allowExpired)) {
- if (recordMiss) {
- ++d_misses;
- }
- return false;
- }
- else {
- stale = true;
- }
- }
-
- if (value.len < sizeof(dnsheader)) {
- return false;
- }
-
- /* check for collision */
- if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.getHeader())), dq.ids.qname, dq.ids.qtype, dq.ids.qclass, receivedOverUDP, dnssecOK, subnet)) {
- ++d_lookupCollisions;
- return false;
- }
-
- if (!truncatedOK) {
- dnsheader dh;
- memcpy(&dh, value.value.data(), sizeof(dh));
- if (dh.tc != 0) {
- return false;
- }
- }
-
- response.resize(value.len);
- memcpy(&response.at(0), &queryId, sizeof(queryId));
- memcpy(&response.at(sizeof(queryId)), &value.value.at(sizeof(queryId)), sizeof(dnsheader) - sizeof(queryId));
-
- if (value.len == sizeof(dnsheader)) {
- /* DNS header only, our work here is done */
- ++d_hits;
- return true;
- }
-
- const size_t dnsQNameLen = dnsQName.length();
- if (value.len < (sizeof(dnsheader) + dnsQNameLen)) {
- return false;
- }
-
- memcpy(&response.at(sizeof(dnsheader)), dnsQName.c_str(), dnsQNameLen);
- if (value.len > (sizeof(dnsheader) + dnsQNameLen)) {
- memcpy(&response.at(sizeof(dnsheader) + dnsQNameLen), &value.value.at(sizeof(dnsheader) + dnsQNameLen), value.len - (sizeof(dnsheader) + dnsQNameLen));
- }
-
- if (!stale) {
- age = now - value.added;
- }
- else {
- age = (value.validity - value.added) - d_staleTTL;
- }
- }
-
- if (!d_dontAge && !skipAging) {
- if (!stale) {
- // coverity[store_truncates_time_t]
- dnsheader_aligned dh_aligned(response.data());
- ageDNSPacket(reinterpret_cast<char *>(&response[0]), response.size(), age, dh_aligned);
- }
- else {
- editDNSPacketTTL(reinterpret_cast<char*>(&response[0]), response.size(),
- [staleTTL = d_staleTTL](uint8_t /* section */, uint16_t /* class_ */, uint16_t /* type */, uint32_t /* ttl */) { return staleTTL; });
- }
- }
-
- ++d_hits;
- return true;
-}
-
-/* Remove expired entries, until the cache has at most
- upTo entries in it.
- If the cache has more than one shard, we will try hard
- to make sure that every shard has free space remaining.
-*/
-size_t DNSDistPacketCache::purgeExpired(size_t upTo, const time_t now)
-{
- const size_t maxPerShard = upTo / d_shardCount;
-
- size_t removed = 0;
-
- ++d_cleanupCount;
- for (auto& shard : d_shards) {
- auto map = shard.d_map.write_lock();
- if (map->size() <= maxPerShard) {
- continue;
- }
-
- size_t toRemove = map->size() - maxPerShard;
-
- for (auto it = map->begin(); toRemove > 0 && it != map->end(); ) {
- const CacheValue& value = it->second;
-
- if (value.validity <= now) {
- it = map->erase(it);
- --toRemove;
- --shard.d_entriesCount;
- ++removed;
- } else {
- ++it;
- }
- }
- }
-
- return removed;
-}
-
-/* Remove all entries, keeping only upTo
- entries in the cache.
- If the cache has more than one shard, we will try hard
- to make sure that every shard has free space remaining.
-*/
-size_t DNSDistPacketCache::expunge(size_t upTo)
-{
- const size_t maxPerShard = upTo / d_shardCount;
-
- size_t removed = 0;
-
- for (auto& shard : d_shards) {
- auto map = shard.d_map.write_lock();
-
- if (map->size() <= maxPerShard) {
- continue;
- }
-
- size_t toRemove = map->size() - maxPerShard;
-
- auto beginIt = map->begin();
- auto endIt = beginIt;
-
- if (map->size() >= toRemove) {
- std::advance(endIt, toRemove);
- map->erase(beginIt, endIt);
- shard.d_entriesCount -= toRemove;
- removed += toRemove;
- }
- else {
- removed += map->size();
- map->clear();
- shard.d_entriesCount = 0;
- }
- }
-
- return removed;
-}
-
-size_t DNSDistPacketCache::expungeByName(const DNSName& name, uint16_t qtype, bool suffixMatch)
-{
- size_t removed = 0;
-
- for (auto& shard : d_shards) {
- auto map = shard.d_map.write_lock();
-
- for(auto it = map->begin(); it != map->end(); ) {
- const CacheValue& value = it->second;
-
- if ((value.qname == name || (suffixMatch && value.qname.isPartOf(name))) && (qtype == QType::ANY || qtype == value.qtype)) {
- it = map->erase(it);
- --shard.d_entriesCount;
- ++removed;
- } else {
- ++it;
- }
- }
- }
-
- return removed;
-}
-
-bool DNSDistPacketCache::isFull()
-{
- return (getSize() >= d_maxEntries);
-}
-
-uint64_t DNSDistPacketCache::getSize()
-{
- uint64_t count = 0;
-
- for (auto& shard : d_shards) {
- count += shard.d_entriesCount;
- }
-
- return count;
-}
-
-uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA)
-{
- return getDNSPacketMinTTL(packet, length, seenNoDataSOA);
-}
-
-uint32_t DNSDistPacketCache::getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP)
-{
- uint32_t result = 0;
- /* skip the query ID */
- if (packet.size() < sizeof(dnsheader)) {
- throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packet.size()) +")");
- }
-
- result = burtle(&packet.at(2), sizeof(dnsheader) - 2, result);
- result = burtleCI((const unsigned char*) qname.c_str(), qname.length(), result);
- if (packet.size() < sizeof(dnsheader) + qnameWireLength) {
- throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packet.size()) + " < " + std::to_string(sizeof(dnsheader) + qnameWireLength) + ")");
- }
- if (packet.size() > ((sizeof(dnsheader) + qnameWireLength))) {
- if (!d_optionsToSkip.empty()) {
- /* skip EDNS options if any */
- result = PacketCache::hashAfterQname(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_optionsToSkip);
- }
- else {
- result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), packet.size() - (sizeof(dnsheader) + qnameWireLength), result);
- }
- }
- result = burtle((const unsigned char*) &receivedOverUDP, sizeof(receivedOverUDP), result);
- return result;
-}
-
-uint32_t DNSDistPacketCache::getShardIndex(uint32_t key) const
-{
- return key % d_shardCount;
-}
-
-string DNSDistPacketCache::toString()
-{
- return std::to_string(getSize()) + "/" + std::to_string(d_maxEntries);
-}
-
-uint64_t DNSDistPacketCache::getEntriesCount()
-{
- return getSize();
-}
-
-uint64_t DNSDistPacketCache::dump(int fd)
-{
- auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
- if (fp == nullptr) {
- return 0;
- }
-
- fprintf(fp.get(), "; dnsdist's packet cache dump follows\n;\n");
-
- uint64_t count = 0;
- time_t now = time(nullptr);
- for (auto& shard : d_shards) {
- auto map = shard.d_map.read_lock();
-
- for (const auto& entry : *map) {
- const CacheValue& value = entry.second;
- count++;
-
- try {
- uint8_t rcode = 0;
- if (value.len >= sizeof(dnsheader)) {
- dnsheader dh;
- memcpy(&dh, value.value.data(), sizeof(dnsheader));
- rcode = dh.rcode;
- }
-
- fprintf(fp.get(), "%s %" PRId64 " %s ; rcode %" PRIu8 ", key %" PRIu32 ", length %" PRIu16 ", received over UDP %d, added %" PRId64 "\n", value.qname.toString().c_str(), static_cast<int64_t>(value.validity - now), QType(value.qtype).toString().c_str(), rcode, entry.first, value.len, value.receivedOverUDP, static_cast<int64_t>(value.added));
- }
- catch(...) {
- fprintf(fp.get(), "; error printing '%s'\n", value.qname.empty() ? "EMPTY" : value.qname.toString().c_str());
- }
- }
- }
-
- return count;
-}
-
-void DNSDistPacketCache::setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip)
-{
- d_optionsToSkip = optionsToSkip;
-}
-
-std::set<DNSName> DNSDistPacketCache::getDomainsContainingRecords(const ComboAddress& addr)
-{
- std::set<DNSName> domains;
-
- for (auto& shard : d_shards) {
- auto map = shard.d_map.read_lock();
-
- for (const auto& entry : *map) {
- const CacheValue& value = entry.second;
-
- try {
- dnsheader dh;
- if (value.len < sizeof(dnsheader)) {
- continue;
- }
-
- memcpy(&dh, value.value.data(), sizeof(dnsheader));
- if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) {
- continue;
- }
-
- bool found = false;
- bool valid = visitDNSPacket(value.value, [addr, &found](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) {
- if (qtype == QType::A && qclass == QClass::IN && addr.isIPv4() && rdatalength == 4 && rdata != nullptr) {
- ComboAddress parsed;
- parsed.sin4.sin_family = AF_INET;
- memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
- if (parsed == addr) {
- found = true;
- return true;
- }
- }
- else if (qtype == QType::AAAA && qclass == QClass::IN && addr.isIPv6() && rdatalength == 16 && rdata != nullptr) {
- ComboAddress parsed;
- parsed.sin6.sin6_family = AF_INET6;
- memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
- if (parsed == addr) {
- found = true;
- return true;
- }
- }
-
- return false;
- });
-
- if (valid && found) {
- domains.insert(value.qname);
- }
- }
- catch (...) {
- continue;
- }
- }
- }
-
- return domains;
-}
-
-std::set<ComboAddress> DNSDistPacketCache::getRecordsForDomain(const DNSName& domain)
-{
- std::set<ComboAddress> addresses;
-
- for (auto& shard : d_shards) {
- auto map = shard.d_map.read_lock();
-
- for (const auto& entry : *map) {
- const CacheValue& value = entry.second;
-
- try {
- if (value.qname != domain) {
- continue;
- }
-
- dnsheader dh;
- if (value.len < sizeof(dnsheader)) {
- continue;
- }
-
- memcpy(&dh, value.value.data(), sizeof(dnsheader));
- if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) {
- continue;
- }
-
- visitDNSPacket(value.value, [&addresses](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) {
- if (qtype == QType::A && qclass == QClass::IN && rdatalength == 4 && rdata != nullptr) {
- ComboAddress parsed;
- parsed.sin4.sin_family = AF_INET;
- memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
- addresses.insert(parsed);
- }
- else if (qtype == QType::AAAA && qclass == QClass::IN && rdatalength == 16 && rdata != nullptr) {
- ComboAddress parsed;
- parsed.sin6.sin6_family = AF_INET6;
- memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
- addresses.insert(parsed);
- }
-
- return false;
- });
- }
- catch (...) {
- continue;
- }
- }
- }
-
- return addresses;
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-#include <atomic>
-#include <unordered_map>
-
-#include "iputils.hh"
-#include "lock.hh"
-#include "noinitvector.hh"
-#include "stat_t.hh"
-#include "ednsoptions.hh"
-
-struct DNSQuestion;
-
-class DNSDistPacketCache : boost::noncopyable
-{
-public:
- DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t maxNegativeTTL=3600, uint32_t staleTTL=60, bool dontAge=false, uint32_t shards=1, bool deferrableInsertLock=true, bool parseECS=false);
-
- void insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL);
- bool get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired = 0, bool skipAging = false, bool truncatedOK = true, bool recordMiss = true);
- size_t purgeExpired(size_t upTo, const time_t now);
- size_t expunge(size_t upTo=0);
- size_t expungeByName(const DNSName& name, uint16_t qtype=QType::ANY, bool suffixMatch=false);
- bool isFull();
- string toString();
- uint64_t getSize();
- uint64_t getHits() const { return d_hits; }
- uint64_t getMisses() const { return d_misses; }
- uint64_t getDeferredLookups() const { return d_deferredLookups; }
- uint64_t getDeferredInserts() const { return d_deferredInserts; }
- uint64_t getLookupCollisions() const { return d_lookupCollisions; }
- uint64_t getInsertCollisions() const { return d_insertCollisions; }
- uint64_t getMaxEntries() const { return d_maxEntries; }
- uint64_t getTTLTooShorts() const { return d_ttlTooShorts; }
- uint64_t getCleanupCount() const { return d_cleanupCount; }
- uint64_t getEntriesCount();
- uint64_t dump(int fd);
-
- /* get the list of domains (qnames) that contains the given address in an A or AAAA record */
- std::set<DNSName> getDomainsContainingRecords(const ComboAddress& addr);
- /* get the list of IP addresses contained in A or AAAA for a given domains (qname) */
- std::set<ComboAddress> getRecordsForDomain(const DNSName& domain);
-
- void setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip);
-
- bool isECSParsingEnabled() const { return d_parseECS; }
-
- bool keepStaleData() const
- {
- return d_keepStaleData;
- }
- void setKeepStaleData(bool keep)
- {
- d_keepStaleData = keep;
- }
-
-
- void setECSParsingEnabled(bool enabled)
- {
- d_parseECS = enabled;
- }
-
- uint32_t getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP);
-
- static uint32_t getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA);
- static bool getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet);
-
-private:
-
- struct CacheValue
- {
- time_t getTTD() const { return validity; }
- std::string value;
- DNSName qname;
- boost::optional<Netmask> subnet;
- uint16_t qtype{0};
- uint16_t qclass{0};
- uint16_t queryFlags{0};
- time_t added{0};
- time_t validity{0};
- uint16_t len{0};
- bool receivedOverUDP{false};
- bool dnssecOK{false};
- };
-
- class CacheShard
- {
- public:
- CacheShard()
- {
- }
- CacheShard(const CacheShard& /* old */)
- {
- }
-
- void setSize(size_t maxSize)
- {
- d_map.write_lock()->reserve(maxSize);
- }
-
- SharedLockGuarded<std::unordered_map<uint32_t,CacheValue>> d_map;
- std::atomic<uint64_t> d_entriesCount{0};
- };
-
- bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const;
- uint32_t getShardIndex(uint32_t key) const;
- void insertLocked(CacheShard& shard, std::unordered_map<uint32_t,CacheValue>& map, uint32_t key, CacheValue& newValue);
-
- std::vector<CacheShard> d_shards;
- std::unordered_set<uint16_t> d_optionsToSkip{EDNSOptionCode::COOKIE};
-
- pdns::stat_t d_deferredLookups{0};
- pdns::stat_t d_deferredInserts{0};
- pdns::stat_t d_hits{0};
- pdns::stat_t d_misses{0};
- pdns::stat_t d_insertCollisions{0};
- pdns::stat_t d_lookupCollisions{0};
- pdns::stat_t d_ttlTooShorts{0};
- pdns::stat_t d_cleanupCount{0};
-
- size_t d_maxEntries;
- uint32_t d_shardCount;
- uint32_t d_maxTTL;
- uint32_t d_tempFailureTTL;
- uint32_t d_maxNegativeTTL;
- uint32_t d_minTTL;
- uint32_t d_staleTTL;
- bool d_dontAge;
- bool d_deferrableInsertLock;
- bool d_parseECS;
- bool d_keepStaleData{false};
-};
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "dnsdist-carbon.hh"
-#include "dnsdist.hh"
-
-#ifndef DISABLE_CARBON
-#include "dolog.hh"
-#include "sstuff.hh"
-#include "threadname.hh"
-
-namespace dnsdist
-{
-
-LockGuarded<Carbon::Config> Carbon::s_config;
-
-static bool doOneCarbonExport(const Carbon::Endpoint& endpoint)
-{
- const auto& server = endpoint.server;
- const std::string& namespace_name = endpoint.namespace_name;
- const std::string& hostname = endpoint.ourname;
- const std::string& instance_name = endpoint.instance_name;
-
- try {
- Socket s(server.sin4.sin_family, SOCK_STREAM);
- s.setNonBlocking();
- s.connect(server); // we do the connect so the attempt happens while we gather stats
- ostringstream str;
-
- const time_t now = time(nullptr);
-
- for (const auto& e : g_stats.entries) {
- str << namespace_name << "." << hostname << "." << instance_name << "." << e.first << ' ';
- if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
- str << (*val)->load();
- }
- else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
- str << (*adval)->load();
- }
- else if (const auto& dval = boost::get<double*>(&e.second)) {
- str << **dval;
- }
- else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
- str << (*func)(e.first);
- }
- str << ' ' << now << "\r\n";
- }
-
- auto states = g_dstates.getLocal();
- for (const auto& state : *states) {
- string serverName = state->getName().empty() ? state->d_config.remote.toStringWithPort() : state->getName();
- boost::replace_all(serverName, ".", "_");
- const string base = namespace_name + "." + hostname + "." + instance_name + ".servers." + serverName + ".";
- str << base << "queries" << ' ' << state->queries.load() << " " << now << "\r\n";
- str << base << "responses" << ' ' << state->responses.load() << " " << now << "\r\n";
- str << base << "drops" << ' ' << state->reuseds.load() << " " << now << "\r\n";
- str << base << "latency" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsec / 1000.0 : 0) << " " << now << "\r\n";
- str << base << "latencytcp" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsecTCP / 1000.0 : 0) << " " << now << "\r\n";
- str << base << "senderrors" << ' ' << state->sendErrors.load() << " " << now << "\r\n";
- str << base << "outstanding" << ' ' << state->outstanding.load() << " " << now << "\r\n";
- str << base << "tcpdiedsendingquery" << ' ' << state->tcpDiedSendingQuery.load() << " " << now << "\r\n";
- str << base << "tcpdiedreaddingresponse" << ' ' << state->tcpDiedReadingResponse.load() << " " << now << "\r\n";
- str << base << "tcpgaveup" << ' ' << state->tcpGaveUp.load() << " " << now << "\r\n";
- str << base << "tcpreadimeouts" << ' ' << state->tcpReadTimeouts.load() << " " << now << "\r\n";
- str << base << "tcpwritetimeouts" << ' ' << state->tcpWriteTimeouts.load() << " " << now << "\r\n";
- str << base << "tcpconnecttimeouts" << ' ' << state->tcpConnectTimeouts.load() << " " << now << "\r\n";
- str << base << "tcpcurrentconnections" << ' ' << state->tcpCurrentConnections.load() << " " << now << "\r\n";
- str << base << "tcpmaxconcurrentconnections" << ' ' << state->tcpMaxConcurrentConnections.load() << " " << now << "\r\n";
- str << base << "tcpnewconnections" << ' ' << state->tcpNewConnections.load() << " " << now << "\r\n";
- str << base << "tcpreusedconnections" << ' ' << state->tcpReusedConnections.load() << " " << now << "\r\n";
- str << base << "tlsresumptions" << ' ' << state->tlsResumptions.load() << " " << now << "\r\n";
- str << base << "tcpavgqueriesperconnection" << ' ' << state->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n";
- str << base << "tcpavgconnectionduration" << ' ' << state->tcpAvgConnectionDuration.load() << " " << now << "\r\n";
- str << base << "tcptoomanyconcurrentconnections" << ' ' << state->tcpTooManyConcurrentConnections.load() << " " << now << "\r\n";
- }
-
- std::map<std::string, uint64_t> frontendDuplicates;
- for (const auto& front : g_frontends) {
- if (front->udpFD == -1 && front->tcpFD == -1) {
- continue;
- }
-
- string frontName = front->local.toStringWithPort() + (front->udpFD >= 0 ? "_udp" : "_tcp");
- boost::replace_all(frontName, ".", "_");
- auto dupPair = frontendDuplicates.insert({frontName, 1});
- if (!dupPair.second) {
- frontName = frontName + "_" + std::to_string(dupPair.first->second);
- ++(dupPair.first->second);
- }
-
- const string base = namespace_name + "." + hostname + "." + instance_name + ".frontends." + frontName + ".";
- str << base << "queries" << ' ' << front->queries.load() << " " << now << "\r\n";
- str << base << "responses" << ' ' << front->responses.load() << " " << now << "\r\n";
- str << base << "tcpdiedreadingquery" << ' ' << front->tcpDiedReadingQuery.load() << " " << now << "\r\n";
- str << base << "tcpdiedsendingresponse" << ' ' << front->tcpDiedSendingResponse.load() << " " << now << "\r\n";
- str << base << "tcpgaveup" << ' ' << front->tcpGaveUp.load() << " " << now << "\r\n";
- str << base << "tcpclientimeouts" << ' ' << front->tcpClientTimeouts.load() << " " << now << "\r\n";
- str << base << "tcpdownstreamtimeouts" << ' ' << front->tcpDownstreamTimeouts.load() << " " << now << "\r\n";
- str << base << "tcpcurrentconnections" << ' ' << front->tcpCurrentConnections.load() << " " << now << "\r\n";
- str << base << "tcpmaxconcurrentconnections" << ' ' << front->tcpMaxConcurrentConnections.load() << " " << now << "\r\n";
- str << base << "tcpavgqueriesperconnection" << ' ' << front->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n";
- str << base << "tcpavgconnectionduration" << ' ' << front->tcpAvgConnectionDuration.load() << " " << now << "\r\n";
- str << base << "tls10-queries" << ' ' << front->tls10queries.load() << " " << now << "\r\n";
- str << base << "tls11-queries" << ' ' << front->tls11queries.load() << " " << now << "\r\n";
- str << base << "tls12-queries" << ' ' << front->tls12queries.load() << " " << now << "\r\n";
- str << base << "tls13-queries" << ' ' << front->tls13queries.load() << " " << now << "\r\n";
- str << base << "tls-unknown-queries" << ' ' << front->tlsUnknownqueries.load() << " " << now << "\r\n";
- str << base << "tlsnewsessions" << ' ' << front->tlsNewSessions.load() << " " << now << "\r\n";
- str << base << "tlsresumptions" << ' ' << front->tlsResumptions.load() << " " << now << "\r\n";
- str << base << "tlsunknownticketkeys" << ' ' << front->tlsUnknownTicketKey.load() << " " << now << "\r\n";
- str << base << "tlsinactiveticketkeys" << ' ' << front->tlsInactiveTicketKey.load() << " " << now << "\r\n";
-
- const TLSErrorCounters* errorCounters = nullptr;
- if (front->tlsFrontend != nullptr) {
- errorCounters = &front->tlsFrontend->d_tlsCounters;
- }
- else if (front->dohFrontend != nullptr) {
- errorCounters = &front->dohFrontend->d_tlsCounters;
- }
- if (errorCounters != nullptr) {
- str << base << "tlsdhkeytoosmall" << ' ' << errorCounters->d_dhKeyTooSmall << " " << now << "\r\n";
- str << base << "tlsinappropriatefallback" << ' ' << errorCounters->d_inappropriateFallBack << " " << now << "\r\n";
- str << base << "tlsnosharedcipher" << ' ' << errorCounters->d_noSharedCipher << " " << now << "\r\n";
- str << base << "tlsunknownciphertype" << ' ' << errorCounters->d_unknownCipherType << " " << now << "\r\n";
- str << base << "tlsunknownkeyexchangetype" << ' ' << errorCounters->d_unknownKeyExchangeType << " " << now << "\r\n";
- str << base << "tlsunknownprotocol" << ' ' << errorCounters->d_unknownProtocol << " " << now << "\r\n";
- str << base << "tlsunsupportedec" << ' ' << errorCounters->d_unsupportedEC << " " << now << "\r\n";
- str << base << "tlsunsupportedprotocol" << ' ' << errorCounters->d_unsupportedProtocol << " " << now << "\r\n";
- }
- }
-
- auto localPools = g_pools.getLocal();
- for (const auto& entry : *localPools) {
- string poolName = entry.first;
- boost::replace_all(poolName, ".", "_");
- if (poolName.empty()) {
- poolName = "_default_";
- }
- const string base = namespace_name + "." + hostname + "." + instance_name + ".pools." + poolName + ".";
- const std::shared_ptr<ServerPool> pool = entry.second;
- str << base << "servers"
- << " " << pool->countServers(false) << " " << now << "\r\n";
- str << base << "servers-up"
- << " " << pool->countServers(true) << " " << now << "\r\n";
- if (pool->packetCache != nullptr) {
- const auto& cache = pool->packetCache;
- str << base << "cache-size"
- << " " << cache->getMaxEntries() << " " << now << "\r\n";
- str << base << "cache-entries"
- << " " << cache->getEntriesCount() << " " << now << "\r\n";
- str << base << "cache-hits"
- << " " << cache->getHits() << " " << now << "\r\n";
- str << base << "cache-misses"
- << " " << cache->getMisses() << " " << now << "\r\n";
- str << base << "cache-deferred-inserts"
- << " " << cache->getDeferredInserts() << " " << now << "\r\n";
- str << base << "cache-deferred-lookups"
- << " " << cache->getDeferredLookups() << " " << now << "\r\n";
- str << base << "cache-lookup-collisions"
- << " " << cache->getLookupCollisions() << " " << now << "\r\n";
- str << base << "cache-insert-collisions"
- << " " << cache->getInsertCollisions() << " " << now << "\r\n";
- str << base << "cache-ttl-too-shorts"
- << " " << cache->getTTLTooShorts() << " " << now << "\r\n";
- str << base << "cache-cleanup-count"
- << " " << cache->getCleanupCount() << " " << now << "\r\n";
- }
- }
-
-#ifdef HAVE_DNS_OVER_HTTPS
- {
- std::map<std::string, uint64_t> dohFrontendDuplicates;
- const string base = "dnsdist." + hostname + ".main.doh.";
- for (const auto& doh : g_dohlocals) {
- string name = doh->d_local.toStringWithPort();
- boost::replace_all(name, ".", "_");
- boost::replace_all(name, ":", "_");
- boost::replace_all(name, "[", "_");
- boost::replace_all(name, "]", "_");
-
- auto dupPair = dohFrontendDuplicates.insert({name, 1});
- if (!dupPair.second) {
- name = name + "_" + std::to_string(dupPair.first->second);
- ++(dupPair.first->second);
- }
-
- vector<pair<const char*, const pdns::stat_t&>> v{
- {"http-connects", doh->d_httpconnects},
- {"http1-queries", doh->d_http1Stats.d_nbQueries},
- {"http2-queries", doh->d_http2Stats.d_nbQueries},
- {"http1-200-responses", doh->d_http1Stats.d_nb200Responses},
- {"http2-200-responses", doh->d_http2Stats.d_nb200Responses},
- {"http1-400-responses", doh->d_http1Stats.d_nb400Responses},
- {"http2-400-responses", doh->d_http2Stats.d_nb400Responses},
- {"http1-403-responses", doh->d_http1Stats.d_nb403Responses},
- {"http2-403-responses", doh->d_http2Stats.d_nb403Responses},
- {"http1-500-responses", doh->d_http1Stats.d_nb500Responses},
- {"http2-500-responses", doh->d_http2Stats.d_nb500Responses},
- {"http1-502-responses", doh->d_http1Stats.d_nb502Responses},
- {"http2-502-responses", doh->d_http2Stats.d_nb502Responses},
- {"http1-other-responses", doh->d_http1Stats.d_nbOtherResponses},
- {"http2-other-responses", doh->d_http2Stats.d_nbOtherResponses},
- {"get-queries", doh->d_getqueries},
- {"post-queries", doh->d_postqueries},
- {"bad-requests", doh->d_badrequests},
- {"error-responses", doh->d_errorresponses},
- {"redirect-responses", doh->d_redirectresponses},
- {"valid-responses", doh->d_validresponses}};
-
- for (const auto& item : v) {
- str << base << name << "." << item.first << " " << item.second << " " << now << "\r\n";
- }
- }
- }
-#endif /* HAVE_DNS_OVER_HTTPS */
-
- {
- std::string qname;
- auto records = g_qcount.records.write_lock();
- for (const auto& record : *records) {
- qname = record.first;
- boost::replace_all(qname, ".", "_");
- str << "dnsdist.querycount." << qname << ".queries " << record.second << " " << now << "\r\n";
- }
- records->clear();
- }
-
- const string msg = str.str();
-
- int ret = waitForRWData(s.getHandle(), false, 1, 0);
- if (ret <= 0) {
- vinfolog("Unable to write data to carbon server on %s: %s", server.toStringWithPort(), (ret < 0 ? stringerror() : "Timeout"));
- return false;
- }
- s.setBlocking();
- writen2(s.getHandle(), msg.c_str(), msg.size());
- }
- catch (const std::exception& e) {
- warnlog("Problem sending carbon data to %s: %s", server.toStringWithPort(), e.what());
- return false;
- }
-
- return true;
-}
-
-static void carbonHandler(Carbon::Endpoint&& endpoint)
-{
- setThreadName("dnsdist/carbon");
- const auto intervalUSec = endpoint.interval * 1000 * 1000;
-
- try {
- uint8_t consecutiveFailures = 0;
- do {
- DTime dt;
- dt.set();
- if (doOneCarbonExport(endpoint)) {
- const auto elapsedUSec = dt.udiff();
- if (elapsedUSec < 0 || static_cast<unsigned int>(elapsedUSec) <= intervalUSec) {
- useconds_t toSleepUSec = intervalUSec - elapsedUSec;
- usleep(toSleepUSec);
- }
- else {
- vinfolog("Carbon export for %s took longer (%s us) than the configured interval (%d us)", endpoint.server.toStringWithPort(), elapsedUSec, intervalUSec);
- }
- consecutiveFailures = 0;
- }
- else {
- /* maximum interval between two attempts is 10 minutes */
- const time_t maxBackOff = 10 * 60;
- time_t backOff = 1;
- double backOffTmp = std::pow(2.0, static_cast<double>(consecutiveFailures));
- if (backOffTmp != HUGE_VAL && static_cast<uint64_t>(backOffTmp) <= static_cast<uint64_t>(std::numeric_limits<time_t>::max())) {
- backOff = static_cast<time_t>(backOffTmp);
- if (backOff > maxBackOff) {
- backOff = maxBackOff;
- }
- }
- if (consecutiveFailures < std::numeric_limits<decltype(consecutiveFailures)>::max()) {
- consecutiveFailures++;
- }
- vinfolog("Run for %s - %s failed, next attempt in %d", endpoint.server.toStringWithPort(), endpoint.ourname, backOff);
- sleep(backOff);
- }
- } while (true);
- }
- catch (const PDNSException& e) {
- errlog("Carbon thread for %s died, PDNSException: %s", endpoint.server.toStringWithPort(), e.reason);
- }
- catch (...) {
- errlog("Carbon thread for %s died", endpoint.server.toStringWithPort());
- }
-}
-
-bool Carbon::addEndpoint(Carbon::Endpoint&& endpoint)
-{
- if (endpoint.ourname.empty()) {
- try {
- endpoint.ourname = getCarbonHostName();
- }
- catch (const std::exception& e) {
- throw std::runtime_error(std::string("The 'ourname' setting in 'carbonServer()' has not been set and we are unable to determine the system's hostname: ") + e.what());
- }
- }
-
- auto config = s_config.lock();
- if (config->d_running) {
- // we already started the threads, let's just spawn a new one
- std::thread newHandler(carbonHandler, std::move(endpoint));
- newHandler.detach();
- }
- else {
- config->d_endpoints.push_back(std::move(endpoint));
- }
- return true;
-}
-
-void Carbon::run()
-{
- auto config = s_config.lock();
- if (config->d_running) {
- throw std::runtime_error("The carbon threads are already running");
- }
- for (auto& endpoint : config->d_endpoints) {
- std::thread newHandler(carbonHandler, std::move(endpoint));
- newHandler.detach();
- }
- config->d_endpoints.clear();
- config->d_running = true;
-}
-
-}
-#endif /* DISABLE_CARBON */
-
-static time_t s_start = time(nullptr);
-
-uint64_t uptimeOfProcess(const std::string& str)
-{
- return time(nullptr) - s_start;
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "config.h"
-
-#include <fstream>
-// we need this to get the home directory of the current user
-#include <pwd.h>
-#include <thread>
-
-#ifdef HAVE_LIBEDIT
-#if defined (__OpenBSD__) || defined(__NetBSD__)
-// If this is not undeffed, __attribute__ wil be redefined by /usr/include/readline/rlstdc.h
-#undef __STRICT_ANSI__
-#include <readline/readline.h>
-#include <readline/history.h>
-#else
-#include <editline/readline.h>
-#endif
-#endif /* HAVE_LIBEDIT */
-
-#include "ext/json11/json11.hpp"
-
-#include "connection-management.hh"
-#include "dolog.hh"
-#include "dnsdist.hh"
-#include "dnsdist-console.hh"
-#include "sodcrypto.hh"
-#include "threadname.hh"
-
-GlobalStateHolder<NetmaskGroup> g_consoleACL;
-vector<pair<struct timeval, string> > g_confDelta;
-std::string g_consoleKey;
-bool g_logConsoleConnections{true};
-bool g_consoleEnabled{false};
-uint32_t g_consoleOutputMsgMaxSize{10000000};
-
-static ConcurrentConnectionManager s_connManager(100);
-
-class ConsoleConnection
-{
-public:
- ConsoleConnection(const ComboAddress& client, FDWrapper&& fd): d_client(client), d_fd(std::move(fd))
- {
- if (!s_connManager.registerConnection()) {
- throw std::runtime_error("Too many concurrent console connections");
- }
- }
- ConsoleConnection(ConsoleConnection&& rhs): d_client(rhs.d_client), d_fd(std::move(rhs.d_fd))
- {
- }
-
- ConsoleConnection(const ConsoleConnection&) = delete;
- ConsoleConnection& operator=(const ConsoleConnection&) = delete;
-
- ~ConsoleConnection()
- {
- if (d_fd.getHandle() != -1) {
- s_connManager.releaseConnection();
- }
- }
-
- int getFD() const
- {
- return d_fd.getHandle();
- }
-
- const ComboAddress& getClient() const
- {
- return d_client;
- }
-
-private:
- ComboAddress d_client;
- FDWrapper d_fd;
-};
-
-void setConsoleMaximumConcurrentConnections(size_t max)
-{
- s_connManager.setMaxConcurrentConnections(max);
-}
-
-// MUST BE CALLED UNDER A LOCK - right now the LuaLock
-static void feedConfigDelta(const std::string& line)
-{
- if(line.empty())
- return;
- struct timeval now;
- gettimeofday(&now, 0);
- g_confDelta.emplace_back(now, line);
-}
-
-#ifdef HAVE_LIBEDIT
-static string historyFile(const bool &ignoreHOME = false)
-{
- string ret;
-
- struct passwd pwd;
- struct passwd *result;
- char buf[16384];
- getpwuid_r(geteuid(), &pwd, buf, sizeof(buf), &result);
-
- const char *homedir = getenv("HOME");
- if (result)
- ret = string(pwd.pw_dir);
- if (homedir && !ignoreHOME) // $HOME overrides what the OS tells us
- ret = string(homedir);
- if (ret.empty())
- ret = "."; // CWD if nothing works..
- ret.append("/.dnsdist_history");
- return ret;
-}
-#endif /* HAVE_LIBEDIT */
-
-enum class ConsoleCommandResult : uint8_t {
- Valid = 0,
- ConnectionClosed,
- TooLarge
-};
-
-static ConsoleCommandResult getMsgLen32(int fd, uint32_t* len)
-{
- try {
- uint32_t raw;
- size_t ret = readn2(fd, &raw, sizeof(raw));
-
- if (ret != sizeof raw) {
- return ConsoleCommandResult::ConnectionClosed;
- }
-
- *len = ntohl(raw);
- if (*len > g_consoleOutputMsgMaxSize) {
- return ConsoleCommandResult::TooLarge;
- }
-
- return ConsoleCommandResult::Valid;
- }
- catch (...) {
- return ConsoleCommandResult::ConnectionClosed;
- }
-}
-
-static bool putMsgLen32(int fd, uint32_t len)
-{
- try
- {
- uint32_t raw = htonl(len);
- size_t ret = writen2(fd, &raw, sizeof raw);
- return ret == sizeof raw;
- }
- catch(...) {
- return false;
- }
-}
-
-static ConsoleCommandResult sendMessageToServer(int fd, const std::string& line, SodiumNonce& readingNonce, SodiumNonce& writingNonce, const bool outputEmptyLine)
-{
- string msg = sodEncryptSym(line, g_consoleKey, writingNonce);
- const auto msgLen = msg.length();
- if (msgLen > std::numeric_limits<uint32_t>::max()) {
- cerr << "Encrypted message is too long to be sent to the server, "<< std::to_string(msgLen) << " > " << std::numeric_limits<uint32_t>::max() << endl;
- return ConsoleCommandResult::TooLarge;
- }
-
- putMsgLen32(fd, static_cast<uint32_t>(msgLen));
-
- if (!msg.empty()) {
- writen2(fd, msg);
- }
-
- uint32_t len;
- auto commandResult = getMsgLen32(fd, &len);
- if (commandResult == ConsoleCommandResult::ConnectionClosed) {
- cout << "Connection closed by the server." << endl;
- return commandResult;
- }
- else if (commandResult == ConsoleCommandResult::TooLarge) {
- cerr << "Received a console message whose length (" << len << ") is exceeding the allowed one (" << g_consoleOutputMsgMaxSize << "), closing that connection" << endl;
- return commandResult;
- }
-
- if (len == 0) {
- if (outputEmptyLine) {
- cout << endl;
- }
-
- return ConsoleCommandResult::Valid;
- }
-
- msg.clear();
- msg.resize(len);
- readn2(fd, msg.data(), len);
- msg = sodDecryptSym(msg, g_consoleKey, readingNonce);
- cout << msg;
- cout.flush();
-
- return ConsoleCommandResult::Valid;
-}
-
-void doClient(ComboAddress server, const std::string& command)
-{
- if (!sodIsValidKey(g_consoleKey)) {
- cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl;
- return;
- }
-
- if (g_verbose) {
- cout<<"Connecting to "<<server.toStringWithPort()<<endl;
- }
-
- auto fd = FDWrapper(socket(server.sin4.sin_family, SOCK_STREAM, 0));
- if (fd.getHandle() < 0) {
- cerr<<"Unable to connect to "<<server.toStringWithPort()<<endl;
- return;
- }
- SConnect(fd.getHandle(), server);
- setTCPNoDelay(fd.getHandle());
- SodiumNonce theirs, ours, readingNonce, writingNonce;
- ours.init();
-
- writen2(fd.getHandle(), (const char*)ours.value, sizeof(ours.value));
- readn2(fd.getHandle(), (char*)theirs.value, sizeof(theirs.value));
- readingNonce.merge(ours, theirs);
- writingNonce.merge(theirs, ours);
-
- /* try sending an empty message, the server should send an empty
- one back. If it closes the connection instead, we are probably
- having a key mismatch issue. */
- auto commandResult = sendMessageToServer(fd.getHandle(), "", readingNonce, writingNonce, false);
- if (commandResult == ConsoleCommandResult::ConnectionClosed) {
- cerr<<"The server closed the connection right away, likely indicating a key mismatch. Please check your setKey() directive."<<endl;
- return;
- }
- else if (commandResult == ConsoleCommandResult::TooLarge) {
- return;
- }
-
- if (!command.empty()) {
- sendMessageToServer(fd.getHandle(), command, readingNonce, writingNonce, false);
- return;
- }
-
-#ifdef HAVE_LIBEDIT
- string histfile = historyFile();
- {
- ifstream history(histfile);
- string line;
- while (getline(history, line)) {
- add_history(line.c_str());
- }
- }
- ofstream history(histfile, std::ios_base::app);
- string lastline;
- for (;;) {
- char* sline = readline("> ");
- rl_bind_key('\t',rl_complete);
- if (!sline) {
- break;
- }
-
- string line(sline);
- if (!line.empty() && line != lastline) {
- add_history(sline);
- history << sline <<endl;
- history.flush();
- }
- lastline = line;
- free(sline);
-
- if (line == "quit") {
- break;
- }
- if (line == "help" || line == "?") {
- line = "help()";
- }
-
- /* no need to send an empty line to the server */
- if (line.empty()) {
- continue;
- }
-
- commandResult = sendMessageToServer(fd.getHandle(), line, readingNonce, writingNonce, true);
- if (commandResult != ConsoleCommandResult::Valid) {
- break;
- }
- }
-#else
- errlog("Client mode requested but libedit support is not available");
-#endif /* HAVE_LIBEDIT */
-}
-
-#ifdef HAVE_LIBEDIT
-static std::optional<std::string> getNextConsoleLine(ofstream& history, std::string& lastline)
-{
- char* sline = readline("> ");
- rl_bind_key('\t', rl_complete);
- if (!sline) {
- return std::nullopt;
- }
-
- string line(sline);
- if (!line.empty() && line != lastline) {
- add_history(sline);
- history << sline <<endl;
- history.flush();
- }
-
- lastline = line;
- free(sline);
-
- return line;
-}
-#else /* HAVE_LIBEDIT */
-static std::optional<std::string> getNextConsoleLine()
-{
- std::string line;
- if (!std::getline(std::cin, line)) {
- return std::nullopt;
- }
- return line;
-}
-#endif /* HAVE_LIBEDIT */
-
-void doConsole()
-{
-#ifdef HAVE_LIBEDIT
- string histfile = historyFile(true);
- {
- ifstream history(histfile);
- string line;
- while (getline(history, line)) {
- add_history(line.c_str());
- }
- }
- ofstream history(histfile, std::ios_base::app);
- string lastline;
-#endif /* HAVE_LIBEDIT */
-
- for (;;) {
-#ifdef HAVE_LIBEDIT
- auto line = getNextConsoleLine(history, lastline);
-#else /* HAVE_LIBEDIT */
- auto line = getNextConsoleLine();
-#endif /* HAVE_LIBEDIT */
- if (!line) {
- break;
- }
-
- if (*line == "quit") {
- break;
- }
- if (*line == "help" || *line == "?") {
- line = "help()";
- }
-
- string response;
- try {
- bool withReturn = true;
- retry:;
- try {
- auto lua = g_lua.lock();
- g_outputBuffer.clear();
- resetLuaSideEffect();
- auto ret = lua->executeCode<
- boost::optional<
- boost::variant<
- string,
- shared_ptr<DownstreamState>,
- ClientState*,
- std::unordered_map<string, double>
- >
- >
- >(withReturn ? ("return "+*line) : *line);
- if (ret) {
- if (const auto dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
- if (*dsValue) {
- cout<<(*dsValue)->getName()<<endl;
- }
- }
- else if (const auto csValue = boost::get<ClientState*>(&*ret)) {
- if (*csValue) {
- cout<<(*csValue)->local.toStringWithPort()<<endl;
- }
- }
- else if (const auto strValue = boost::get<string>(&*ret)) {
- cout<<*strValue<<endl;
- }
- else if (const auto um = boost::get<std::unordered_map<string, double> >(&*ret)) {
- using namespace json11;
- Json::object o;
- for(const auto& v : *um)
- o[v.first]=v.second;
- Json out = o;
- cout<<out.dump()<<endl;
- }
- }
- else {
- cout << g_outputBuffer << std::flush;
- }
-
- if (!getLuaNoSideEffect()) {
- feedConfigDelta(*line);
- }
- }
- catch (const LuaContext::SyntaxErrorException&) {
- if (withReturn) {
- withReturn=false;
- goto retry;
- }
- throw;
- }
- }
- catch (const LuaContext::WrongTypeException& e) {
- std::cerr<<"Command returned an object we can't print: "<<std::string(e.what())<<std::endl;
- // tried to return something we don't understand
- }
- catch (const LuaContext::ExecutionErrorException& e) {
- if (!strcmp(e.what(), "invalid key to 'next'")) {
- std::cerr<<"Error parsing parameters, did you forget parameter name?";
- }
- else {
- std::cerr << e.what();
- }
-
- try {
- std::rethrow_if_nested(e);
-
- std::cerr << std::endl;
- } catch (const std::exception& ne) {
- // ne is the exception that was thrown from inside the lambda
- std::cerr << ": " << ne.what() << std::endl;
- }
- catch (const PDNSException& ne) {
- // ne is the exception that was thrown from inside the lambda
- std::cerr << ": " << ne.reason << std::endl;
- }
- }
- catch (const std::exception& e) {
- std::cerr << e.what() << std::endl;
- }
- }
-}
-
-#ifndef DISABLE_COMPLETION
-/**** CARGO CULT CODE AHEAD ****/
-const std::vector<ConsoleKeyword> g_consoleKeywords{
- /* keyword, function, parameters, description */
- { "addACL", true, "netmask", "add to the ACL set who can use this server" },
- { "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\", name=\"name\"}]", "add a rule" },
- { "addBPFFilterDynBlocks", true, "addresses, dynbpf[[, seconds=10], msg]", "This is the eBPF equivalent of addDynBlocks(), blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter" },
- { "addCapabilitiesToRetain", true, "capability or list of capabilities", "Linux capabilities to retain after startup, like CAP_BPF" },
- { "addConsoleACL", true, "netmask", "add a netmask to the console ACL" },
- { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" },
- { "addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables" },
- { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
- { "addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
- { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
- { "addCacheHitResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a cache hit response rule" },
- { "addCacheInsertedResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a cache inserted response rule" },
- { "addResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a response rule" },
- { "addSelfAnsweredResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a self-answered response rule" },
- { "addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table" },
- { "AllowAction", true, "", "let these packets go through" },
- { "AllowResponseAction", true, "", "let these packets go through" },
- { "AllRule", true, "", "matches all traffic" },
- { "AndRule", true, "list of DNS rules", "matches if all sub-rules matches" },
- { "benchRule", true, "DNS Rule [, iterations [, suffix]]", "bench the specified DNS rule" },
- { "carbonServer", true, "serverIP, [ourname], [interval]", "report statistics to serverIP using our hostname, or 'ourname' if provided, every 'interval' seconds" },
- { "clearConsoleHistory", true, "", "clear the internal (in-memory) history of console commands" },
- { "clearDynBlocks", true, "", "clear all dynamic blocks" },
- { "clearQueryCounters", true, "", "clears the query counter buffer" },
- { "clearRules", true, "", "remove all current rules" },
- { "controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode" },
- { "ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action" },
- { "declareMetric", true, "name, type, description [, prometheusName]", "Declare a custom metric" },
- { "decMetric", true, "name", "Decrement a custom metric" },
- { "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
- { "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
- { "delta", true, "", "shows all commands entered that changed the configuration" },
- { "DNSSECRule", true, "", "matches queries with the DO bit set" },
- { "DnstapLogAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this query to a FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSQuestion and a DnstapMessage, that can be used to modify the dnstap message" },
- { "DnstapLogResponseAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this response to a remote or FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSResponse and a DnstapMessage, that can be used to modify the dnstap message" },
- { "DropAction", true, "", "drop these packets" },
- { "DropResponseAction", true, "", "drop these packets" },
- { "DSTPortRule", true, "port", "matches questions received to the destination port specified" },
- { "dumpStats", true, "", "print all statistics we gather" },
- { "dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object" },
- { "EDNSVersionRule", true, "version", "matches queries with the specified EDNS version" },
- { "EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present" },
- { "ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode" },
- { "ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)" },
- { "exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds" },
- { "exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds" },
- { "exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds" },
- { "exceedRespByterate", true, "rate, seconds", "get set of addresses that exceeded `rate` bytes/s answers over `seconds` seconds" },
- { "exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servfails/s over `seconds` seconds" },
- { "firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit" },
- { "fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer" },
- { "generateDNSCryptCertificate", true, "\"/path/to/providerPrivate.key\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", serial, validFrom, validUntil", "generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key" },
- { "generateDNSCryptProviderKeys", true, "\"/path/to/providerPublic.key\", \"/path/to/providerPrivate.key\"", "generate a new provider keypair" },
- { "getAction", true, "n", "Returns the Action associated with rule n" },
- { "getBind", true, "n", "returns the listener at index n" },
- { "getBindCount", true, "", "returns the number of listeners all kinds" },
- { "getCurrentTime", true, "", "returns the current time" },
- { "getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`" },
- { "getDNSCryptBindCount", true, "", "returns the number of DNSCrypt listeners" },
- { "getDOHFrontend", true, "n", "returns the DOH frontend with index n" },
- { "getDOHFrontendCount", true, "", "returns the number of DoH listeners" },
- { "getListOfAddressesOfNetworkInterface", true, "itf", "returns the list of addresses configured on a given network interface, as strings" },
- { "getListOfNetworkInterfaces", true, "", "returns the list of network interfaces present on the system, as strings" },
- { "getListOfRangesOfNetworkInterface", true, "itf", "returns the list of network ranges configured on a given network interface, as strings" },
- { "getMACAddress", true, "IP addr", "return the link-level address (MAC) corresponding to the supplied neighbour IP address, if known by the kernel" },
- { "getMetric", true, "name", "Get the value of a custom metric" },
- { "getOutgoingTLSSessionCacheSize", true, "", "returns the number of TLS sessions (for outgoing connections) currently cached" },
- { "getPool", true, "name", "return the pool named `name`, or \"\" for the default pool" },
- { "getPoolServers", true, "pool", "return servers part of this pool" },
- { "getPoolNames", true, "", "returns a table with all the pool names" },
- { "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
- { "getResponseRing", true, "", "return the current content of the response ring" },
- { "getRespRing", true, "", "return the qname/rcode content of the response ring" },
- { "getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string" },
- { "getServers", true, "", "returns a table with all defined servers" },
- { "getStatisticsCounters", true, "", "returns a map of statistic counters" },
- { "getTopCacheHitResponseRules", true, "[top]", "return the `top` cache-hit response rules" },
- { "getTopCacheInsertedResponseRules", true, "[top]", "return the `top` cache-inserted response rules" },
- { "getTopResponseRules", true, "[top]", "return the `top` response rules" },
- { "getTopRules", true, "[top]", "return the `top` rules" },
- { "getTopSelfAnsweredResponseRules", true, "[top]", "return the `top` self-answered response rules" },
- { "getTLSContext", true, "n", "returns the TLS context with index n" },
- { "getTLSFrontend", true, "n", "returns the TLS frontend with index n" },
- { "getTLSFrontendCount", true, "", "returns the number of DoT listeners" },
- { "getVerbose", true, "", "get whether log messages at the verbose level will be logged" },
- { "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n] [,options]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
- { "hashPassword", true, "password [, workFactor]", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"},
- { "HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"},
- { "HTTPPathRegexRule", true, "regex", "matches DoH queries whose HTTP path matches 'regex'"},
- { "HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"},
- { "HTTPStatusAction", true, "status, reason, body", "return an HTTP response"},
- { "inClientStartup", true, "", "returns true during console client parsing of configuration" },
- { "includeDirectory", true, "path", "include configuration files from `path`" },
- { "incMetric", true, "name", "Increment a custom metric" },
- { "KeyValueLookupKeyQName", true, "[wireFormat]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the qname of the query, either in wire format (default) or in plain text if 'wireFormat' is false" },
- { "KeyValueLookupKeySourceIP", true, "[v4Mask [, v6Mask [, includePort]]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the (possibly bitmasked) source IP of the client in network byte-order." },
- { "KeyValueLookupKeySuffix", true, "[minLabels [,wireFormat]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return a vector of keys based on the labels of the qname in DNS wire format or plain text" },
- { "KeyValueLookupKeyTag", true, "tag", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the value of the corresponding tag for this query, if it exists" },
- { "KeyValueStoreLookupAction", true, "kvs, lookupKey, destinationTag", "does a lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'" },
- { "KeyValueStoreRangeLookupAction", true, "kvs, lookupKey, destinationTag", "does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'" },
- { "KeyValueStoreLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store" },
- { "KeyValueStoreRangeLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store" },
- { "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
-#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
- { "loadTLSEngine", true, "engineName [, defaultString]", "Load the OpenSSL engine named 'engineName', setting the engine default string to 'defaultString' if supplied"},
-#endif
-#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
- { "loadTLSProvider", true, "providerName", "Load the OpenSSL provider named 'providerName'"},
-#endif
- { "LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
- { "LogResponseAction", true, "[filename], [append], [buffered]", "Log a line for each response, to the specified file if any, to the console (require verbose) otherwise. The `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
- { "LuaAction", true, "function", "Invoke a Lua function that accepts a DNSQuestion" },
- { "LuaFFIAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion" },
- { "LuaFFIPerThreadAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion, with a per-thread Lua context" },
- { "LuaFFIPerThreadResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse, with a per-thread Lua context" },
- { "LuaFFIResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse" },
- { "LuaFFIRule", true, "function", "Invoke a Lua FFI function that filters DNS questions" },
- { "LuaResponseAction", true, "function", "Invoke a Lua function that accepts a DNSResponse" },
- { "LuaRule", true, "function", "Invoke a Lua function that filters DNS questions" },
-#ifdef HAVE_IPCIPHER
- { "makeIPCipherKey", true, "password", "generates a 16-byte key that can be used to pseudonymize IP addresses with IP cipher" },
-#endif /* HAVE_IPCIPHER */
- { "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" },
- { "makeRule", true, "rule", "Make a NetmaskGroupRule() or a SuffixMatchNodeRule(), depending on how it is called" } ,
- { "MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60 [, scanFraction=10 [, shards=10]]]]]]]", "matches traffic exceeding the qps limit per subnet" },
- { "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" },
- { "mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
- { "mvCacheHitResponseRuleToTop", true, "", "move the last cache hit response rule to the first position" },
- { "mvCacheInsertedResponseRule", true, "from, to", "move cache inserted response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
- { "mvCacheInsertedResponseRuleToTop", true, "", "move the last cache inserted response rule to the first position" },
- { "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
- { "mvResponseRuleToTop", true, "", "move the last response rule to the first position" },
- { "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" },
- { "mvRuleToTop", true, "", "move the last rule to the first position" },
- { "mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
- { "mvSelfAnsweredResponseRuleToTop", true, "", "move the last self-answered response rule to the first position" },
- { "NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients" },
- { "newBPFFilter", true, "{ipv4MaxItems=int, ipv4PinnedPath=string, ipv6MaxItems=int, ipv6PinnedPath=string, cidr4MaxItems=int, cidr4PinnedPath=string, cidr6MaxItems=int, cidr6PinnedPath=string, qnamesMaxItems=int, qnamesPinnedPath=string, external=bool}", "Return a new eBPF socket filter with specified options." },
- { "newCA", true, "address", "Returns a ComboAddress based on `address`" },
-#ifdef HAVE_CDB
- { "newCDBKVStore", true, "fname, refreshDelay", "Return a new KeyValueStore object associated to the corresponding CDB database" },
-#endif
- { "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
- { "newDNSNameSet", true, "", "returns a new DNSNameSet" },
- { "newDynBPFFilter", true, "bpf", "Return a new dynamic eBPF filter associated to a given BPF Filter" },
- { "newFrameStreamTcpLogger", true, "addr [, options]", "create a FrameStream logger object writing to a TCP address (addr should be ip:port), to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
- { "newFrameStreamUnixLogger", true, "socket [, options]", "create a FrameStream logger object writing to a local unix socket, to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
-#ifdef HAVE_LMDB
- { "newLMDBKVStore", true, "fname, dbName [, noLock]", "Return a new KeyValueStore object associated to the corresponding LMDB database" },
-#endif
- { "newNMG", true, "", "Returns a NetmaskGroup" },
- { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache" },
- { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" },
- { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" },
- { "newRuleAction", true, "DNS rule, DNS action [, {uuid=\"UUID\", name=\"name\"}]", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
- { "newServer", true, "{address=\"ip:port\", qps=1000, order=1, weight=10, pool=\"abuse\", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName=\"a.root-servers.net.\", checkType=\"A\", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source=\"address|interface name|address@interface\", sockets=1, reconnectOnUp=false}", "instantiate a server" },
- { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
- { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
- { "newSVCRecordParameters", true, "priority, target, mandatoryParams, alpns, noDefaultAlpn [, port [, ech [, ipv4hints [, ipv6hints [, additionalParameters ]]]]]", "return a new SVCRecordParameters object, to use with SpoofSVCAction" },
- { "NegativeAndSOAAction", true, "nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options]", "Turn a query into a NXDomain or NoData answer and sets a SOA record in the additional section" },
- { "NoneAction", true, "", "Does nothing. Subsequent rules are processed after this action" },
- { "NotRule", true, "selector", "Matches the traffic if the selector rule does not match" },
- { "OpcodeRule", true, "code", "Matches queries with opcode code. code can be directly specified as an integer, or one of the built-in DNSOpcodes" },
- { "OrRule", true, "selectors", "Matches the traffic if one or more of the the selectors rules does match" },
- { "PoolAction", true, "poolname", "set the packet into the specified pool" },
- { "PoolAvailableRule", true, "poolname", "Check whether a pool has any servers available to handle queries" },
- { "PoolOutstandingRule", true, "poolname, limit", "Check whether a pool has outstanding queries above limit" },
- { "printDNSCryptProviderFingerprint", true, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" },
- { "ProbaRule", true, "probability", "Matches queries with a given probability. 1.0 means always" },
- { "ProxyProtocolValueRule", true, "type [, value]", "matches queries with a specified Proxy Protocol TLV value of that type, optionally matching the content of the option as well" },
- { "QClassRule", true, "qclass", "Matches queries with the specified qclass. class can be specified as an integer or as one of the built-in DNSClass" },
- { "QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels" },
- { "QNameRule", true, "qname", "matches queries with the specified qname" },
- { "QNameSetRule", true, "set", "Matches if the set contains exact qname" },
- { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
- { "QPSAction", true, "maxqps", "Drop a packet if it does exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise" },
- { "QPSPoolAction", true, "maxqps, poolname", "Send the packet into the specified pool only if it does not exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise" },
- { "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
- { "RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode" },
- { "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
- { "RDRule", true, "", "Matches queries with the RD flag set" },
- { "RecordsCountRule", true, "section, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records in the section section. section can be specified as an integer or as a DNS Packet Sections" },
- { "RecordsTypeCountRule", true, "section, qtype, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records of type type in the section section" },
- { "RegexRule", true, "regex", "matches the query name against the supplied regex" },
- { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
- { "reloadAllCertificates", true, "", "reload all DNSCrypt and TLS certificates, along with their associated keys" },
- { "RemoteLogAction", true, "RemoteLogger [, alterFunction [, serverID]]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes. `serverID` is the server identifier." },
- { "RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME [, serverID]]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records. `serverID` is the server identifier." },
- { "requestTCPStatesDump", true, "", "Request a dump of the TCP states (incoming connections, outgoing connections) during the next scan. Useful for debugging purposes only" },
- { "rmACL", true, "netmask", "remove netmask from ACL" },
- { "rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
- { "rmCacheInsertedResponseRule", true, "id", "remove cache inserted response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
- { "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
- { "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
- { "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
- { "rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string" },
- { "roundrobin", false, "", "Simple round robin over available servers" },
- { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
- { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
- { "setACLFromFile", true, "file", "replace the ACL set with netmasks in this file" },
- { "setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS" },
- { "setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends" },
- { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
- { "setCacheCleaningDelay", true, "num", "Set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries" },
- { "setCacheCleaningPercentage", true, "num", "Set the percentage of the cache that the cache cleaning algorithm will try to free by removing expired entries. By default (100), all expired entries are remove" },
- { "setConsistentHashingBalancingFactor", true, "factor", "Set the balancing factor for bounded-load consistent hashing" },
- { "setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks" },
- { "setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections" },
- { "setConsoleMaximumConcurrentConnections", true, "max", "Set the maximum number of concurrent console connections" },
- { "setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB" },
- { "setDefaultBPFFilter", true, "filter", "When used at configuration time, the corresponding BPFFilter will be attached to every bind" },
- { "setDoHDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle DoH downstream connections" },
- { "setDoHDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream DoH connection to a backend might stay idle" },
- { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" },
- { "setDynBlocksPurgeInterval", true, "sec", "set how often the expired dynamic block entries should be removed" },
- { "setDropEmptyQueries", true, "drop", "Whether to drop empty queries right away instead of sending a NOTIMP response" },
- { "setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query" },
- { "setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries" },
- { "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" },
- { "setKey", true, "key", "set access key to that key" },
- { "setLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "reset the list of addresses we listen on to this address" },
- { "setMaxCachedDoHConnectionsPerDownstream", true, "max", "Set the maximum number of inactive DoH connections to a backend cached by each worker DoH thread" },
- { "setMaxCachedTCPConnectionsPerDownstream", true, "max", "Set the maximum number of inactive TCP connections to a backend cached by each worker TCP thread" },
- { "setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections" },
- { "setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited" },
- { "setMaxTCPConnectionsPerClient", true, "n", "set the maximum number of TCP connections per client. 0 means unlimited" },
- { "setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited" },
- { "setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)" },
- { "setMaxUDPOutstanding", true, "n", "set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 65535" },
- { "setMetric", true, "name, value", "Set the value of a custom metric to the supplied value" },
- { "setPayloadSizeOnSelfGeneratedAnswers", true, "payloadSize", "set the UDP payload size advertised via EDNS on self-generated responses" },
- { "setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy" },
- { "setPoolServerPolicyLua", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
- { "setPoolServerPolicyLuaFFI", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
- { "setPoolServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy for this pool to one named 'name' and returned by the Lua FFI code passed in 'code'" },
- { "setProxyProtocolACL", true, "{netmask, netmask}", "Set the netmasks who are allowed to send Proxy Protocol headers in front of queries/connections" },
- { "setProxyProtocolApplyACLToProxiedClients", true, "apply", "Whether the general ACL should be applied to the source IP address gathered from a Proxy Protocol header, in addition to being first applied to the source address seen by dnsdist" },
- { "setProxyProtocolMaximumPayloadSize", true, "max", "Set the maximum size of a Proxy Protocol payload, in bytes" },
- { "setQueryCount", true, "bool", "set whether queries should be counted" },
- { "setQueryCountFilter", true, "func", "filter queries that would be counted, where `func` is a function with parameter `dq` which decides whether a query should and how it should be counted" },
- { "SetReducedTTLResponseAction", true, "percentage", "Reduce the TTL of records in a response to a given percentage" },
- { "setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking" },
- { "setRingBuffersOptions", true, "{ lockRetries=int, recordQueries=true, recordResponses=true }", "set ringbuffer options" },
- { "setRingBuffersSize", true, "n [, numberOfShards]", "set the capacity of the ringbuffers used for live traffic inspection to `n`, and optionally the number of shards to use to `numberOfShards`" },
- { "setRoundRobinFailOnNoServer", true, "value", "By default the roundrobin load-balancing policy will still try to select a backend even if all backends are currently down. Setting this to true will make the policy fail and return that no server is available instead" },
- { "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" },
- { "setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds" },
- { "setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value" },
- { "setServerPolicy", true, "policy", "set server selection policy to that policy" },
- { "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" },
- { "setServerPolicyLuaFFI", true, "name, function", "set server selection policy to one named 'name' and provided by the Lua FFI 'function'" },
- { "setServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy to one named 'name' and returned by the Lua FFI code passed in 'code'" },
- { "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" },
- { "setStaleCacheEntriesTTL", true, "n", "allows using cache entries expired for at most n seconds when there is no backend available to answer for a query" },
- { "setSyslogFacility", true, "facility", "set the syslog logging facility to 'facility'. Defaults to LOG_DAEMON" },
- { "setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections" },
- { "setTCPFastOpenKey", true, "string", "TCP Fast Open Key" },
- { "setTCPDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream TCP connection to a backend might stay idle" },
- { "setTCPInternalPipeBufferSize", true, "size", "Set the size in bytes of the internal buffer of the pipes used internally to distribute connections to TCP (and DoT) workers threads" },
- { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" },
- { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" },
- { "setUDPMultipleMessagesVectorSize", true, "n", "set the size of the vector passed to recvmmsg() to receive UDP messages. Default to 1 which means that the feature is disabled and recvmsg() is used instead" },
- { "setUDPSocketBufferSizes", true, "recv, send", "Set the size of the receive (SO_RCVBUF) and send (SO_SNDBUF) buffers for incoming UDP sockets" },
- { "setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds" },
- { "setVerbose", true, "bool", "set whether log messages at the verbose level will be logged" },
- { "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" },
- { "setVerboseLogDestination", true, "destination file", "Set a destination file to write the 'verbose' log messages to, instead of sending them to syslog and/or the standard output" },
- { "setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders, statsRequireAuthentication}]", "Updates webserver configuration" },
- { "setWeightedBalancingFactor", true, "factor", "Set the balancing factor for bounded-load weighted policies (whashed, wrandom)" },
- { "setWHashedPertubation", true, "value", "Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instance" },
- { "show", true, "string", "outputs `string`" },
- { "showACL", true, "", "show our ACL set" },
- { "showBinds", true, "", "show listening addresses (frontends)" },
- { "showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width" },
- { "showConsoleACL", true, "", "show our current console ACL set" },
- { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
- { "showDOHFrontends", true, "", "list all the available DOH frontends" },
- { "showDOHResponseCodes", true, "", "show the HTTP response code statistics for the DoH frontends"},
- { "showDynBlocks", true, "", "show dynamic blocks in force" },
- { "showPools", true, "", "show the available pools" },
- { "showPoolServerPolicy", true, "pool", "show server selection policy for this pool" },
- { "showResponseLatency", true, "", "show a plot of the response time latency distribution" },
- { "showResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined response rules, optionally with their UUIDs and optionally truncated to a given width" },
- { "showRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined rules, optionally with their UUIDs and optionally truncated to a given width" },
- { "showSecurityStatus", true, "", "Show the security status"},
- { "showSelfAnsweredResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined self-answered response rules, optionally with their UUIDs and optionally truncated to a given width" },
- { "showServerPolicy", true, "", "show name of currently operational server selection policy" },
- { "showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs" },
- { "showTCPStats", true, "", "show some statistics regarding TCP" },
- { "showTLSContexts", true, "", "list all the available TLS contexts" },
- { "showTLSErrorCounters", true, "", "show metrics about TLS handshake failures" },
- { "showVersion", true, "", "show the current version" },
- { "showWebserverConfig", true, "", "Show the current webserver configuration" },
- { "shutdown", true, "", "shut down `dnsdist`" },
- { "snmpAgent", true, "enableTraps [, daemonSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `daemonSocket` an optional string specifying how to connect to the daemon agent"},
- { "SetAdditionalProxyProtocolValueAction", true, "type, value", "Add a Proxy Protocol TLV value of this type" },
- { "SetDisableECSAction", true, "", "Disable the sending of ECS to the backend. Subsequent rules are processed after this action." },
- { "SetDisableValidationAction", true, "", "set the CD bit in the question, let it go through" },
- { "SetECSAction", true, "v4[, v6]", "Set the ECS prefix and prefix length sent to backends to an arbitrary value" },
- { "SetECSOverrideAction", true, "override", "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action" },
- { "SetECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action" },
- { "SetMacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action" },
- { "SetEDNSOptionAction", true, "option, data", "Add arbitrary EDNS option and data to the query. Subsequent rules are processed after this action" },
- { "SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through" },
- { "setOutgoingDoHWorkerThreads", true, "n", "Number of outgoing DoH worker threads" },
- { "SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'" },
- { "SetSkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer" },
- { "SetSkipCacheResponseAction", true, "", "Don’t store this response into the cache" },
- { "SetTagAction", true, "name, value", "set the tag named 'name' to the given value" },
- { "SetTagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
- { "SetTempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies" },
- { "SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)" },
- { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
- { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
- { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
- { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
- { "SpoofRawAction", true, "raw|list of raws [, options]", "Forge a response with the specified record data as raw bytes. If you specify multiple raws (it is assumed they match the query type), all will get spoofed in" },
- { "SpoofSVCAction", true, "list of svcParams [, options]", "Forge a response with the specified SVC record data" } ,
- { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
- { "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" },
- { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
- { "TCPRule", true, "[tcp]", "Matches question received over TCP if tcp is true, over UDP otherwise" },
- { "TeeAction", true, "remote [, addECS [, local]]", "send copy of query to remote, optionally adding ECS info, optionally set local address" },
- { "testCrypto", true, "", "test of the crypto all works" },
- { "TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"},
- { "topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer" },
- { "topCacheHitResponseRules", true, "[top][, vars]", "show `top` cache-hit response rules" },
- { "topCacheInsertedResponseRules", true, "[top][, vars]", "show `top` cache-inserted response rules" },
- { "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" },
- { "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" },
- { "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=NXDomain), as grouped when optionally cut down to 'labels' labels" },
- { "topResponseRules", true, "[top][, vars]", "show `top` response rules" },
- { "topRules", true, "[top][, vars]", "show `top` rules" },
- { "topSelfAnsweredResponseRules", true, "[top][, vars]", "show `top` self-answered response rules" },
- { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" },
- { "TrailingDataRule", true, "", "Matches if the query has trailing data" },
- { "truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891." },
- { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" },
- { "webserver", true, "address:port", "launch a webserver with stats on that address" },
- { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" },
- { "chashed", false, "", "Consistent hashed ('sticky') distribution over available servers, also based on the server 'weight' parameter" },
- { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" },
-};
-
-#if defined(HAVE_LIBEDIT)
-extern "C" {
-static char* my_generator(const char* text, int state)
-{
- string t(text);
- /* to keep it readable, we try to keep only 4 keywords per line
- and to start a new line when the first letter changes */
- static int s_counter = 0;
- int counter=0;
- if (!state) {
- s_counter = 0;
- }
-
- for (const auto& keyword : g_consoleKeywords) {
- if (boost::starts_with(keyword.name, t) && counter++ == s_counter) {
- std::string value(keyword.name);
- s_counter++;
- if (keyword.function) {
- value += "(";
- if (keyword.parameters.empty()) {
- value += ")";
- }
- }
- return strdup(value.c_str());
- }
- }
- return 0;
-}
-
-char** my_completion( const char * text , int start, int end)
-{
- char **matches=0;
- if (start == 0) {
- matches = rl_completion_matches ((char*)text, &my_generator);
- }
-
- // skip default filename completion.
- rl_attempted_completion_over = 1;
-
- return matches;
-}
-}
-#endif /* HAVE_LIBEDIT */
-#endif /* DISABLE_COMPLETION */
-
-static void controlClientThread(ConsoleConnection&& conn)
-{
- try {
- setThreadName("dnsdist/conscli");
-
- setTCPNoDelay(conn.getFD());
-
- SodiumNonce theirs, ours, readingNonce, writingNonce;
- ours.init();
- readn2(conn.getFD(), (char*)theirs.value, sizeof(theirs.value));
- writen2(conn.getFD(), (char*)ours.value, sizeof(ours.value));
- readingNonce.merge(ours, theirs);
- writingNonce.merge(theirs, ours);
-
- for (;;) {
- uint32_t len;
- if (getMsgLen32(conn.getFD(), &len) != ConsoleCommandResult::Valid) {
- break;
- }
-
- if (len == 0) {
- /* just ACK an empty message
- with an empty response */
- putMsgLen32(conn.getFD(), 0);
- continue;
- }
-
- std::string line;
- line.resize(len);
- readn2(conn.getFD(), line.data(), len);
-
- line = sodDecryptSym(line, g_consoleKey, readingNonce);
-
- string response;
- try {
- bool withReturn = true;
- retry:;
- try {
- auto lua = g_lua.lock();
-
- g_outputBuffer.clear();
- resetLuaSideEffect();
- auto ret = lua->executeCode<
- boost::optional<
- boost::variant<
- string,
- shared_ptr<DownstreamState>,
- ClientState*,
- std::unordered_map<string, double>
- >
- >
- >(withReturn ? ("return "+line) : line);
-
- if (ret) {
- if (const auto dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
- if (*dsValue) {
- response = (*dsValue)->getName()+"\n";
- } else {
- response = "";
- }
- }
- else if (const auto csValue = boost::get<ClientState*>(&*ret)) {
- if (*csValue) {
- response = (*csValue)->local.toStringWithPort()+"\n";
- } else {
- response = "";
- }
- }
- else if (const auto strValue = boost::get<string>(&*ret)) {
- response = *strValue+"\n";
- }
- else if (const auto um = boost::get<std::unordered_map<string, double> >(&*ret)) {
- using namespace json11;
- Json::object o;
- for(const auto& v : *um) {
- o[v.first] = v.second;
- }
- Json out = o;
- response = out.dump()+"\n";
- }
- }
- else {
- response = g_outputBuffer;
- }
- if (!getLuaNoSideEffect()) {
- feedConfigDelta(line);
- }
- }
- catch (const LuaContext::SyntaxErrorException&) {
- if(withReturn) {
- withReturn=false;
- goto retry;
- }
- throw;
- }
- }
- catch(const LuaContext::WrongTypeException& e) {
- response = "Command returned an object we can't print: " +std::string(e.what()) + "\n";
- // tried to return something we don't understand
- }
- catch (const LuaContext::ExecutionErrorException& e) {
- if (!strcmp(e.what(),"invalid key to 'next'")) {
- response = "Error: Parsing function parameters, did you forget parameter name?";
- }
- else {
- response = "Error: " + string(e.what());
- }
-
- try {
- std::rethrow_if_nested(e);
- } catch (const std::exception& ne) {
- // ne is the exception that was thrown from inside the lambda
- response+= ": " + string(ne.what());
- }
- catch (const PDNSException& ne) {
- // ne is the exception that was thrown from inside the lambda
- response += ": " + string(ne.reason);
- }
- }
- catch (const LuaContext::SyntaxErrorException& e) {
- response = "Error: " + string(e.what()) + ": ";
- }
- response = sodEncryptSym(response, g_consoleKey, writingNonce);
- putMsgLen32(conn.getFD(), response.length());
- writen2(conn.getFD(), response.c_str(), response.length());
- }
- if (g_logConsoleConnections) {
- infolog("Closed control connection from %s", conn.getClient().toStringWithPort());
- }
- }
- catch (const std::exception& e) {
- errlog("Got an exception in client connection from %s: %s", conn.getClient().toStringWithPort(), e.what());
- }
-}
-
-void controlThread(int fd, ComboAddress local)
-{
- FDWrapper acceptFD(fd);
- try
- {
- setThreadName("dnsdist/control");
- ComboAddress client;
- int sock;
- auto localACL = g_consoleACL.getLocal();
- infolog("Accepting control connections on %s", local.toStringWithPort());
-
- while ((sock = SAccept(acceptFD.getHandle(), client)) >= 0) {
-
- FDWrapper socket(sock);
- if (!sodIsValidKey(g_consoleKey)) {
- vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort());
- continue;
- }
-
- if (!localACL->match(client)) {
- vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort());
- continue;
- }
-
- try {
- ConsoleConnection conn(client, std::move(socket));
- if (g_logConsoleConnections) {
- warnlog("Got control connection from %s", client.toStringWithPort());
- }
-
- std::thread t(controlClientThread, std::move(conn));
- t.detach();
- }
- catch (const std::exception& e) {
- errlog("Control connection died: %s", e.what());
- }
- }
- }
- catch (const std::exception& e) {
- errlog("Control thread died: %s", e.what());
- }
-}
-
-void clearConsoleHistory()
-{
-#ifdef HAVE_LIBEDIT
- clear_history();
-#endif /* HAVE_LIBEDIT */
- g_confDelta.clear();
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-#include "config.h"
-
-#ifndef DISABLE_COMPLETION
-struct ConsoleKeyword {
- std::string name;
- bool function;
- std::string parameters;
- std::string description;
- std::string toString() const
- {
- std::string res(name);
- if (function) {
- res += "(" + parameters + ")";
- }
- res += ": ";
- res += description;
- return res;
- }
-};
-extern const std::vector<ConsoleKeyword> g_consoleKeywords;
-extern "C" {
-char** my_completion( const char * text , int start, int end);
-}
-
-#endif /* DISABLE_COMPLETION */
-
-extern GlobalStateHolder<NetmaskGroup> g_consoleACL;
-extern std::string g_consoleKey; // in theory needs locking
-extern bool g_logConsoleConnections;
-extern bool g_consoleEnabled;
-extern uint32_t g_consoleOutputMsgMaxSize;
-
-void doClient(ComboAddress server, const std::string& command);
-void doConsole();
-void controlThread(int fd, ComboAddress local);
-void clearConsoleHistory();
-
-void setConsoleMaximumConcurrentConnections(size_t max);
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-#ifndef DISABLE_DYNBLOCKS
-#include <unordered_set>
-
-#include "dolog.hh"
-#include "dnsdist-rings.hh"
-#include "statnode.hh"
-
-#include "dnsdist-lua-inspection-ffi.hh"
-
-// dnsdist_ffi_stat_node_t is a lightuserdata
-template<>
-struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*> {
- static const int minSize = 1;
- static const int maxSize = 1;
-
- static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept {
- lua_pushlightuserdata(state, ptr);
- return PushedObject{state, 1};
- }
-};
-
-typedef std::function<bool(dnsdist_ffi_stat_node_t*)> dnsdist_ffi_stat_node_visitor_t;
-
-struct dnsdist_ffi_stat_node_t
-{
- dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, std::optional<std::string>& reason_): node(node_), self(self_), children(children_), reason(reason_)
- {
- }
-
- const StatNode& node;
- const StatNode::Stat& self;
- const StatNode::Stat& children;
- std::optional<std::string>& reason;
-};
-
-class DynBlockRulesGroup
-{
-private:
-
- struct Counts
- {
- std::map<uint8_t, uint64_t> d_rcodeCounts;
- std::map<uint16_t, uint64_t> d_qtypeCounts;
- uint64_t queries{0};
- uint64_t responses{0};
- uint64_t respBytes{0};
- };
-
- struct DynBlockRule
- {
- DynBlockRule(): d_enabled(false)
- {
- }
-
- DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action): d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true)
- {
- }
-
- bool matches(const struct timespec& when)
- {
- if (!d_enabled) {
- return false;
- }
-
- if (d_seconds && when < d_cutOff) {
- return false;
- }
-
- if (when < d_minTime) {
- d_minTime = when;
- }
-
- return true;
- }
-
- bool rateExceeded(unsigned int count, const struct timespec& now) const
- {
- if (!d_enabled) {
- return false;
- }
-
- double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
- double limit = delta * d_rate;
- return (count > limit);
- }
-
- bool warningRateExceeded(unsigned int count, const struct timespec& now) const
- {
- if (!d_enabled) {
- return false;
- }
-
- if (d_warningRate == 0) {
- return false;
- }
-
- double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
- double limit = delta * d_warningRate;
- return (count > limit);
- }
-
- bool isEnabled() const
- {
- return d_enabled;
- }
-
- std::string toString() const
- {
- if (!isEnabled()) {
- return "";
- }
-
- std::stringstream result;
- if (d_action != DNSAction::Action::None) {
- result << DNSAction::typeToString(d_action) << " ";
- }
- else {
- result << "Apply the global DynBlock action ";
- }
- result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
-
- return result.str();
- }
-
- std::string d_blockReason;
- struct timespec d_cutOff;
- struct timespec d_minTime;
- unsigned int d_blockDuration{0};
- unsigned int d_rate{0};
- unsigned int d_warningRate{0};
- unsigned int d_seconds{0};
- DNSAction::Action d_action{DNSAction::Action::None};
- bool d_enabled{false};
- };
-
- struct DynBlockRatioRule: DynBlockRule
- {
- DynBlockRatioRule(): DynBlockRule()
- {
- }
-
- DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses): DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio)
- {
- }
-
- bool ratioExceeded(unsigned int total, unsigned int count) const
- {
- if (!d_enabled) {
- return false;
- }
-
- if (total < d_minimumNumberOfResponses) {
- return false;
- }
-
- double allowed = d_ratio * static_cast<double>(total);
- return (count > allowed);
- }
-
- bool warningRatioExceeded(unsigned int total, unsigned int count) const
- {
- if (!d_enabled) {
- return false;
- }
-
- if (d_warningRatio == 0.0) {
- return false;
- }
-
- if (total < d_minimumNumberOfResponses) {
- return false;
- }
-
- double allowed = d_warningRatio * static_cast<double>(total);
- return (count > allowed);
- }
-
- std::string toString() const
- {
- if (!isEnabled()) {
- return "";
- }
-
- std::stringstream result;
- if (d_action != DNSAction::Action::None) {
- result << DNSAction::typeToString(d_action) << " ";
- }
- else {
- result << "Apply the global DynBlock action ";
- }
- result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
-
- return result.str();
- }
-
- size_t d_minimumNumberOfResponses{0};
- double d_ratio{0.0};
- double d_warningRatio{0.0};
- };
-
- typedef std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash> counts_t;
-
-public:
- DynBlockRulesGroup()
- {
- }
-
- void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
- {
- d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
- }
-
- /* rate is in bytes per second */
- void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
- {
- d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
- }
-
- void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
- {
- auto& entry = d_rcodeRules[rcode];
- entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
- }
-
- void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
- {
- auto& entry = d_rcodeRatioRules[rcode];
- entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses);
- }
-
- void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
- {
- auto& entry = d_qtypeRules[qtype];
- entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
- }
-
- typedef std::function<std::tuple<bool, boost::optional<std::string>>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> smtVisitor_t;
-
- void setSuffixMatchRule(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor)
- {
- d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
- d_smtVisitor = visitor;
- }
-
- void setSuffixMatchRuleFFI(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor)
- {
- d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
- d_smtVisitorFFI = visitor;
- }
-
- void setMasks(uint8_t v4, uint8_t v6, uint8_t port)
- {
- d_v4Mask = v4;
- d_v6Mask = v6;
- d_portMask = port;
- }
-
- void apply()
- {
- struct timespec now;
- gettime(&now);
-
- apply(now);
- }
-
- void apply(const struct timespec& now);
-
- void excludeRange(const Netmask& range)
- {
- d_excludedSubnets.addMask(range);
- }
-
- void excludeRange(const NetmaskGroup& group)
- {
- d_excludedSubnets.addMasks(group, true);
- }
-
- void includeRange(const Netmask& range)
- {
- d_excludedSubnets.addMask(range, false);
- }
-
- void includeRange(const NetmaskGroup& group)
- {
- d_excludedSubnets.addMasks(group, false);
- }
-
- void excludeDomain(const DNSName& domain)
- {
- d_excludedDomains.add(domain);
- }
-
- std::string toString() const
- {
- std::stringstream result;
-
- result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
- result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
- result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl;
- result << "RCode rules: " << std::endl;
- for (const auto& rule : d_rcodeRules) {
- result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
- }
- for (const auto& rule : d_rcodeRatioRules) {
- result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
- }
- result << "QType rules: " << std::endl;
- for (const auto& rule : d_qtypeRules) {
- result << "- " << QType(rule.first).toString() << ": " << rule.second.toString() << std::endl;
- }
- result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
- result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl;
-
- return result.str();
- }
-
- void setQuiet(bool quiet)
- {
- d_beQuiet = quiet;
- }
-
-private:
-
- bool checkIfQueryTypeMatches(const Rings::Query& query);
- bool checkIfResponseCodeMatches(const Rings::Response& response);
- void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning);
- void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
-
- void addBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
- {
- addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
- }
-
- void handleWarning(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
- {
- addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
- }
-
- bool hasQueryRules() const
- {
- return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
- }
-
- bool hasResponseRules() const
- {
- return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty();
- }
-
- bool hasSuffixMatchRules() const
- {
- return d_suffixMatchRule.isEnabled();
- }
-
- bool hasRules() const
- {
- return hasQueryRules() || hasResponseRules();
- }
-
- void processQueryRules(counts_t& counts, const struct timespec& now);
- void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
-
- std::map<uint8_t, DynBlockRule> d_rcodeRules;
- std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
- std::map<uint16_t, DynBlockRule> d_qtypeRules;
- DynBlockRule d_queryRateRule;
- DynBlockRule d_respRateRule;
- DynBlockRule d_suffixMatchRule;
- NetmaskGroup d_excludedSubnets;
- SuffixMatchNode d_excludedDomains;
- smtVisitor_t d_smtVisitor;
- dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
- uint8_t d_v6Mask{128};
- uint8_t d_v4Mask{32};
- uint8_t d_portMask{0};
- bool d_beQuiet{false};
-};
-
-class DynBlockMaintenance
-{
-public:
- static void run();
-
- /* return the (cached) number of hits per second for the top offenders, averaged over 60s */
- static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getHitsForTopNetmasks();
- static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getHitsForTopSuffixes();
-
- /* get the the top offenders based on the current value of the counters */
- static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getTopNetmasks(size_t topN);
- static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes(size_t topN);
- static void purgeExpired(const struct timespec& now);
-
- static time_t s_expiredDynBlocksPurgeInterval;
-
-private:
- static void collectMetrics();
- static void generateMetrics();
-
- struct MetricsSnapshot
- {
- std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> nmgData;
- std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> smtData;
- };
-
- struct Tops
- {
- std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGsByReason;
- std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> topSMTsByReason;
- };
-
- static LockGuarded<Tops> s_tops;
- /* s_metricsData should only be accessed by the dynamic blocks maintenance thread so it does not need a lock */
- // need N+1 datapoints to be able to do the diff after a collection point has been reached
- static std::list<MetricsSnapshot> s_metricsData;
- static size_t s_topN;
-};
-
-#endif /* DISABLE_DYNBLOCKS */
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "dnsdist-dynbpf.hh"
-
-bool DynBPFFilter::block(const ComboAddress& addr, const struct timespec& until)
-{
- bool inserted = false;
- auto data = d_data.lock();
-
- if (data->d_excludedSubnets.match(addr)) {
- /* do not add a block for excluded subnets */
- return inserted;
- }
-
- const container_t::iterator it = data->d_entries.find(addr);
- if (it != data->d_entries.end()) {
- if (it->d_until < until) {
- data->d_entries.replace(it, BlockEntry(addr, until));
- }
- }
- else {
- data->d_bpf->block(addr, BPFFilter::MatchAction::Drop);
- data->d_entries.insert(BlockEntry(addr, until));
- inserted = true;
- }
- return inserted;
-}
-
-void DynBPFFilter::purgeExpired(const struct timespec& now)
-{
- auto data = d_data.lock();
-
- typedef boost::multi_index::nth_index<container_t,1>::type ordered_until;
- ordered_until& ou = boost::multi_index::get<1>(data->d_entries);
-
- for (ordered_until::iterator it = ou.begin(); it != ou.end(); ) {
- if (it->d_until < now) {
- ComboAddress addr = it->d_addr;
- it = ou.erase(it);
- data->d_bpf->unblock(addr);
- }
- else {
- break;
- }
- }
-}
-
-std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > DynBPFFilter::getAddrStats()
-{
- std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > result;
- auto data = d_data.lock();
-
- if (!data->d_bpf) {
- return result;
- }
-
- const auto& stats = data->d_bpf->getAddrStats();
- result.reserve(stats.size());
- for (const auto& stat : stats) {
- const container_t::iterator it = data->d_entries.find(stat.first);
- if (it != data->d_entries.end()) {
- result.push_back(std::make_tuple(stat.first, stat.second, it->d_until));
- }
- }
- return result;
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-#include "config.h"
-
-#include "bpf-filter.hh"
-#include "iputils.hh"
-
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index/member.hpp>
-
-class DynBPFFilter
-{
-public:
- DynBPFFilter(std::shared_ptr<BPFFilter>& bpf)
- {
- d_data.lock()->d_bpf = bpf;
- }
- ~DynBPFFilter()
- {
- }
- void excludeRange(const Netmask& range)
- {
- d_data.lock()->d_excludedSubnets.addMask(range);
- }
- void includeRange(const Netmask& range)
- {
- d_data.lock()->d_excludedSubnets.addMask(range, false);
- }
- /* returns true if the addr wasn't already blocked, false otherwise */
- bool block(const ComboAddress& addr, const struct timespec& until);
- void purgeExpired(const struct timespec& now);
- std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > getAddrStats();
-private:
- struct BlockEntry
- {
- BlockEntry(const ComboAddress& addr, const struct timespec until): d_addr(addr), d_until(until)
- {
- }
- ComboAddress d_addr;
- struct timespec d_until;
- };
- typedef boost::multi_index_container<BlockEntry,
- boost::multi_index::indexed_by <
- boost::multi_index::ordered_unique< boost::multi_index::member<BlockEntry,ComboAddress,&BlockEntry::d_addr>, ComboAddress::addressOnlyLessThan >,
- boost::multi_index::ordered_non_unique< boost::multi_index::member<BlockEntry,struct timespec,&BlockEntry::d_until> >
- >
- > container_t;
- struct Data {
- container_t d_entries;
- std::shared_ptr<BPFFilter> d_bpf{nullptr};
- NetmaskGroup d_excludedSubnets;
- };
- LockGuarded<Data> d_data;
-};
-
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "dolog.hh"
-#include "dnsdist.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsparser.hh"
-#include "dnswriter.hh"
-#include "ednsoptions.hh"
-#include "ednssubnet.hh"
-
-/* when we add EDNS to a query, we don't want to advertise
- a large buffer size */
-size_t g_EdnsUDPPayloadSize = 512;
-static const uint16_t defaultPayloadSizeSelfGenAnswers = 1232;
-static_assert(defaultPayloadSizeSelfGenAnswers < s_udpIncomingBufferSize, "The UDP responder's payload size should be smaller or equal to our incoming buffer size");
-uint16_t g_PayloadSizeSelfGenAnswers{defaultPayloadSizeSelfGenAnswers};
-
-/* draft-ietf-dnsop-edns-client-subnet-04 "11.1. Privacy" */
-uint16_t g_ECSSourcePrefixV4 = 24;
-uint16_t g_ECSSourcePrefixV6 = 56;
-
-bool g_ECSOverride{false};
-bool g_addEDNSToSelfGeneratedResponses{true};
-
-int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent)
-{
- assert(initialPacket.size() >= sizeof(dnsheader));
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
-
- if (ntohs(dh->arcount) == 0)
- return ENOENT;
-
- if (ntohs(dh->qdcount) == 0)
- return ENOENT;
-
- PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
-
- size_t idx = 0;
- DNSName rrname;
- uint16_t qdcount = ntohs(dh->qdcount);
- uint16_t ancount = ntohs(dh->ancount);
- uint16_t nscount = ntohs(dh->nscount);
- uint16_t arcount = ntohs(dh->arcount);
- uint16_t rrtype;
- uint16_t rrclass;
- string blob;
- struct dnsrecordheader ah;
-
- rrname = pr.getName();
- rrtype = pr.get16BitInt();
- rrclass = pr.get16BitInt();
-
- GenericDNSPacketWriter<PacketBuffer> pw(newContent, rrname, rrtype, rrclass, dh->opcode);
- pw.getHeader()->id=dh->id;
- pw.getHeader()->qr=dh->qr;
- pw.getHeader()->aa=dh->aa;
- pw.getHeader()->tc=dh->tc;
- pw.getHeader()->rd=dh->rd;
- pw.getHeader()->ra=dh->ra;
- pw.getHeader()->ad=dh->ad;
- pw.getHeader()->cd=dh->cd;
- pw.getHeader()->rcode=dh->rcode;
-
- /* consume remaining qd if any */
- if (qdcount > 1) {
- for(idx = 1; idx < qdcount; idx++) {
- rrname = pr.getName();
- rrtype = pr.get16BitInt();
- rrclass = pr.get16BitInt();
- (void) rrtype;
- (void) rrclass;
- }
- }
-
- /* copy AN and NS */
- for (idx = 0; idx < ancount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- }
-
- for (idx = 0; idx < nscount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- }
- /* consume AR, looking for OPT */
- for (idx = 0; idx < arcount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- if (ah.d_type != QType::OPT) {
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- } else {
-
- pr.skip(ah.d_clen);
- }
- }
- pw.commit();
-
- return 0;
-}
-
-static bool addOrReplaceEDNSOption(std::vector<std::pair<uint16_t, std::string>>& options, uint16_t optionCode, bool& optionAdded, bool overrideExisting, const string& newOptionContent)
-{
- for (auto it = options.begin(); it != options.end(); ) {
- if (it->first == optionCode) {
- optionAdded = false;
-
- if (!overrideExisting) {
- return false;
- }
-
- it = options.erase(it);
- }
- else {
- ++it;
- }
- }
-
- options.emplace_back(optionCode, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)));
- return true;
-}
-
-bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent)
-{
- assert(initialPacket.size() >= sizeof(dnsheader));
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
-
- if (ntohs(dh->qdcount) == 0) {
- return false;
- }
-
- if (ntohs(dh->ancount) == 0 && ntohs(dh->nscount) == 0 && ntohs(dh->arcount) == 0) {
- throw std::runtime_error(std::string(__PRETTY_FUNCTION__) + " should not be called for queries that have no records");
- }
-
- optionAdded = false;
- ednsAdded = true;
-
- PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
-
- size_t idx = 0;
- DNSName rrname;
- uint16_t qdcount = ntohs(dh->qdcount);
- uint16_t ancount = ntohs(dh->ancount);
- uint16_t nscount = ntohs(dh->nscount);
- uint16_t arcount = ntohs(dh->arcount);
- uint16_t rrtype;
- uint16_t rrclass;
- string blob;
- struct dnsrecordheader ah;
-
- rrname = pr.getName();
- rrtype = pr.get16BitInt();
- rrclass = pr.get16BitInt();
-
- GenericDNSPacketWriter<PacketBuffer> pw(newContent, rrname, rrtype, rrclass, dh->opcode);
- pw.getHeader()->id=dh->id;
- pw.getHeader()->qr=dh->qr;
- pw.getHeader()->aa=dh->aa;
- pw.getHeader()->tc=dh->tc;
- pw.getHeader()->rd=dh->rd;
- pw.getHeader()->ra=dh->ra;
- pw.getHeader()->ad=dh->ad;
- pw.getHeader()->cd=dh->cd;
- pw.getHeader()->rcode=dh->rcode;
-
- /* consume remaining qd if any */
- if (qdcount > 1) {
- for(idx = 1; idx < qdcount; idx++) {
- rrname = pr.getName();
- rrtype = pr.get16BitInt();
- rrclass = pr.get16BitInt();
- (void) rrtype;
- (void) rrclass;
- }
- }
-
- /* copy AN and NS */
- for (idx = 0; idx < ancount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- }
-
- for (idx = 0; idx < nscount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- }
-
- /* consume AR, looking for OPT */
- for (idx = 0; idx < arcount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- if (ah.d_type != QType::OPT) {
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- } else {
-
- ednsAdded = false;
- pr.xfrBlob(blob);
-
- std::vector<std::pair<uint16_t, std::string>> options;
- getEDNSOptionsFromContent(blob, options);
-
- /* getDnsrecordheader() has helpfully converted the TTL for us, which we do not want in that case */
- uint32_t ttl = htonl(ah.d_ttl);
- EDNS0Record edns0;
- static_assert(sizeof(edns0) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
- memcpy(&edns0, &ttl, sizeof(edns0));
-
- /* addOrReplaceEDNSOption will set it to false if there is already an existing option */
- optionAdded = true;
- addOrReplaceEDNSOption(options, optionToReplace, optionAdded, overrideExisting, newOptionContent);
- pw.addOpt(ah.d_class, edns0.extRCode, edns0.extFlags, options, edns0.version);
- }
- }
-
- if (ednsAdded) {
- pw.addOpt(g_EdnsUDPPayloadSize, 0, 0, {{optionToReplace, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))}}, 0);
- optionAdded = true;
- }
-
- pw.commit();
-
- return true;
-}
-
-static bool slowParseEDNSOptions(const PacketBuffer& packet, EDNSOptionViewMap& options)
-{
- if (packet.size() < sizeof(dnsheader)) {
- return false;
- }
-
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
-
- if (ntohs(dh->qdcount) == 0) {
- return false;
- }
-
- if (ntohs(dh->arcount) == 0) {
- throw std::runtime_error("slowParseEDNSOptions() should not be called for queries that have no EDNS");
- }
-
- try {
- uint64_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount);
- DNSPacketMangler dpm(const_cast<char*>(reinterpret_cast<const char*>(&packet.at(0))), packet.size());
- uint64_t n;
- for(n=0; n < ntohs(dh->qdcount) ; ++n) {
- dpm.skipDomainName();
- /* type and class */
- dpm.skipBytes(4);
- }
-
- for(n=0; n < numrecords; ++n) {
- dpm.skipDomainName();
-
- uint8_t section = n < ntohs(dh->ancount) ? 1 : (n < (ntohs(dh->ancount) + ntohs(dh->nscount)) ? 2 : 3);
- uint16_t dnstype = dpm.get16BitInt();
- dpm.get16BitInt();
- dpm.skipBytes(4); /* TTL */
-
- if(section == 3 && dnstype == QType::OPT) {
- uint32_t offset = dpm.getOffset();
- if (offset >= packet.size()) {
- return false;
- }
- /* if we survive this call, we can parse it safely */
- dpm.skipRData();
- return getEDNSOptions(reinterpret_cast<const char*>(&packet.at(offset)), packet.size() - offset, options) == 0;
- }
- else {
- dpm.skipRData();
- }
- }
- }
- catch(...)
- {
- return false;
- }
-
- return true;
-}
-
-int locateEDNSOptRR(const PacketBuffer& packet, uint16_t * optStart, size_t * optLen, bool * last)
-{
- assert(optStart != NULL);
- assert(optLen != NULL);
- assert(last != NULL);
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
-
- if (ntohs(dh->arcount) == 0)
- return ENOENT;
-
- PacketReader pr(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()));
-
- size_t idx = 0;
- DNSName rrname;
- uint16_t qdcount = ntohs(dh->qdcount);
- uint16_t ancount = ntohs(dh->ancount);
- uint16_t nscount = ntohs(dh->nscount);
- uint16_t arcount = ntohs(dh->arcount);
- uint16_t rrtype;
- uint16_t rrclass;
- struct dnsrecordheader ah;
-
- /* consume qd */
- for(idx = 0; idx < qdcount; idx++) {
- rrname = pr.getName();
- rrtype = pr.get16BitInt();
- rrclass = pr.get16BitInt();
- (void) rrtype;
- (void) rrclass;
- }
-
- /* consume AN and NS */
- for (idx = 0; idx < ancount + nscount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
- pr.skip(ah.d_clen);
- }
-
- /* consume AR, looking for OPT */
- for (idx = 0; idx < arcount; idx++) {
- uint16_t start = pr.getPosition();
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- if (ah.d_type == QType::OPT) {
- *optStart = start;
- *optLen = (pr.getPosition() - start) + ah.d_clen;
-
- if (packet.size() < (*optStart + *optLen)) {
- throw std::range_error("Opt record overflow");
- }
-
- if (idx == ((size_t) arcount - 1)) {
- *last = true;
- }
- else {
- *last = false;
- }
- return 0;
- }
- pr.skip(ah.d_clen);
- }
-
- return ENOENT;
-}
-
-/* extract the start of the OPT RR in a QUERY packet if any */
-int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t* remaining)
-{
- assert(optRDPosition != nullptr);
- assert(remaining != nullptr);
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
-
- if (offset >= packet.size()) {
- return ENOENT;
- }
-
- if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->arcount) != 1 || ntohs(dh->nscount) != 0)
- return ENOENT;
-
- size_t pos = sizeof(dnsheader) + offset;
- pos += DNS_TYPE_SIZE + DNS_CLASS_SIZE;
-
- if (pos >= packet.size())
- return ENOENT;
-
- if ((pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE) >= packet.size()) {
- return ENOENT;
- }
-
- if (packet[pos] != 0) {
- /* not the root so not an OPT record */
- return ENOENT;
- }
- pos += 1;
-
- uint16_t qtype = packet.at(pos)*256 + packet.at(pos+1);
- pos += DNS_TYPE_SIZE;
- pos += DNS_CLASS_SIZE;
-
- if (qtype != QType::OPT || (packet.size() - pos) < (DNS_TTL_SIZE + DNS_RDLENGTH_SIZE)) {
- return ENOENT;
- }
-
- pos += DNS_TTL_SIZE;
- *optRDPosition = pos;
- *remaining = packet.size() - pos;
-
- return 0;
-}
-
-void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength)
-{
- Netmask sourceNetmask(source, ECSPrefixLength);
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = sourceNetmask;
- string payload = makeEDNSSubnetOptsString(ecsOpts);
- generateEDNSOption(EDNSOptionCode::ECS, payload, res);
-}
-
-bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK)
-{
- const uint8_t name = 0;
- dnsrecordheader dh;
- EDNS0Record edns0;
- edns0.extRCode = ednsrcode;
- edns0.version = 0;
- edns0.extFlags = dnssecOK ? htons(EDNS_HEADER_FLAG_DO) : 0;
-
- if ((maximumSize - res.size()) < (sizeof(name) + sizeof(dh) + optRData.length())) {
- return false;
- }
-
- dh.d_type = htons(QType::OPT);
- dh.d_class = htons(udpPayloadSize);
- static_assert(sizeof(EDNS0Record) == sizeof(dh.d_ttl), "sizeof(EDNS0Record) must match sizeof(dnsrecordheader.d_ttl)");
- memcpy(&dh.d_ttl, &edns0, sizeof edns0);
- dh.d_clen = htons(static_cast<uint16_t>(optRData.length()));
-
- res.reserve(res.size() + sizeof(name) + sizeof(dh) + optRData.length());
- res.insert(res.end(), reinterpret_cast<const uint8_t*>(&name), reinterpret_cast<const uint8_t*>(&name) + sizeof(name));
- res.insert(res.end(), reinterpret_cast<const uint8_t*>(&dh), reinterpret_cast<const uint8_t*>(&dh) + sizeof(dh));
- res.insert(res.end(), reinterpret_cast<const uint8_t*>(optRData.data()), reinterpret_cast<const uint8_t*>(optRData.data()) + optRData.length());
-
- return true;
-}
-
-static bool replaceEDNSClientSubnetOption(PacketBuffer& packet, size_t maximumSize, size_t const oldEcsOptionStartPosition, size_t const oldEcsOptionSize, size_t const optRDLenPosition, const string& newECSOption)
-{
- assert(oldEcsOptionStartPosition < packet.size());
- assert(optRDLenPosition < packet.size());
-
- if (newECSOption.size() == oldEcsOptionSize) {
- /* same size as the existing option */
- memcpy(&packet.at(oldEcsOptionStartPosition), newECSOption.c_str(), oldEcsOptionSize);
- }
- else {
- /* different size than the existing option */
- const unsigned int newPacketLen = packet.size() + (newECSOption.length() - oldEcsOptionSize);
- const size_t beforeOptionLen = oldEcsOptionStartPosition;
- const size_t dataBehindSize = packet.size() - beforeOptionLen - oldEcsOptionSize;
-
- /* check that it fits in the existing buffer */
- if (newPacketLen > packet.size()) {
- if (newPacketLen > maximumSize) {
- return false;
- }
-
- packet.resize(newPacketLen);
- }
-
- /* fix the size of ECS Option RDLen */
- uint16_t newRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1);
- newRDLen += (newECSOption.size() - oldEcsOptionSize);
- packet.at(optRDLenPosition) = newRDLen / 256;
- packet.at(optRDLenPosition + 1) = newRDLen % 256;
-
- if (dataBehindSize > 0) {
- memmove(&packet.at(oldEcsOptionStartPosition), &packet.at(oldEcsOptionStartPosition + oldEcsOptionSize), dataBehindSize);
- }
- memcpy(&packet.at(oldEcsOptionStartPosition + dataBehindSize), newECSOption.c_str(), newECSOption.size());
- packet.resize(newPacketLen);
- }
-
- return true;
-}
-
-/* This function looks for an OPT RR, return true if a valid one was found (even if there was no options)
- and false otherwise. */
-bool parseEDNSOptions(const DNSQuestion& dq)
-{
- const auto dh = dq.getHeader();
- if (dq.ednsOptions != nullptr) {
- return true;
- }
-
- // dq.ednsOptions is mutable
- dq.ednsOptions = std::make_unique<EDNSOptionViewMap>();
-
- if (ntohs(dh->arcount) == 0) {
- /* nothing in additional so no EDNS */
- return false;
- }
-
- if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) > 1) {
- return slowParseEDNSOptions(dq.getData(), *dq.ednsOptions);
- }
-
- size_t remaining = 0;
- uint16_t optRDPosition;
- int res = getEDNSOptionsStart(dq.getData(), dq.ids.qname.wirelength(), &optRDPosition, &remaining);
-
- if (res == 0) {
- res = getEDNSOptions(reinterpret_cast<const char*>(&dq.getData().at(optRDPosition)), remaining, *dq.ednsOptions);
- return (res == 0);
- }
-
- return false;
-}
-
-static bool addECSToExistingOPT(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, size_t optRDLenPosition, bool& ecsAdded)
-{
- /* we need to add one EDNS0 ECS option, fixing the size of EDNS0 RDLENGTH */
- /* getEDNSOptionsStart has already checked that there is exactly one AR,
- no NS and no AN */
- uint16_t oldRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1);
- if (packet.size() != (optRDLenPosition + sizeof(uint16_t) + oldRDLen)) {
- /* we are supposed to be the last record, do we have some trailing data to remove? */
- uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(packet.data()), packet.size());
- packet.resize(realPacketLen);
- }
-
- if ((maximumSize - packet.size()) < newECSOption.size()) {
- return false;
- }
-
- uint16_t newRDLen = oldRDLen + newECSOption.size();
- packet.at(optRDLenPosition) = newRDLen / 256;
- packet.at(optRDLenPosition + 1) = newRDLen % 256;
-
- packet.insert(packet.end(), newECSOption.begin(), newECSOption.end());
- ecsAdded = true;
-
- return true;
-}
-
-static bool addEDNSWithECS(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, bool& ednsAdded, bool& ecsAdded)
-{
- if (!generateOptRR(newECSOption, packet, maximumSize, g_EdnsUDPPayloadSize, 0, false)) {
- return false;
- }
-
- struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(packet.data());
- uint16_t arcount = ntohs(dh->arcount);
- arcount++;
- dh->arcount = htons(arcount);
- ednsAdded = true;
- ecsAdded = true;
-
- return true;
-}
-
-bool handleEDNSClientSubnet(PacketBuffer& packet, const size_t maximumSize, const size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption)
-{
- assert(qnameWireLength <= packet.size());
-
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
-
- if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || (ntohs(dh->arcount) != 0 && ntohs(dh->arcount) != 1)) {
- PacketBuffer newContent;
- newContent.reserve(packet.size());
-
- if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::ECS, ecsAdded, overrideExisting, newECSOption)) {
- return false;
- }
-
- if (newContent.size() > maximumSize) {
- ednsAdded = false;
- ecsAdded = false;
- return false;
- }
-
- packet = std::move(newContent);
- return true;
- }
-
- uint16_t optRDPosition = 0;
- size_t remaining = 0;
-
- int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining);
-
- if (res != 0) {
- /* no EDNS but there might be another record in additional (TSIG?) */
- /* Careful, this code assumes that ANCOUNT == 0 && NSCOUNT == 0 */
- size_t minimumPacketSize = sizeof(dnsheader) + qnameWireLength + sizeof(uint16_t) + sizeof(uint16_t);
- if (packet.size() > minimumPacketSize) {
- if (ntohs(dh->arcount) == 0) {
- /* well now.. */
- packet.resize(minimumPacketSize);
- }
- else {
- uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(packet.data()), packet.size());
- packet.resize(realPacketLen);
- }
- }
-
- return addEDNSWithECS(packet, maximumSize, newECSOption, ednsAdded, ecsAdded);
- }
-
- size_t ecsOptionStartPosition = 0;
- size_t ecsOptionSize = 0;
-
- res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize);
-
- if (res == 0) {
- /* there is already an ECS value */
- if (!overrideExisting) {
- return true;
- }
-
- return replaceEDNSClientSubnetOption(packet, maximumSize, optRDPosition + ecsOptionStartPosition, ecsOptionSize, optRDPosition, newECSOption);
- } else {
- /* we have an EDNS OPT RR but no existing ECS option */
- return addECSToExistingOPT(packet, maximumSize, newECSOption, optRDPosition, ecsAdded);
- }
-
- return true;
-}
-
-bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded)
-{
- string newECSOption;
- generateECSOption(dq.ecs ? dq.ecs->getNetwork() : dq.ids.origRemote, newECSOption, dq.ecs ? dq.ecs->getBits() : dq.ecsPrefixLength);
-
- return handleEDNSClientSubnet(dq.getMutableData(), dq.getMaximumSize(), dq.ids.qname.wirelength(), ednsAdded, ecsAdded, dq.ecsOverride, newECSOption);
-}
-
-static int removeEDNSOptionFromOptions(unsigned char* optionsStart, const uint16_t optionsLen, const uint16_t optionCodeToRemove, uint16_t* newOptionsLen)
-{
- unsigned char* p = optionsStart;
- size_t pos = 0;
- while ((pos + 4) <= optionsLen) {
- unsigned char* optionBegin = p;
- const uint16_t optionCode = 0x100*p[0] + p[1];
- p += sizeof(optionCode);
- pos += sizeof(optionCode);
- const uint16_t optionLen = 0x100*p[0] + p[1];
- p += sizeof(optionLen);
- pos += sizeof(optionLen);
- if ((pos + optionLen) > optionsLen) {
- return EINVAL;
- }
- if (optionCode == optionCodeToRemove) {
- if (pos + optionLen < optionsLen) {
- /* move remaining options over the removed one,
- if any */
- memmove(optionBegin, p + optionLen, optionsLen - (pos + optionLen));
- }
- *newOptionsLen = optionsLen - (sizeof(optionCode) + sizeof(optionLen) + optionLen);
- return 0;
- }
- p += optionLen;
- pos += optionLen;
- }
- return ENOENT;
-}
-
-int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove)
-{
- if (*optLen < optRecordMinimumSize) {
- return EINVAL;
- }
- const unsigned char* end = (const unsigned char*) optStart + *optLen;
- unsigned char* p = (unsigned char*) optStart + 9;
- unsigned char* rdLenPtr = p;
- uint16_t rdLen = (0x100*p[0] + p[1]);
- p += sizeof(rdLen);
- if (p + rdLen != end) {
- return EINVAL;
- }
- uint16_t newRdLen = 0;
- int res = removeEDNSOptionFromOptions(p, rdLen, optionCodeToRemove, &newRdLen);
- if (res != 0) {
- return res;
- }
- *optLen -= (rdLen - newRdLen);
- rdLenPtr[0] = newRdLen / 0x100;
- rdLenPtr[1] = newRdLen % 0x100;
- return 0;
-}
-
-bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart, uint16_t* optContentLen)
-{
- if (optLen < optRecordMinimumSize) {
- return false;
- }
- size_t p = optStart + 9;
- uint16_t rdLen = (0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1)));
- p += sizeof(rdLen);
- if (rdLen > (optLen - optRecordMinimumSize)) {
- return false;
- }
-
- size_t rdEnd = p + rdLen;
- while ((p + 4) <= rdEnd) {
- const uint16_t optionCode = 0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1));
- p += sizeof(optionCode);
- const uint16_t optionLen = 0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1));
- p += sizeof(optionLen);
-
- if ((p + optionLen) > rdEnd) {
- return false;
- }
-
- if (optionCode == optionCodeToFind) {
- if (optContentStart != nullptr) {
- *optContentStart = p;
- }
-
- if (optContentLen != nullptr) {
- *optContentLen = optionLen;
- }
-
- return true;
- }
- p += optionLen;
- }
- return false;
-}
-
-int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent)
-{
- assert(initialPacket.size() >= sizeof(dnsheader));
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
-
- if (ntohs(dh->arcount) == 0)
- return ENOENT;
-
- if (ntohs(dh->qdcount) == 0)
- return ENOENT;
-
- PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
-
- size_t idx = 0;
- DNSName rrname;
- uint16_t qdcount = ntohs(dh->qdcount);
- uint16_t ancount = ntohs(dh->ancount);
- uint16_t nscount = ntohs(dh->nscount);
- uint16_t arcount = ntohs(dh->arcount);
- uint16_t rrtype;
- uint16_t rrclass;
- string blob;
- struct dnsrecordheader ah;
-
- rrname = pr.getName();
- rrtype = pr.get16BitInt();
- rrclass = pr.get16BitInt();
-
- GenericDNSPacketWriter<PacketBuffer> pw(newContent, rrname, rrtype, rrclass, dh->opcode);
- pw.getHeader()->id=dh->id;
- pw.getHeader()->qr=dh->qr;
- pw.getHeader()->aa=dh->aa;
- pw.getHeader()->tc=dh->tc;
- pw.getHeader()->rd=dh->rd;
- pw.getHeader()->ra=dh->ra;
- pw.getHeader()->ad=dh->ad;
- pw.getHeader()->cd=dh->cd;
- pw.getHeader()->rcode=dh->rcode;
-
- /* consume remaining qd if any */
- if (qdcount > 1) {
- for(idx = 1; idx < qdcount; idx++) {
- rrname = pr.getName();
- rrtype = pr.get16BitInt();
- rrclass = pr.get16BitInt();
- (void) rrtype;
- (void) rrclass;
- }
- }
-
- /* copy AN and NS */
- for (idx = 0; idx < ancount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- }
-
- for (idx = 0; idx < nscount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- }
-
- /* consume AR, looking for OPT */
- for (idx = 0; idx < arcount; idx++) {
- rrname = pr.getName();
- pr.getDnsrecordheader(ah);
-
- if (ah.d_type != QType::OPT) {
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true);
- pr.xfrBlob(blob);
- pw.xfrBlob(blob);
- } else {
- pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, false);
- pr.xfrBlob(blob);
- uint16_t rdLen = blob.length();
- removeEDNSOptionFromOptions((unsigned char*)blob.c_str(), rdLen, optionCodeToSkip, &rdLen);
- /* xfrBlob(string, size) completely ignores size.. */
- if (rdLen > 0) {
- blob.resize((size_t)rdLen);
- pw.xfrBlob(blob);
- } else {
- pw.commit();
- }
- }
- }
- pw.commit();
-
- return 0;
-}
-
-bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode)
-{
- if (!generateOptRR(std::string(), packet, maximumSize, payloadSize, ednsrcode, dnssecOK)) {
- return false;
- }
-
- auto dh = reinterpret_cast<dnsheader*>(packet.data());
- dh->arcount = htons(ntohs(dh->arcount) + 1);
-
- return true;
-}
-
-/*
- This function keeps the existing header and DNSSECOK bit (if any) but wipes anything else,
- generating a NXD or NODATA answer with a SOA record in the additional section (or optionally the authority section for a full cacheable NXDOMAIN/NODATA).
-*/
-bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection)
-{
- auto& packet = dq.getMutableData();
- auto dh = dq.getHeader();
- if (ntohs(dh->qdcount) != 1) {
- return false;
- }
-
- size_t queryPartSize = sizeof(dnsheader) + dq.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
- if (packet.size() < queryPartSize) {
- /* something is already wrong, don't build on flawed foundations */
- return false;
- }
-
- uint16_t qtype = htons(QType::SOA);
- uint16_t qclass = htons(QClass::IN);
- uint16_t rdLength = mname.wirelength() + rname.wirelength() + sizeof(serial) + sizeof(refresh) + sizeof(retry) + sizeof(expire) + sizeof(minimum);
- size_t soaSize = zone.wirelength() + sizeof(qtype) + sizeof(qclass) + sizeof(ttl) + sizeof(rdLength) + rdLength;
- bool hadEDNS = false;
- bool dnssecOK = false;
-
- if (g_addEDNSToSelfGeneratedResponses) {
- uint16_t payloadSize = 0;
- uint16_t z = 0;
- hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &z);
- if (hadEDNS) {
- dnssecOK = z & EDNS_HEADER_FLAG_DO;
- }
- }
-
- /* chop off everything after the question */
- packet.resize(queryPartSize);
- dh = dq.getHeader();
- if (nxd) {
- dh->rcode = RCode::NXDomain;
- }
- else {
- dh->rcode = RCode::NoError;
- }
- dh->qr = true;
- dh->ancount = 0;
- dh->nscount = 0;
- dh->arcount = 0;
-
- rdLength = htons(rdLength);
- ttl = htonl(ttl);
- serial = htonl(serial);
- refresh = htonl(refresh);
- retry = htonl(retry);
- expire = htonl(expire);
- minimum = htonl(minimum);
-
- std::string soa;
- soa.reserve(soaSize);
- soa.append(zone.toDNSString());
- soa.append(reinterpret_cast<const char*>(&qtype), sizeof(qtype));
- soa.append(reinterpret_cast<const char*>(&qclass), sizeof(qclass));
- soa.append(reinterpret_cast<const char*>(&ttl), sizeof(ttl));
- soa.append(reinterpret_cast<const char*>(&rdLength), sizeof(rdLength));
- soa.append(mname.toDNSString());
- soa.append(rname.toDNSString());
- soa.append(reinterpret_cast<const char*>(&serial), sizeof(serial));
- soa.append(reinterpret_cast<const char*>(&refresh), sizeof(refresh));
- soa.append(reinterpret_cast<const char*>(&retry), sizeof(retry));
- soa.append(reinterpret_cast<const char*>(&expire), sizeof(expire));
- soa.append(reinterpret_cast<const char*>(&minimum), sizeof(minimum));
-
- if (soa.size() != soaSize) {
- throw std::runtime_error("Unexpected SOA response size: " + std::to_string(soa.size()) + " vs " + std::to_string(soaSize));
- }
-
- packet.insert(packet.end(), soa.begin(), soa.end());
- dh = dq.getHeader();
-
- /* We are populating a response with only the query in place, order of sections is QD,AN,NS,AR
- NS (authority) is before AR (additional) so we can just decide which section the SOA record is in here
- and have EDNS added to AR afterwards */
- if (soaInAuthoritySection) {
- dh->nscount = htons(1);
- } else {
- dh->arcount = htons(1);
- }
-
- if (hadEDNS) {
- /* now we need to add a new OPT record */
- return addEDNS(packet, dq.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dq.ednsRCode);
- }
-
- return true;
-}
-
-bool addEDNSToQueryTurnedResponse(DNSQuestion& dq)
-{
- uint16_t optRDPosition;
- /* remaining is at least the size of the rdlen + the options if any + the following records if any */
- size_t remaining = 0;
-
- auto& packet = dq.getMutableData();
- int res = getEDNSOptionsStart(packet, dq.ids.qname.wirelength(), &optRDPosition, &remaining);
-
- if (res != 0) {
- /* if the initial query did not have EDNS0, we are done */
- return true;
- }
-
- const size_t existingOptLen = /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2 + remaining;
- if (existingOptLen >= packet.size()) {
- /* something is wrong, bail out */
- return false;
- }
-
- uint8_t* optRDLen = &packet.at(optRDPosition);
- uint8_t* optPtr = (optRDLen - (/* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2));
-
- const uint8_t* zPtr = optPtr + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE;
- uint16_t z = 0x100 * (*zPtr) + *(zPtr + 1);
- bool dnssecOK = z & EDNS_HEADER_FLAG_DO;
-
- /* remove the existing OPT record, and everything else that follows (any SIG or TSIG would be useless anyway) */
- packet.resize(packet.size() - existingOptLen);
- dq.getHeader()->arcount = 0;
-
- if (g_addEDNSToSelfGeneratedResponses) {
- /* now we need to add a new OPT record */
- return addEDNS(packet, dq.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dq.ednsRCode);
- }
-
- /* otherwise we are just fine */
- return true;
-}
-
-// goal in life - if you send us a reasonably normal packet, we'll get Z for you, otherwise 0
-int getEDNSZ(const DNSQuestion& dq)
-{
- try
- {
- const auto& dh = dq.getHeader();
- if (ntohs(dh->qdcount) != 1 || dh->ancount != 0 || ntohs(dh->arcount) != 1 || dh->nscount != 0) {
- return 0;
- }
-
- if (dq.getData().size() <= sizeof(dnsheader)) {
- return 0;
- }
-
- size_t pos = sizeof(dnsheader) + dq.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
-
- if (dq.getData().size() <= (pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE)) {
- return 0;
- }
-
- auto& packet = dq.getData();
-
- if (packet.at(pos) != 0) {
- /* not root, so not a valid OPT record */
- return 0;
- }
-
- pos++;
-
- uint16_t qtype = packet.at(pos)*256 + packet.at(pos+1);
- pos += DNS_TYPE_SIZE;
- pos += DNS_CLASS_SIZE;
-
- if (qtype != QType::OPT || (pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + 1) >= packet.size()) {
- return 0;
- }
-
- const uint8_t* z = &packet.at(pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE);
- return 0x100 * (*z) + *(z+1);
- }
- catch(...)
- {
- return 0;
- }
-}
-
-bool queryHasEDNS(const DNSQuestion& dq)
-{
- uint16_t optRDPosition;
- size_t ecsRemaining = 0;
-
- int res = getEDNSOptionsStart(dq.getData(), dq.ids.qname.wirelength(), &optRDPosition, &ecsRemaining);
- if (res == 0) {
- return true;
- }
-
- return false;
-}
-
-bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0)
-{
- uint16_t optStart;
- size_t optLen = 0;
- bool last = false;
- int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
- if (res != 0) {
- // no EDNS OPT RR
- return false;
- }
-
- if (optLen < optRecordMinimumSize) {
- return false;
- }
-
- if (optStart < packet.size() && packet.at(optStart) != 0) {
- // OPT RR Name != '.'
- return false;
- }
-
- static_assert(sizeof(EDNS0Record) == sizeof(uint32_t), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
- // copy out 4-byte "ttl" (really the EDNS0 record), after root label (1) + type (2) + class (2).
- memcpy(&edns0, &packet.at(optStart + 5), sizeof edns0);
- return true;
-}
-
-bool setEDNSOption(DNSQuestion& dq, uint16_t ednsCode, const std::string& ednsData)
-{
- std::string optRData;
- generateEDNSOption(ednsCode, ednsData, optRData);
-
- if (dq.getHeader()->arcount) {
- bool ednsAdded = false;
- bool optionAdded = false;
- PacketBuffer newContent;
- newContent.reserve(dq.getData().size());
-
- if (!slowRewriteEDNSOptionInQueryWithRecords(dq.getData(), newContent, ednsAdded, ednsCode, optionAdded, true, optRData)) {
- return false;
- }
-
- if (newContent.size() > dq.getMaximumSize()) {
- return false;
- }
-
- dq.getMutableData() = std::move(newContent);
- if (!dq.ids.ednsAdded && ednsAdded) {
- dq.ids.ednsAdded = true;
- }
-
- return true;
- }
-
- auto& data = dq.getMutableData();
- if (generateOptRR(optRData, data, dq.getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) {
- dq.getHeader()->arcount = htons(1);
- // make sure that any EDNS sent by the backend is removed before forwarding the response to the client
- dq.ids.ednsAdded = true;
- }
-
- return true;
-}
-
-namespace dnsdist {
-bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer, uint8_t rcode, bool clearAnswers)
-{
- const auto qnameLength = state.qname.wirelength();
- if (buffer.size() < sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t)) {
- return false;
- }
-
- EDNS0Record edns0;
- bool hadEDNS = false;
- if (clearAnswers) {
- hadEDNS = getEDNS0Record(buffer, edns0);
- }
-
- auto dh = reinterpret_cast<dnsheader*>(buffer.data());
- dh->rcode = rcode;
- dh->ad = false;
- dh->aa = false;
- dh->ra = dh->rd;
- dh->qr = true;
-
- if (clearAnswers) {
- dh->ancount = 0;
- dh->nscount = 0;
- dh->arcount = 0;
- buffer.resize(sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t));
- if (hadEDNS) {
- DNSQuestion dq(state, buffer);
- if (!addEDNS(buffer, dq.getMaximumSize(), edns0.extFlags & htons(EDNS_HEADER_FLAG_DO), g_PayloadSizeSelfGenAnswers, 0)) {
- return false;
- }
- }
- }
-
- return true;
-}
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-#include <string>
-
-#include "iputils.hh"
-#include "noinitvector.hh"
-
-struct DNSQuestion;
-
-// root label (1), type (2), class (2), ttl (4) + rdlen (2)
-static const size_t optRecordMinimumSize = 11;
-
-extern size_t g_EdnsUDPPayloadSize;
-extern uint16_t g_PayloadSizeSelfGenAnswers;
-
-int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent);
-bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent);
-int locateEDNSOptRR(const PacketBuffer & packet, uint16_t * optStart, size_t * optLen, bool * last);
-bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK);
-void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength);
-int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove);
-int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent);
-int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t * remaining);
-bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart = nullptr, uint16_t* optContentLen = nullptr);
-bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode);
-bool addEDNSToQueryTurnedResponse(DNSQuestion& dq);
-bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection);
-
-bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded);
-bool handleEDNSClientSubnet(PacketBuffer& packet, size_t maximumSize, size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption);
-
-bool parseEDNSOptions(const DNSQuestion& dq);
-
-int getEDNSZ(const DNSQuestion& dq);
-bool queryHasEDNS(const DNSQuestion& dq);
-bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0);
-
-bool setEDNSOption(DNSQuestion& dq, uint16_t ednsCode, const std::string& data);
-
-namespace dnsdist {
-bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer, uint8_t rcode, bool clearAnswers);
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-#include "config.h"
-#include "dnsname.hh"
-#include "dnsdist-protocols.hh"
-#include "gettime.hh"
-#include "iputils.hh"
-#include "uuid-utils.hh"
-
-struct ClientState;
-struct DOHUnit;
-class DNSCryptQuery;
-class DNSDistPacketCache;
-
-using QTag = std::unordered_map<string, string>;
-
-struct StopWatch
-{
- StopWatch(bool realTime = false) :
- d_needRealTime(realTime)
- {
- }
-
- void start()
- {
- d_start = getCurrentTime();
- }
-
- void set(const struct timespec& from)
- {
- d_start = from;
- }
-
- double udiff() const
- {
- struct timespec now = getCurrentTime();
- return 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
- }
-
- double udiffAndSet()
- {
- struct timespec now = getCurrentTime();
- auto ret = 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
- d_start = now;
- return ret;
- }
-
- struct timespec getStartTime() const
- {
- return d_start;
- }
-
- struct timespec d_start
- {
- 0, 0
- };
-
-private:
- struct timespec getCurrentTime() const
- {
- struct timespec now;
- if (gettime(&now, d_needRealTime) < 0) {
- unixDie("Getting timestamp");
- }
- return now;
- }
-
- bool d_needRealTime;
-};
-
-struct InternalQueryState
-{
- struct ProtoBufData
- {
- std::optional<boost::uuids::uuid> uniqueId{std::nullopt}; // 17
- std::string d_deviceName;
- std::string d_deviceID;
- std::string d_requestorID;
- };
-
- static void DeleterPlaceHolder(DOHUnit*)
- {
- }
-
- InternalQueryState() :
- du(std::unique_ptr<DOHUnit, void (*)(DOHUnit*)>(nullptr, DeleterPlaceHolder))
- {
- origDest.sin4.sin_family = 0;
- }
-
- InternalQueryState(InternalQueryState&& rhs) = default;
- InternalQueryState& operator=(InternalQueryState&& rhs) = default;
-
- InternalQueryState(const InternalQueryState& orig) = delete;
- InternalQueryState& operator=(const InternalQueryState& orig) = delete;
-
- boost::optional<Netmask> subnet{boost::none}; // 40
- ComboAddress origRemote; // 28
- ComboAddress origDest; // 28
- ComboAddress hopRemote;
- ComboAddress hopLocal;
- DNSName qname; // 24
- std::string poolName; // 24
- StopWatch queryRealTime{true}; // 24
- std::shared_ptr<DNSDistPacketCache> packetCache{nullptr}; // 16
- std::unique_ptr<DNSCryptQuery> dnsCryptQuery{nullptr}; // 8
- std::unique_ptr<QTag> qTag{nullptr}; // 8
- std::unique_ptr<PacketBuffer> d_packet{nullptr}; // Initial packet, so we can restart the query from the response path if needed // 8
- std::unique_ptr<ProtoBufData> d_protoBufData{nullptr};
- boost::optional<uint32_t> tempFailureTTL{boost::none}; // 8
- ClientState* cs{nullptr}; // 8
- std::unique_ptr<DOHUnit, void (*)(DOHUnit*)> du; // 8
- uint32_t cacheKey{0}; // 4
- uint32_t cacheKeyNoECS{0}; // 4
- // DoH-only */
- uint32_t cacheKeyUDP{0}; // 4
- uint32_t ttlCap{0}; // cap the TTL _after_ inserting into the packet cache // 4
- int backendFD{-1}; // 4
- int delayMsec{0};
- uint16_t qtype{0}; // 2
- uint16_t qclass{0}; // 2
- // origID is in network-byte order
- uint16_t origID{0}; // 2
- uint16_t origFlags{0}; // 2
- uint16_t cacheFlags{0}; // DNS flags as sent to the backend // 2
- uint16_t udpPayloadSize{0}; // Max UDP payload size from the query // 2
- dnsdist::Protocol protocol; // 1
- bool ednsAdded{false};
- bool ecsAdded{false};
- bool skipCache{false};
- bool dnssecOK{false};
- bool useZeroScope{false};
- bool forwardedOverUDP{false};
- bool selfGenerated{false};
-};
-
-struct IDState
-{
- IDState()
- {
- }
-
- IDState(const IDState& orig) = delete;
- IDState(IDState&& rhs) noexcept :
- internal(std::move(rhs.internal))
- {
- inUse.store(rhs.inUse.load());
- age.store(rhs.age.load());
- }
-
- IDState& operator=(IDState&& rhs) noexcept
- {
- inUse.store(rhs.inUse.load());
- age.store(rhs.age.load());
- internal = std::move(rhs.internal);
- return *this;
- }
-
- bool isInUse() const
- {
- return inUse;
- }
-
- /* For performance reasons we don't want to use a lock here, but that means
- we need to be very careful when modifying this value. Modifications happen
- from:
- - one of the UDP or DoH 'client' threads receiving a query, selecting a backend
- then picking one of the states associated to this backend (via the idOffset).
- Most of the time this state should not be in use and usageIndicator is -1, but we
- might not yet have received a response for the query previously associated to this
- state, meaning that we will 'reuse' this state and erase the existing state.
- If we ever receive a response for this state, it will be discarded. This is
- mostly fine for UDP except that we still need to be careful in order to miss
- the 'outstanding' counters, which should only be increased when we are picking
- an empty state, and not when reusing ;
- For DoH, though, we have dynamically allocated a DOHUnit object that needs to
- be freed, as well as internal objects internals to libh2o.
- - one of the UDP receiver threads receiving a response from a backend, picking
- the corresponding state and sending the response to the client ;
- - the 'healthcheck' thread scanning the states to actively discover timeouts,
- mostly to keep some counters like the 'outstanding' one sane.
-
- We have two flags:
- - inUse tells us if there currently is a in-flight query whose state is stored
- in this state
- - locked tells us whether someone currently owns the state, so no-one else can touch
- it
- */
- InternalQueryState internal;
- std::atomic<uint16_t> age{0};
-
- class StateGuard
- {
- public:
- StateGuard(IDState& ids) :
- d_ids(ids)
- {
- }
- ~StateGuard()
- {
- d_ids.release();
- }
- StateGuard(const StateGuard&) = delete;
- StateGuard(StateGuard&&) = delete;
- StateGuard& operator=(const StateGuard&) = delete;
- StateGuard& operator=(StateGuard&&) = delete;
-
- private:
- IDState& d_ids;
- };
-
- [[nodiscard]] std::optional<StateGuard> acquire()
- {
- bool expected = false;
- if (locked.compare_exchange_strong(expected, true)) {
- return std::optional<StateGuard>(*this);
- }
- return std::nullopt;
- }
-
- void release()
- {
- locked.store(false);
- }
-
- std::atomic<bool> inUse{false}; // 1
-
-private:
- std::atomic<bool> locked{false}; // 1
-};
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-struct dnsdist_ffi_servers_list_t;
-struct dnsdist_ffi_server_t;
-struct dnsdist_ffi_dnsquestion_t;
-
-struct DownstreamState;
-
-struct PerThreadPoliciesState;
-
-class ServerPolicy
-{
-public:
- template <class T> using NumberedVector = std::vector<std::pair<unsigned int, T> >;
- using NumberedServerVector = NumberedVector<shared_ptr<DownstreamState>>;
- typedef std::function<shared_ptr<DownstreamState>(const NumberedServerVector& servers, const DNSQuestion*)> policyfunc_t;
- typedef std::function<unsigned int(dnsdist_ffi_servers_list_t* servers, dnsdist_ffi_dnsquestion_t* dq)> ffipolicyfunc_t;
-
- ServerPolicy(const std::string& name_, policyfunc_t policy_, bool isLua_): d_name(name_), d_policy(policy_), d_isLua(isLua_)
- {
- }
-
- ServerPolicy(const std::string& name_, ffipolicyfunc_t policy_): d_name(name_), d_ffipolicy(policy_), d_isLua(true), d_isFFI(true)
- {
- }
-
- /* create a per-thread FFI policy */
- ServerPolicy(const std::string& name_, const std::string& code);
-
- ServerPolicy()
- {
- }
-
- std::shared_ptr<DownstreamState> getSelectedBackend(const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dq) const;
-
- const std::string& getName() const
- {
- return d_name;
- }
-
- std::string toString() const {
- return string("ServerPolicy") + (d_isLua ? " (Lua)" : "") + " \"" + d_name + "\"";
- }
-
-private:
- struct PerThreadState
- {
- LuaContext d_luaContext;
- std::unordered_map<std::string, ffipolicyfunc_t> d_policies;
- bool d_initialized{false};
- };
-
- const ffipolicyfunc_t& getPerThreadPolicy() const;
- static thread_local PerThreadState t_perThreadState;
-
-
-public:
- std::string d_name;
- std::string d_perThreadPolicyCode;
-
- policyfunc_t d_policy;
- ffipolicyfunc_t d_ffipolicy;
-
- bool d_isLua{false};
- bool d_isFFI{false};
- bool d_isPerThread{false};
-};
-
-struct ServerPool;
-
-using pools_t = map<std::string, std::shared_ptr<ServerPool>>;
-std::shared_ptr<ServerPool> getPool(const pools_t& pools, const std::string& poolName);
-std::shared_ptr<ServerPool> createPoolIfNotExists(pools_t& pools, const string& poolName);
-void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr<ServerPolicy> policy);
-void addServerToPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server);
-void removeServerFromPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server);
-
-const std::shared_ptr<const ServerPolicy::NumberedServerVector> getDownstreamCandidates(const map<std::string,std::shared_ptr<ServerPool>>& pools, const std::string& poolName);
-
-std::shared_ptr<DownstreamState> firstAvailable(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-
-std::shared_ptr<DownstreamState> leastOutstanding(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-std::shared_ptr<DownstreamState> wrandom(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-std::shared_ptr<DownstreamState> whashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-std::shared_ptr<DownstreamState> whashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash);
-std::shared_ptr<DownstreamState> chashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-std::shared_ptr<DownstreamState> chashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash);
-std::shared_ptr<DownstreamState> roundrobin(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-
-extern double g_consistentHashBalancingFactor;
-extern double g_weightedBalancingFactor;
-extern uint32_t g_hashperturb;
-extern bool g_roundrobinFailOnNoServer;
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "config.h"
-#include "threadname.hh"
-#include "dnsdist.hh"
-#include "dnsdist-async.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-lua-ffi.hh"
-#include "dnsdist-mac-address.hh"
-#include "dnsdist-protobuf.hh"
-#include "dnsdist-kvs.hh"
-#include "dnsdist-svc.hh"
-
-#include "dnstap.hh"
-#include "dnswriter.hh"
-#include "ednsoptions.hh"
-#include "fstrm_logger.hh"
-#include "remote_logger.hh"
-#include "svc-records.hh"
-
-#include <boost/optional/optional_io.hpp>
-
-#include "ipcipher.hh"
-
-class DropAction : public DNSAction
-{
-public:
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- return Action::Drop;
- }
- std::string toString() const override
- {
- return "drop";
- }
-};
-
-class AllowAction : public DNSAction
-{
-public:
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- return Action::Allow;
- }
- std::string toString() const override
- {
- return "allow";
- }
-};
-
-class NoneAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- return Action::None;
- }
- std::string toString() const override
- {
- return "no op";
- }
-};
-
-class QPSAction : public DNSAction
-{
-public:
- QPSAction(int limit) : d_qps(QPSLimiter(limit, limit))
- {
- }
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (d_qps.lock()->check()) {
- return Action::None;
- }
- else {
- return Action::Drop;
- }
- }
- std::string toString() const override
- {
- return "qps limit to "+std::to_string(d_qps.lock()->getRate());
- }
-private:
- mutable LockGuarded<QPSLimiter> d_qps;
-};
-
-class DelayAction : public DNSAction
-{
-public:
- DelayAction(int msec) : d_msec(msec)
- {
- }
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- *ruleresult = std::to_string(d_msec);
- return Action::Delay;
- }
- std::string toString() const override
- {
- return "delay by "+std::to_string(d_msec)+ " ms";
- }
-private:
- int d_msec;
-};
-
-class TeeAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- TeeAction(const ComboAddress& rca, const boost::optional<ComboAddress>& lca, bool addECS=false);
- ~TeeAction() override;
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override;
- std::string toString() const override;
- std::map<std::string, double> getStats() const override;
-
-private:
- ComboAddress d_remote;
- std::thread d_worker;
- void worker();
-
- int d_fd{-1};
- mutable std::atomic<unsigned long> d_senderrors{0};
- unsigned long d_recverrors{0};
- mutable std::atomic<unsigned long> d_queries{0};
- stat_t d_responses{0};
- stat_t d_nxdomains{0};
- stat_t d_servfails{0};
- stat_t d_refuseds{0};
- stat_t d_formerrs{0};
- stat_t d_notimps{0};
- stat_t d_noerrors{0};
- mutable stat_t d_tcpdrops{0};
- stat_t d_otherrcode{0};
- std::atomic<bool> d_pleaseQuit{false};
- bool d_addECS{false};
-};
-
-TeeAction::TeeAction(const ComboAddress& rca, const boost::optional<ComboAddress>& lca, bool addECS)
- : d_remote(rca), d_addECS(addECS)
-{
- d_fd=SSocket(d_remote.sin4.sin_family, SOCK_DGRAM, 0);
- try {
- if (lca) {
- SBind(d_fd, *lca);
- }
- SConnect(d_fd, d_remote);
- setNonBlocking(d_fd);
- d_worker=std::thread([this](){worker();});
- }
- catch (...) {
- if (d_fd != -1) {
- close(d_fd);
- }
- throw;
- }
-}
-
-TeeAction::~TeeAction()
-{
- d_pleaseQuit=true;
- close(d_fd);
- d_worker.join();
-}
-
-DNSAction::Action TeeAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
-{
- if (dq->overTCP()) {
- d_tcpdrops++;
- }
- else {
- ssize_t res;
- d_queries++;
-
- if(d_addECS) {
- PacketBuffer query(dq->getData());
- bool ednsAdded = false;
- bool ecsAdded = false;
-
- std::string newECSOption;
- generateECSOption(dq->ecs ? dq->ecs->getNetwork() : dq->ids.origRemote, newECSOption, dq->ecs ? dq->ecs->getBits() : dq->ecsPrefixLength);
-
- if (!handleEDNSClientSubnet(query, dq->getMaximumSize(), dq->ids.qname.wirelength(), ednsAdded, ecsAdded, dq->ecsOverride, newECSOption)) {
- return DNSAction::Action::None;
- }
-
- res = send(d_fd, query.data(), query.size(), 0);
- }
- else {
- res = send(d_fd, dq->getData().data(), dq->getData().size(), 0);
- }
-
- if (res <= 0) {
- d_senderrors++;
- }
- }
-
- return DNSAction::Action::None;
-}
-
-std::string TeeAction::toString() const
-{
- return "tee to "+d_remote.toStringWithPort();
-}
-
-std::map<std::string,double> TeeAction::getStats() const
-{
- return {{"queries", d_queries},
- {"responses", d_responses},
- {"recv-errors", d_recverrors},
- {"send-errors", d_senderrors},
- {"noerrors", d_noerrors},
- {"nxdomains", d_nxdomains},
- {"refuseds", d_refuseds},
- {"servfails", d_servfails},
- {"other-rcode", d_otherrcode},
- {"tcp-drops", d_tcpdrops}
- };
-}
-
-void TeeAction::worker()
-{
- setThreadName("dnsdist/TeeWork");
- char packet[1500];
- int res=0;
- struct dnsheader* dh=(struct dnsheader*)packet;
- for(;;) {
- res=waitForData(d_fd, 0, 250000);
- if(d_pleaseQuit)
- break;
- if(res < 0) {
- usleep(250000);
- continue;
- }
- if(res==0)
- continue;
- res=recv(d_fd, packet, sizeof(packet), 0);
- if(res <= (int)sizeof(struct dnsheader))
- d_recverrors++;
- else
- d_responses++;
-
- if(dh->rcode == RCode::NoError)
- d_noerrors++;
- else if(dh->rcode == RCode::ServFail)
- d_servfails++;
- else if(dh->rcode == RCode::NXDomain)
- d_nxdomains++;
- else if(dh->rcode == RCode::Refused)
- d_refuseds++;
- else if(dh->rcode == RCode::FormErr)
- d_formerrs++;
- else if(dh->rcode == RCode::NotImp)
- d_notimps++;
- }
-}
-
-class PoolAction : public DNSAction
-{
-public:
- PoolAction(const std::string& pool, bool stopProcessing) : d_pool(pool), d_stopProcessing(stopProcessing) {}
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (d_stopProcessing) {
- /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */
- *ruleresult = d_pool;
- return Action::Pool;
- }
- else {
- dq->ids.poolName = d_pool;
- return Action::None;
- }
- }
-
- std::string toString() const override
- {
- return "to pool " + d_pool;
- }
-
-private:
- const std::string d_pool;
- const bool d_stopProcessing;
-};
-
-
-class QPSPoolAction : public DNSAction
-{
-public:
- QPSPoolAction(unsigned int limit, const std::string& pool, bool stopProcessing) : d_qps(QPSLimiter(limit, limit)), d_pool(pool), d_stopProcessing(stopProcessing) {}
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (d_qps.lock()->check()) {
- if (d_stopProcessing) {
- /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */
- *ruleresult = d_pool;
- return Action::Pool;
- }
- else {
- dq->ids.poolName = d_pool;
- return Action::None;
- }
- }
- else {
- return Action::None;
- }
- }
- std::string toString() const override
- {
- return "max " + std::to_string(d_qps.lock()->getRate()) + " to pool " + d_pool;
- }
-
-private:
- mutable LockGuarded<QPSLimiter> d_qps;
- const std::string d_pool;
- const bool d_stopProcessing;
-};
-
-class RCodeAction : public DNSAction
-{
-public:
- RCodeAction(uint8_t rcode) : d_rcode(rcode) {}
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->getHeader()->rcode = d_rcode;
- dq->getHeader()->qr = true; // for good measure
- setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
- return Action::HeaderModify;
- }
- std::string toString() const override
- {
- return "set rcode "+std::to_string(d_rcode);
- }
-
- ResponseConfig d_responseConfig;
-private:
- uint8_t d_rcode;
-};
-
-class ERCodeAction : public DNSAction
-{
-public:
- ERCodeAction(uint8_t rcode) : d_rcode(rcode) {}
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->getHeader()->rcode = (d_rcode & 0xF);
- dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
- dq->getHeader()->qr = true; // for good measure
- setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
- return Action::HeaderModify;
- }
- std::string toString() const override
- {
- return "set ercode "+ERCode::to_s(d_rcode);
- }
-
- ResponseConfig d_responseConfig;
-private:
- uint8_t d_rcode;
-};
-
-class SpoofSVCAction : public DNSAction
-{
-public:
- SpoofSVCAction(const LuaArray<SVCRecordParameters>& parameters)
- {
- d_payloads.reserve(parameters.size());
-
- for (const auto& param : parameters) {
- std::vector<uint8_t> payload;
- if (!generateSVCPayload(payload, param.second)) {
- throw std::runtime_error("Unable to generate a valid SVC record from the supplied parameters");
- }
-
- d_totalPayloadsSize += payload.size();
- d_payloads.push_back(std::move(payload));
-
- for (const auto& hint : param.second.ipv4hints) {
- d_additionals4.insert({ param.second.target, ComboAddress(hint) });
- }
-
- for (const auto& hint : param.second.ipv6hints) {
- d_additionals6.insert({ param.second.target, ComboAddress(hint) });
- }
- }
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- /* it will likely be a bit bigger than that because of additionals */
- uint16_t numberOfRecords = d_payloads.size();
- const auto qnameWireLength = dq->ids.qname.wirelength();
- if (dq->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + d_totalPayloadsSize)) {
- return Action::None;
- }
-
- PacketBuffer newPacket;
- newPacket.reserve(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + d_totalPayloadsSize);
- GenericDNSPacketWriter<PacketBuffer> pw(newPacket, dq->ids.qname, dq->ids.qtype);
- for (const auto& payload : d_payloads) {
- pw.startRecord(dq->ids.qname, dq->ids.qtype, d_responseConfig.ttl);
- pw.xfrBlob(payload);
- pw.commit();
- }
-
- if (newPacket.size() < dq->getMaximumSize()) {
- for (const auto& additional : d_additionals4) {
- pw.startRecord(additional.first.isRoot() ? dq->ids.qname : additional.first, QType::A, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
- pw.xfrCAWithoutPort(4, additional.second);
- pw.commit();
- }
- }
-
- if (newPacket.size() < dq->getMaximumSize()) {
- for (const auto& additional : d_additionals6) {
- pw.startRecord(additional.first.isRoot() ? dq->ids.qname : additional.first, QType::AAAA, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
- pw.xfrCAWithoutPort(6, additional.second);
- pw.commit();
- }
- }
-
- if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dq)) {
- bool dnssecOK = getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO;
- pw.addOpt(g_PayloadSizeSelfGenAnswers, 0, dnssecOK ? EDNS_HEADER_FLAG_DO : 0);
- pw.commit();
- }
-
- if (newPacket.size() >= dq->getMaximumSize()) {
- /* sorry! */
- return Action::None;
- }
-
- pw.getHeader()->id = dq->getHeader()->id;
- pw.getHeader()->qr = true; // for good measure
- setResponseHeadersFromConfig(*pw.getHeader(), d_responseConfig);
- dq->getMutableData() = std::move(newPacket);
-
- return Action::HeaderModify;
- }
- std::string toString() const override
- {
- return "spoof SVC record ";
- }
-
- ResponseConfig d_responseConfig;
-private:
- std::vector<std::vector<uint8_t>> d_payloads;
- std::set<std::pair<DNSName, ComboAddress>> d_additionals4;
- std::set<std::pair<DNSName, ComboAddress>> d_additionals6;
- size_t d_totalPayloadsSize{0};
-};
-
-class TCAction : public DNSAction
-{
-public:
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- return Action::Truncate;
- }
- std::string toString() const override
- {
- return "tc=1 answer";
- }
-};
-
-class LuaAction : public DNSAction
-{
-public:
- typedef std::function<std::tuple<int, boost::optional<string> >(DNSQuestion* dq)> func_t;
- LuaAction(const LuaAction::func_t& func) : d_func(func)
- {}
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- try {
- DNSAction::Action result;
- {
- auto lock = g_lua.lock();
- auto ret = d_func(dq);
- if (ruleresult) {
- if (boost::optional<std::string> rule = std::get<1>(ret)) {
- *ruleresult = *rule;
- }
- else {
- // default to empty string
- ruleresult->clear();
- }
- }
- result = static_cast<Action>(std::get<0>(ret));
- }
- dnsdist::handleQueuedAsynchronousEvents();
- return result;
- } catch (const std::exception &e) {
- warnlog("LuaAction failed inside Lua, returning ServFail: %s", e.what());
- } catch (...) {
- warnlog("LuaAction failed inside Lua, returning ServFail: [unknown exception]");
- }
- return DNSAction::Action::ServFail;
- }
-
- string toString() const override
- {
- return "Lua script";
- }
-private:
- func_t d_func;
-};
-
-class LuaResponseAction : public DNSResponseAction
-{
-public:
- typedef std::function<std::tuple<int, boost::optional<string> >(DNSResponse* dr)> func_t;
- LuaResponseAction(const LuaResponseAction::func_t& func) : d_func(func)
- {}
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- try {
- DNSResponseAction::Action result;
- {
- auto lock = g_lua.lock();
- auto ret = d_func(dr);
- if (ruleresult) {
- if (boost::optional<std::string> rule = std::get<1>(ret)) {
- *ruleresult = *rule;
- }
- else {
- // default to empty string
- ruleresult->clear();
- }
- }
- result = static_cast<Action>(std::get<0>(ret));
- }
- dnsdist::handleQueuedAsynchronousEvents();
- return result;
- } catch (const std::exception &e) {
- warnlog("LuaResponseAction failed inside Lua, returning ServFail: %s", e.what());
- } catch (...) {
- warnlog("LuaResponseAction failed inside Lua, returning ServFail: [unknown exception]");
- }
- return DNSResponseAction::Action::ServFail;
- }
-
- string toString() const override
- {
- return "Lua response script";
- }
-private:
- func_t d_func;
-};
-
-class LuaFFIAction: public DNSAction
-{
-public:
- typedef std::function<int(dnsdist_ffi_dnsquestion_t* dq)> func_t;
-
- LuaFFIAction(const LuaFFIAction::func_t& func): d_func(func)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dnsdist_ffi_dnsquestion_t dqffi(dq);
- try {
- DNSAction::Action result;
- {
- auto lock = g_lua.lock();
- auto ret = d_func(&dqffi);
- if (ruleresult) {
- if (dqffi.result) {
- *ruleresult = *dqffi.result;
- }
- else {
- // default to empty string
- ruleresult->clear();
- }
- }
- result = static_cast<DNSAction::Action>(ret);
- }
- dnsdist::handleQueuedAsynchronousEvents();
- return result;
- } catch (const std::exception &e) {
- warnlog("LuaFFIAction failed inside Lua, returning ServFail: %s", e.what());
- } catch (...) {
- warnlog("LuaFFIAction failed inside Lua, returning ServFail: [unknown exception]");
- }
- return DNSAction::Action::ServFail;
- }
-
- string toString() const override
- {
- return "Lua FFI script";
- }
-private:
- func_t d_func;
-};
-
-class LuaFFIPerThreadAction: public DNSAction
-{
-public:
- typedef std::function<int(dnsdist_ffi_dnsquestion_t* dq)> func_t;
-
- LuaFFIPerThreadAction(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- try {
- auto& state = t_perThreadStates[d_functionID];
- if (!state.d_initialized) {
- setupLuaFFIPerThreadContext(state.d_luaContext);
- /* mark the state as initialized first so if there is a syntax error
- we only try to execute the code once */
- state.d_initialized = true;
- state.d_func = state.d_luaContext.executeCode<func_t>(d_functionCode);
- }
-
- if (!state.d_func) {
- /* the function was not properly initialized */
- return DNSAction::Action::None;
- }
-
- dnsdist_ffi_dnsquestion_t dqffi(dq);
- auto ret = state.d_func(&dqffi);
- if (ruleresult) {
- if (dqffi.result) {
- *ruleresult = *dqffi.result;
- }
- else {
- // default to empty string
- ruleresult->clear();
- }
- }
- dnsdist::handleQueuedAsynchronousEvents();
- return static_cast<DNSAction::Action>(ret);
- }
- catch (const std::exception &e) {
- warnlog("LuaFFIPerThreadAction failed inside Lua, returning ServFail: %s", e.what());
- }
- catch (...) {
- warnlog("LuaFFIPerthreadAction failed inside Lua, returning ServFail: [unknown exception]");
- }
- return DNSAction::Action::ServFail;
- }
-
- string toString() const override
- {
- return "Lua FFI per-thread script";
- }
-
-private:
- struct PerThreadState
- {
- LuaContext d_luaContext;
- func_t d_func;
- bool d_initialized{false};
- };
- static std::atomic<uint64_t> s_functionsCounter;
- static thread_local std::map<uint64_t, PerThreadState> t_perThreadStates;
- const std::string d_functionCode;
- const uint64_t d_functionID;
-};
-
-std::atomic<uint64_t> LuaFFIPerThreadAction::s_functionsCounter = 0;
-thread_local std::map<uint64_t, LuaFFIPerThreadAction::PerThreadState> LuaFFIPerThreadAction::t_perThreadStates;
-
-class LuaFFIResponseAction: public DNSResponseAction
-{
-public:
- typedef std::function<int(dnsdist_ffi_dnsresponse_t* dq)> func_t;
-
- LuaFFIResponseAction(const LuaFFIResponseAction::func_t& func): d_func(func)
- {
- }
-
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- dnsdist_ffi_dnsresponse_t drffi(dr);
- try {
- DNSResponseAction::Action result;
- {
- auto lock = g_lua.lock();
- auto ret = d_func(&drffi);
- if (ruleresult) {
- if (drffi.result) {
- *ruleresult = *drffi.result;
- }
- else {
- // default to empty string
- ruleresult->clear();
- }
- }
- result = static_cast<DNSResponseAction::Action>(ret);
- }
- dnsdist::handleQueuedAsynchronousEvents();
- return result;
- } catch (const std::exception &e) {
- warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: %s", e.what());
- } catch (...) {
- warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: [unknown exception]");
- }
- return DNSResponseAction::Action::ServFail;
- }
-
- string toString() const override
- {
- return "Lua FFI script";
- }
-private:
- func_t d_func;
-};
-
-class LuaFFIPerThreadResponseAction: public DNSResponseAction
-{
-public:
- typedef std::function<int(dnsdist_ffi_dnsresponse_t* dr)> func_t;
-
- LuaFFIPerThreadResponseAction(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++)
- {
- }
-
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- try {
- auto& state = t_perThreadStates[d_functionID];
- if (!state.d_initialized) {
- setupLuaFFIPerThreadContext(state.d_luaContext);
- /* mark the state as initialized first so if there is a syntax error
- we only try to execute the code once */
- state.d_initialized = true;
- state.d_func = state.d_luaContext.executeCode<func_t>(d_functionCode);
- }
-
- if (!state.d_func) {
- /* the function was not properly initialized */
- return DNSResponseAction::Action::None;
- }
-
- dnsdist_ffi_dnsresponse_t drffi(dr);
- auto ret = state.d_func(&drffi);
- if (ruleresult) {
- if (drffi.result) {
- *ruleresult = *drffi.result;
- }
- else {
- // default to empty string
- ruleresult->clear();
- }
- }
- dnsdist::handleQueuedAsynchronousEvents();
- return static_cast<DNSResponseAction::Action>(ret);
- }
- catch (const std::exception &e) {
- warnlog("LuaFFIPerThreadResponseAction failed inside Lua, returning ServFail: %s", e.what());
- }
- catch (...) {
- warnlog("LuaFFIPerthreadResponseAction failed inside Lua, returning ServFail: [unknown exception]");
- }
- return DNSResponseAction::Action::ServFail;
- }
-
- string toString() const override
- {
- return "Lua FFI per-thread script";
- }
-
-private:
- struct PerThreadState
- {
- LuaContext d_luaContext;
- func_t d_func;
- bool d_initialized{false};
- };
-
- static std::atomic<uint64_t> s_functionsCounter;
- static thread_local std::map<uint64_t, PerThreadState> t_perThreadStates;
- const std::string d_functionCode;
- const uint64_t d_functionID;
-};
-
-std::atomic<uint64_t> LuaFFIPerThreadResponseAction::s_functionsCounter = 0;
-thread_local std::map<uint64_t, LuaFFIPerThreadResponseAction::PerThreadState> LuaFFIPerThreadResponseAction::t_perThreadStates;
-
-thread_local std::default_random_engine SpoofAction::t_randomEngine;
-
-DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
-{
- uint16_t qtype = dq->ids.qtype;
- // do we even have a response?
- if (d_cname.empty() &&
- d_rawResponses.empty() &&
- // make sure pre-forged response is greater than sizeof(dnsheader)
- (d_raw.size() < sizeof(dnsheader)) &&
- d_types.count(qtype) == 0) {
- return Action::None;
- }
-
- if (d_raw.size() >= sizeof(dnsheader)) {
- auto id = dq->getHeader()->id;
- dq->getMutableData() = d_raw;
- dq->getHeader()->id = id;
- return Action::HeaderModify;
- }
- vector<ComboAddress> addrs;
- vector<std::string> rawResponses;
- unsigned int totrdatalen = 0;
- uint16_t numberOfRecords = 0;
- if (!d_cname.empty()) {
- qtype = QType::CNAME;
- totrdatalen += d_cname.getStorage().size();
- numberOfRecords = 1;
- } else if (!d_rawResponses.empty()) {
- rawResponses.reserve(d_rawResponses.size());
- for(const auto& rawResponse : d_rawResponses){
- totrdatalen += rawResponse.size();
- rawResponses.push_back(rawResponse);
- ++numberOfRecords;
- }
- if (rawResponses.size() > 1) {
- shuffle(rawResponses.begin(), rawResponses.end(), t_randomEngine);
- }
- }
- else {
- for(const auto& addr : d_addrs) {
- if(qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) ||
- (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) {
- continue;
- }
- totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr);
- addrs.push_back(addr);
- ++numberOfRecords;
- }
- }
-
- if (addrs.size() > 1) {
- shuffle(addrs.begin(), addrs.end(), t_randomEngine);
- }
-
- unsigned int qnameWireLength=0;
- DNSName ignore(reinterpret_cast<const char*>(dq->getData().data()), dq->getData().size(), sizeof(dnsheader), false, 0, 0, &qnameWireLength);
-
- if (dq->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen)) {
- return Action::None;
- }
-
- bool dnssecOK = false;
- bool hadEDNS = false;
- if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dq)) {
- hadEDNS = true;
- dnssecOK = getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO;
- }
-
- auto& data = dq->getMutableData();
- data.resize(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen); // there goes your EDNS
- uint8_t* dest = &(data.at(sizeof(dnsheader) + qnameWireLength + 4));
-
- dq->getHeader()->qr = true; // for good measure
- setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
- dq->getHeader()->ancount = 0;
- dq->getHeader()->arcount = 0; // for now, forget about your EDNS, we're marching over it
-
- uint32_t ttl = htonl(d_responseConfig.ttl);
- unsigned char recordstart[] = {0xc0, 0x0c, // compressed name
- 0, 0, // QTYPE
- 0, QClass::IN,
- 0, 0, 0, 0, // TTL
- 0, 0 }; // rdata length
- static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid");
- memcpy(&recordstart[6], &ttl, sizeof(ttl));
- bool raw = false;
-
- if (qtype == QType::CNAME) {
- const auto& wireData = d_cname.getStorage(); // Note! This doesn't do compression!
- uint16_t rdataLen = htons(wireData.length());
- qtype = htons(qtype);
- memcpy(&recordstart[2], &qtype, sizeof(qtype));
- memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
-
- memcpy(dest, recordstart, sizeof(recordstart));
- dest += sizeof(recordstart);
- memcpy(dest, wireData.c_str(), wireData.length());
- dq->getHeader()->ancount++;
- }
- else if (!rawResponses.empty()) {
- qtype = htons(qtype);
- for(const auto& rawResponse : rawResponses){
- uint16_t rdataLen = htons(rawResponse.size());
- memcpy(&recordstart[2], &qtype, sizeof(qtype));
- memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
-
- memcpy(dest, recordstart, sizeof(recordstart));
- dest += sizeof(recordstart);
-
- memcpy(dest, rawResponse.c_str(), rawResponse.size());
- dest += rawResponse.size();
-
- dq->getHeader()->ancount++;
- }
- raw = true;
- }
- else {
- for(const auto& addr : addrs) {
- uint16_t rdataLen = htons(addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
- qtype = htons(addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA);
- memcpy(&recordstart[2], &qtype, sizeof(qtype));
- memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
-
- memcpy(dest, recordstart, sizeof(recordstart));
- dest += sizeof(recordstart);
-
- memcpy(dest,
- addr.sin4.sin_family == AF_INET ? reinterpret_cast<const void*>(&addr.sin4.sin_addr.s_addr) : reinterpret_cast<const void*>(&addr.sin6.sin6_addr.s6_addr),
- addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
- dest += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
- dq->getHeader()->ancount++;
- }
- }
-
- dq->getHeader()->ancount = htons(dq->getHeader()->ancount);
-
- if (hadEDNS && raw == false) {
- addEDNS(dq->getMutableData(), dq->getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, 0);
- }
-
- return Action::HeaderModify;
-}
-
-class SetMacAddrAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetMacAddrAction(uint16_t code) : d_code(code)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dnsdist::MacAddress mac;
- int res = dnsdist::MacAddressesCache::get(dq->ids.origRemote, mac.data(), mac.size());
- if (res != 0) {
- return Action::None;
- }
-
- std::string optRData;
- generateEDNSOption(d_code, reinterpret_cast<const char*>(mac.data()), optRData);
-
- if (dq->getHeader()->arcount) {
- bool ednsAdded = false;
- bool optionAdded = false;
- PacketBuffer newContent;
- newContent.reserve(dq->getData().size());
-
- if (!slowRewriteEDNSOptionInQueryWithRecords(dq->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) {
- return Action::None;
- }
-
- if (newContent.size() > dq->getMaximumSize()) {
- return Action::None;
- }
-
- dq->getMutableData() = std::move(newContent);
- if (!dq->ids.ednsAdded && ednsAdded) {
- dq->ids.ednsAdded = true;
- }
-
- return Action::None;
- }
-
- auto& data = dq->getMutableData();
- if (generateOptRR(optRData, data, dq->getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) {
- dq->getHeader()->arcount = htons(1);
- // make sure that any EDNS sent by the backend is removed before forwarding the response to the client
- dq->ids.ednsAdded = true;
- }
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "add EDNS MAC (code=" + std::to_string(d_code) + ")";
- }
-private:
- uint16_t d_code{3};
-};
-
-class SetEDNSOptionAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetEDNSOptionAction(uint16_t code, const std::string& data) : d_code(code), d_data(data)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- setEDNSOption(*dq, d_code, d_data);
- return Action::None;
- }
-
- std::string toString() const override
- {
- return "add EDNS Option (code=" + std::to_string(d_code) + ")";
- }
-
-private:
- uint16_t d_code;
- std::string d_data;
-};
-
-class SetNoRecurseAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->getHeader()->rd = false;
- return Action::None;
- }
- std::string toString() const override
- {
- return "set rd=0";
- }
-};
-
-class LogAction : public DNSAction, public boost::noncopyable
-{
-public:
- // this action does not stop the processing
- LogAction()
- {
- }
-
- LogAction(const std::string& str, bool binary=true, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_binary(binary), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
- {
- if (str.empty()) {
- return;
- }
-
- if (!reopenLogFile()) {
- throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
- }
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
- if (!fp) {
- if (!d_verboseOnly || g_verbose) {
- if (d_includeTimestamp) {
- infolog("[%u.%u] Packet from %s for %s %s with id %d", static_cast<unsigned long long>(dq->getQueryRealTime().tv_sec), static_cast<unsigned long>(dq->getQueryRealTime().tv_nsec), dq->ids.origRemote.toStringWithPort(), dq->ids.qname.toString(), QType(dq->ids.qtype).toString(), dq->getHeader()->id);
- }
- else {
- infolog("Packet from %s for %s %s with id %d", dq->ids.origRemote.toStringWithPort(), dq->ids.qname.toString(), QType(dq->ids.qtype).toString(), dq->getHeader()->id);
- }
- }
- }
- else {
- if (d_binary) {
- const auto& out = dq->ids.qname.getStorage();
- if (d_includeTimestamp) {
- uint64_t tv_sec = static_cast<uint64_t>(dq->getQueryRealTime().tv_sec);
- uint32_t tv_nsec = static_cast<uint32_t>(dq->getQueryRealTime().tv_nsec);
- fwrite(&tv_sec, sizeof(tv_sec), 1, fp.get());
- fwrite(&tv_nsec, sizeof(tv_nsec), 1, fp.get());
- }
- uint16_t id = dq->getHeader()->id;
- fwrite(&id, sizeof(id), 1, fp.get());
- fwrite(out.c_str(), 1, out.size(), fp.get());
- fwrite(&dq->ids.qtype, sizeof(dq->ids.qtype), 1, fp.get());
- fwrite(&dq->ids.origRemote.sin4.sin_family, sizeof(dq->ids.origRemote.sin4.sin_family), 1, fp.get());
- if (dq->ids.origRemote.sin4.sin_family == AF_INET) {
- fwrite(&dq->ids.origRemote.sin4.sin_addr.s_addr, sizeof(dq->ids.origRemote.sin4.sin_addr.s_addr), 1, fp.get());
- }
- else if (dq->ids.origRemote.sin4.sin_family == AF_INET6) {
- fwrite(&dq->ids.origRemote.sin6.sin6_addr.s6_addr, sizeof(dq->ids.origRemote.sin6.sin6_addr.s6_addr), 1, fp.get());
- }
- fwrite(&dq->ids.origRemote.sin4.sin_port, sizeof(dq->ids.origRemote.sin4.sin_port), 1, fp.get());
- }
- else {
- if (d_includeTimestamp) {
- fprintf(fp.get(), "[%llu.%lu] Packet from %s for %s %s with id %u\n", static_cast<unsigned long long>(dq->getQueryRealTime().tv_sec), static_cast<unsigned long>(dq->getQueryRealTime().tv_nsec), dq->ids.origRemote.toStringWithPort().c_str(), dq->ids.qname.toString().c_str(), QType(dq->ids.qtype).toString().c_str(), dq->getHeader()->id);
- }
- else {
- fprintf(fp.get(), "Packet from %s for %s %s with id %u\n", dq->ids.origRemote.toStringWithPort().c_str(), dq->ids.qname.toString().c_str(), QType(dq->ids.qtype).toString().c_str(), dq->getHeader()->id);
- }
- }
- }
- return Action::None;
- }
-
- std::string toString() const override
- {
- if (!d_fname.empty()) {
- return "log to " + d_fname;
- }
- return "log";
- }
-
- void reload() override
- {
- if (!reopenLogFile()) {
- warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror());
- }
- }
-
-private:
- bool reopenLogFile()
- {
- // we are using a naked pointer here because we don't want fclose to be called
- // with a nullptr, which would happen if we constructor a shared_ptr with fclose
- // as a custom deleter and nullptr as a FILE*
- auto nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w");
- if (!nfp) {
- /* don't fall on our sword when reopening */
- return false;
- }
-
- auto fp = std::shared_ptr<FILE>(nfp, fclose);
- nfp = nullptr;
-
- if (!d_buffered) {
- setbuf(fp.get(), 0);
- }
-
- std::atomic_store_explicit(&d_fp, fp, std::memory_order_release);
- return true;
- }
-
- std::string d_fname;
- std::shared_ptr<FILE> d_fp{nullptr};
- bool d_binary{true};
- bool d_verboseOnly{true};
- bool d_includeTimestamp{false};
- bool d_append{false};
- bool d_buffered{true};
-};
-
-class LogResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
- LogResponseAction()
- {
- }
-
- LogResponseAction(const std::string& str, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
- {
- if (str.empty()) {
- return;
- }
-
- if (!reopenLogFile()) {
- throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
- }
- }
-
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
- if (!fp) {
- if (!d_verboseOnly || g_verbose) {
- if (d_includeTimestamp) {
- infolog("[%u.%u] Answer to %s for %s %s (%s) with id %u", static_cast<unsigned long long>(dr->getQueryRealTime().tv_sec), static_cast<unsigned long>(dr->getQueryRealTime().tv_nsec), dr->ids.origRemote.toStringWithPort(), dr->ids.qname.toString(), QType(dr->ids.qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id);
- }
- else {
- infolog("Answer to %s for %s %s (%s) with id %u", dr->ids.origRemote.toStringWithPort(), dr->ids.qname.toString(), QType(dr->ids.qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id);
- }
- }
- }
- else {
- if (d_includeTimestamp) {
- fprintf(fp.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %u\n", static_cast<unsigned long long>(dr->getQueryRealTime().tv_sec), static_cast<unsigned long>(dr->getQueryRealTime().tv_nsec), dr->ids.origRemote.toStringWithPort().c_str(), dr->ids.qname.toString().c_str(), QType(dr->ids.qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
- }
- else {
- fprintf(fp.get(), "Answer to %s for %s %s (%s) with id %u\n", dr->ids.origRemote.toStringWithPort().c_str(), dr->ids.qname.toString().c_str(), QType(dr->ids.qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
- }
- }
- return Action::None;
- }
-
- std::string toString() const override
- {
- if (!d_fname.empty()) {
- return "log to " + d_fname;
- }
- return "log";
- }
-
- void reload() override
- {
- if (!reopenLogFile()) {
- warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror());
- }
- }
-
-private:
- bool reopenLogFile()
- {
- // we are using a naked pointer here because we don't want fclose to be called
- // with a nullptr, which would happen if we constructor a shared_ptr with fclose
- // as a custom deleter and nullptr as a FILE*
- auto nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w");
- if (!nfp) {
- /* don't fall on our sword when reopening */
- return false;
- }
-
- auto fp = std::shared_ptr<FILE>(nfp, fclose);
- nfp = nullptr;
-
- if (!d_buffered) {
- setbuf(fp.get(), 0);
- }
-
- std::atomic_store_explicit(&d_fp, fp, std::memory_order_release);
- return true;
- }
-
- std::string d_fname;
- std::shared_ptr<FILE> d_fp{nullptr};
- bool d_verboseOnly{true};
- bool d_includeTimestamp{false};
- bool d_append{false};
- bool d_buffered{true};
-};
-
-class SetDisableValidationAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->getHeader()->cd = true;
- return Action::None;
- }
- std::string toString() const override
- {
- return "set cd=1";
- }
-};
-
-class SetSkipCacheAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->ids.skipCache = true;
- return Action::None;
- }
- std::string toString() const override
- {
- return "skip cache";
- }
-};
-
-class SetSkipCacheResponseAction : public DNSResponseAction
-{
-public:
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- dr->ids.skipCache = true;
- return Action::None;
- }
- std::string toString() const override
- {
- return "skip cache";
- }
-};
-
-class SetTempFailureCacheTTLAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetTempFailureCacheTTLAction(uint32_t ttl) : d_ttl(ttl)
- {
- }
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->ids.tempFailureTTL = d_ttl;
- return Action::None;
- }
- std::string toString() const override
- {
- return "set tempfailure cache ttl to "+std::to_string(d_ttl);
- }
-private:
- uint32_t d_ttl;
-};
-
-class SetECSPrefixLengthAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) : d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length)
- {
- }
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->ecsPrefixLength = dq->ids.origRemote.sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength;
- return Action::None;
- }
- std::string toString() const override
- {
- return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength);
- }
-private:
- uint16_t d_v4PrefixLength;
- uint16_t d_v6PrefixLength;
-};
-
-class SetECSOverrideAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetECSOverrideAction(bool ecsOverride) : d_ecsOverride(ecsOverride)
- {
- }
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->ecsOverride = d_ecsOverride;
- return Action::None;
- }
- std::string toString() const override
- {
- return "set ECS override to " + std::to_string(d_ecsOverride);
- }
-private:
- bool d_ecsOverride;
-};
-
-
-class SetDisableECSAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->useECS = false;
- return Action::None;
- }
- std::string toString() const override
- {
- return "disable ECS";
- }
-};
-
-class SetECSAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetECSAction(const Netmask& v4): d_v4(v4), d_hasV6(false)
- {
- }
-
- SetECSAction(const Netmask& v4, const Netmask& v6): d_v4(v4), d_v6(v6), d_hasV6(true)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (d_hasV6) {
- dq->ecs = std::make_unique<Netmask>(dq->ids.origRemote.isIPv4() ? d_v4 : d_v6);
- }
- else {
- dq->ecs = std::make_unique<Netmask>(d_v4);
- }
-
- return Action::None;
- }
-
- std::string toString() const override
- {
- std::string result = "set ECS to " + d_v4.toString();
- if (d_hasV6) {
- result += " / " + d_v6.toString();
- }
- return result;
- }
-
-private:
- Netmask d_v4;
- Netmask d_v6;
- bool d_hasV6;
-};
-
-#ifndef DISABLE_PROTOBUF
-static DnstapMessage::ProtocolType ProtocolToDNSTap(dnsdist::Protocol protocol)
-{
- if (protocol == dnsdist::Protocol::DoUDP) {
- return DnstapMessage::ProtocolType::DoUDP;
- }
- else if (protocol == dnsdist::Protocol::DoTCP) {
- return DnstapMessage::ProtocolType::DoTCP;
- }
- else if (protocol == dnsdist::Protocol::DoT) {
- return DnstapMessage::ProtocolType::DoT;
- }
- else if (protocol == dnsdist::Protocol::DoH) {
- return DnstapMessage::ProtocolType::DoH;
- }
- else if (protocol == dnsdist::Protocol::DNSCryptUDP) {
- return DnstapMessage::ProtocolType::DNSCryptUDP;
- }
- else if (protocol == dnsdist::Protocol::DNSCryptTCP) {
- return DnstapMessage::ProtocolType::DNSCryptTCP;
- }
- throw std::runtime_error("Unhandled protocol for dnstap: " + protocol.toPrettyString());
-}
-
-static void remoteLoggerQueueData(RemoteLoggerInterface& r, const std::string& data)
-{
- auto ret = r.queueData(data);
-
- switch (ret) {
- case RemoteLoggerInterface::Result::Queued:
- break;
- case RemoteLoggerInterface::Result::PipeFull: {
- vinfolog("%s: %s", r.name(), RemoteLoggerInterface::toErrorString(ret));
- break;
- }
- case RemoteLoggerInterface::Result::TooLarge: {
- warnlog("%s: %s", r.name(), RemoteLoggerInterface::toErrorString(ret));
- break;
- }
- case RemoteLoggerInterface::Result::OtherError:
- warnlog("%s: %s", r.name(), RemoteLoggerInterface::toErrorString(ret));
- }
-}
-
-class DnstapLogAction : public DNSAction, public boost::noncopyable
-{
-public:
- // this action does not stop the processing
- DnstapLogAction(const std::string& identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > alterFunc): d_identity(identity), d_logger(logger), d_alterFunc(alterFunc)
- {
- }
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- static thread_local std::string data;
- data.clear();
-
- DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dq->getProtocol());
- DnstapMessage message(data, !dq->getHeader()->qr ? DnstapMessage::MessageType::client_query : DnstapMessage::MessageType::client_response, d_identity, &dq->ids.origRemote, &dq->ids.origDest, protocol, reinterpret_cast<const char*>(dq->getData().data()), dq->getData().size(), &dq->getQueryRealTime(), nullptr);
- {
- if (d_alterFunc) {
- auto lock = g_lua.lock();
- (*d_alterFunc)(dq, &message);
- }
- }
-
- remoteLoggerQueueData(*d_logger, data);
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "remote log as dnstap to " + (d_logger ? d_logger->toString() : "");
- }
-private:
- std::string d_identity;
- std::shared_ptr<RemoteLoggerInterface> d_logger;
- boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > d_alterFunc;
-};
-
-static void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::vector<std::pair<std::string, ProtoBufMetaKey>>& metas)
-{
- for (const auto& [name, meta] : metas) {
- message.addMeta(name, meta.getValues(dq));
- }
-}
-
-static void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::unordered_set<std::string>& allowed)
-{
- if (!dq.ids.qTag) {
- return;
- }
-
- for (const auto& [key, value] : *dq.ids.qTag) {
- if (!allowed.empty() && allowed.count(key) == 0) {
- continue;
- }
-
- if (value.empty()) {
- message.addTag(key);
- }
- else {
- message.addTag(key + ":" + value);
- }
- }
-}
-
-class RemoteLogAction : public DNSAction, public boost::noncopyable
-{
-public:
- // this action does not stop the processing
- RemoteLogAction(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, std::vector<std::pair<std::string, ProtoBufMetaKey>>&& metas, std::optional<std::unordered_set<std::string>>&& tagsToExport): d_tagsToExport(std::move(tagsToExport)), d_metas(std::move(metas)), d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (!dq->ids.d_protoBufData) {
- dq->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
- }
- if (!dq->ids.d_protoBufData->uniqueId) {
- dq->ids.d_protoBufData->uniqueId = getUniqueID();
- }
-
- DNSDistProtoBufMessage message(*dq);
- if (!d_serverID.empty()) {
- message.setServerIdentity(d_serverID);
- }
-
-#if HAVE_IPCIPHER
- if (!d_ipEncryptKey.empty())
- {
- message.setRequestor(encryptCA(dq->ids.origRemote, d_ipEncryptKey));
- }
-#endif /* HAVE_IPCIPHER */
-
- if (d_tagsToExport) {
- addTagsToProtobuf(message, *dq, *d_tagsToExport);
- }
-
- addMetaDataToProtobuf(message, *dq, d_metas);
-
- if (d_alterFunc) {
- auto lock = g_lua.lock();
- (*d_alterFunc)(dq, &message);
- }
-
- static thread_local std::string data;
- data.clear();
- message.serialize(data);
- remoteLoggerQueueData(*d_logger, data);
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "remote log to " + (d_logger ? d_logger->toString() : "");
- }
-private:
- std::optional<std::unordered_set<std::string>> d_tagsToExport;
- std::vector<std::pair<std::string, ProtoBufMetaKey>> d_metas;
- std::shared_ptr<RemoteLoggerInterface> d_logger;
- boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > d_alterFunc;
- std::string d_serverID;
- std::string d_ipEncryptKey;
-};
-
-#endif /* DISABLE_PROTOBUF */
-
-class SNMPTrapAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SNMPTrapAction(const std::string& reason): d_reason(reason)
- {
- }
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (g_snmpAgent && g_snmpTrapsEnabled) {
- g_snmpAgent->sendDNSTrap(*dq, d_reason);
- }
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "send SNMP trap";
- }
-private:
- std::string d_reason;
-};
-
-class SetTagAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetTagAction(const std::string& tag, const std::string& value): d_tag(tag), d_value(value)
- {
- }
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->setTag(d_tag, d_value);
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "set tag '" + d_tag + "' to value '" + d_value + "'";
- }
-private:
- std::string d_tag;
- std::string d_value;
-};
-
-#ifndef DISABLE_PROTOBUF
-class DnstapLogResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
- // this action does not stop the processing
- DnstapLogResponseAction(const std::string& identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > alterFunc): d_identity(identity), d_logger(logger), d_alterFunc(alterFunc)
- {
- }
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- static thread_local std::string data;
- struct timespec now;
- gettime(&now, true);
- data.clear();
-
- DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dr->getProtocol());
- DnstapMessage message(data, DnstapMessage::MessageType::client_response, d_identity, &dr->ids.origRemote, &dr->ids.origDest, protocol, reinterpret_cast<const char*>(dr->getData().data()), dr->getData().size(), &dr->getQueryRealTime(), &now);
- {
- if (d_alterFunc) {
- auto lock = g_lua.lock();
- (*d_alterFunc)(dr, &message);
- }
- }
-
- remoteLoggerQueueData(*d_logger, data);
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "log response as dnstap to " + (d_logger ? d_logger->toString() : "");
- }
-private:
- std::string d_identity;
- std::shared_ptr<RemoteLoggerInterface> d_logger;
- boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > d_alterFunc;
-};
-
-class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
- // this action does not stop the processing
- RemoteLogResponseAction(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, bool includeCNAME, std::vector<std::pair<std::string, ProtoBufMetaKey>>&& metas, std::optional<std::unordered_set<std::string>>&& tagsToExport): d_tagsToExport(std::move(tagsToExport)), d_metas(std::move(metas)), d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey), d_includeCNAME(includeCNAME)
- {
- }
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- if (!dr->ids.d_protoBufData) {
- dr->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
- }
- if (!dr->ids.d_protoBufData->uniqueId) {
- dr->ids.d_protoBufData->uniqueId = getUniqueID();
- }
-
- DNSDistProtoBufMessage message(*dr, d_includeCNAME);
- if (!d_serverID.empty()) {
- message.setServerIdentity(d_serverID);
- }
-
-#if HAVE_IPCIPHER
- if (!d_ipEncryptKey.empty())
- {
- message.setRequestor(encryptCA(dr->ids.origRemote, d_ipEncryptKey));
- }
-#endif /* HAVE_IPCIPHER */
-
- if (d_tagsToExport) {
- addTagsToProtobuf(message, *dr, *d_tagsToExport);
- }
-
- addMetaDataToProtobuf(message, *dr, d_metas);
-
- if (d_alterFunc) {
- auto lock = g_lua.lock();
- (*d_alterFunc)(dr, &message);
- }
-
- static thread_local std::string data;
- data.clear();
- message.serialize(data);
- d_logger->queueData(data);
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "remote log response to " + (d_logger ? d_logger->toString() : "");
- }
-private:
- std::optional<std::unordered_set<std::string>> d_tagsToExport;
- std::vector<std::pair<std::string, ProtoBufMetaKey>> d_metas;
- std::shared_ptr<RemoteLoggerInterface> d_logger;
- boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > d_alterFunc;
- std::string d_serverID;
- std::string d_ipEncryptKey;
- bool d_includeCNAME;
-};
-
-#endif /* DISABLE_PROTOBUF */
-
-class DropResponseAction : public DNSResponseAction
-{
-public:
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- return Action::Drop;
- }
- std::string toString() const override
- {
- return "drop";
- }
-};
-
-class AllowResponseAction : public DNSResponseAction
-{
-public:
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- return Action::Allow;
- }
- std::string toString() const override
- {
- return "allow";
- }
-};
-
-class DelayResponseAction : public DNSResponseAction
-{
-public:
- DelayResponseAction(int msec) : d_msec(msec)
- {
- }
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- *ruleresult = std::to_string(d_msec);
- return Action::Delay;
- }
- std::string toString() const override
- {
- return "delay by "+std::to_string(d_msec)+ " ms";
- }
-private:
- int d_msec;
-};
-
-#ifdef HAVE_NET_SNMP
-class SNMPTrapResponseAction : public DNSResponseAction
-{
-public:
- // this action does not stop the processing
- SNMPTrapResponseAction(const std::string& reason): d_reason(reason)
- {
- }
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- if (g_snmpAgent && g_snmpTrapsEnabled) {
- g_snmpAgent->sendDNSTrap(*dr, d_reason);
- }
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "send SNMP trap";
- }
-private:
- std::string d_reason;
-};
-#endif /* HAVE_NET_SNMP */
-
-class SetTagResponseAction : public DNSResponseAction
-{
-public:
- // this action does not stop the processing
- SetTagResponseAction(const std::string& tag, const std::string& value): d_tag(tag), d_value(value)
- {
- }
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- dr->setTag(d_tag, d_value);
-
- return Action::None;
- }
- std::string toString() const override
- {
- return "set tag '" + d_tag + "' to value '" + d_value + "'";
- }
-private:
- std::string d_tag;
- std::string d_value;
-};
-
-class ClearRecordTypesResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
- ClearRecordTypesResponseAction(const std::unordered_set<QType>& qtypes) : d_qtypes(qtypes)
- {
- }
-
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- if (d_qtypes.size() > 0) {
- clearDNSPacketRecordTypes(dr->getMutableData(), d_qtypes);
- }
- return DNSResponseAction::Action::None;
- }
-
- std::string toString() const override
- {
- return "clear record types";
- }
-
-private:
- std::unordered_set<QType> d_qtypes{};
-};
-
-class ContinueAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- ContinueAction(std::shared_ptr<DNSAction>& action): d_action(action)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (d_action) {
- /* call the action */
- auto action = (*d_action)(dq, ruleresult);
- bool drop = false;
- /* apply the changes if needed (pool selection, flags, etc */
- processRulesResult(action, *dq, *ruleresult, drop);
- }
-
- /* but ignore the resulting action no matter what */
- return Action::None;
- }
-
- std::string toString() const override
- {
- if (d_action) {
- return "continue after: " + (d_action ? d_action->toString() : "");
- }
- else {
- return "no op";
- }
- }
-
-private:
- std::shared_ptr<DNSAction> d_action;
-};
-
-#ifdef HAVE_DNS_OVER_HTTPS
-class HTTPStatusAction: public DNSAction
-{
-public:
- HTTPStatusAction(int code, const PacketBuffer& body, const std::string& contentType): d_body(body), d_contentType(contentType), d_code(code)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (!dq->ids.du) {
- return Action::None;
- }
-
- dq->ids.du->setHTTPResponse(d_code, PacketBuffer(d_body), d_contentType);
- dq->getHeader()->qr = true; // for good measure
- setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
- return Action::HeaderModify;
- }
-
- std::string toString() const override
- {
- return "return an HTTP status of " + std::to_string(d_code);
- }
-
- ResponseConfig d_responseConfig;
-private:
- PacketBuffer d_body;
- std::string d_contentType;
- int d_code;
-};
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-#if defined(HAVE_LMDB) || defined(HAVE_CDB)
-class KeyValueStoreLookupAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- KeyValueStoreLookupAction(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag): d_kvs(kvs), d_key(lookupKey), d_tag(destinationTag)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- std::vector<std::string> keys = d_key->getKeys(*dq);
- std::string result;
- for (const auto& key : keys) {
- if (d_kvs->getValue(key, result) == true) {
- break;
- }
- }
-
- dq->setTag(d_tag, std::move(result));
-
- return Action::None;
- }
-
- std::string toString() const override
- {
- return "lookup key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
- }
-
-private:
- std::shared_ptr<KeyValueStore> d_kvs;
- std::shared_ptr<KeyValueLookupKey> d_key;
- std::string d_tag;
-};
-
-class KeyValueStoreRangeLookupAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- KeyValueStoreRangeLookupAction(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag): d_kvs(kvs), d_key(lookupKey), d_tag(destinationTag)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- std::vector<std::string> keys = d_key->getKeys(*dq);
- std::string result;
- for (const auto& key : keys) {
- if (d_kvs->getRangeValue(key, result) == true) {
- break;
- }
- }
-
- dq->setTag(d_tag, std::move(result));
-
- return Action::None;
- }
-
- std::string toString() const override
- {
- return "do a range-based lookup in key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
- }
-
-private:
- std::shared_ptr<KeyValueStore> d_kvs;
- std::shared_ptr<KeyValueLookupKey> d_key;
- std::string d_tag;
-};
-#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
-
-class MaxReturnedTTLAction : public DNSAction
-{
-public:
- MaxReturnedTTLAction(uint32_t cap) : d_cap(cap)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- dq->ids.ttlCap = d_cap;
- return DNSAction::Action::None;
- }
-
- std::string toString() const override
- {
- return "cap the TTL of the returned response to " + std::to_string(d_cap);
- }
-
-private:
- uint32_t d_cap;
-};
-
-class MaxReturnedTTLResponseAction : public DNSResponseAction
-{
-public:
- MaxReturnedTTLResponseAction(uint32_t cap) : d_cap(cap)
- {
- }
-
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- dr->ids.ttlCap = d_cap;
- return DNSResponseAction::Action::None;
- }
-
- std::string toString() const override
- {
- return "cap the TTL of the returned response to " + std::to_string(d_cap);
- }
-
-private:
- uint32_t d_cap;
-};
-
-class NegativeAndSOAAction: public DNSAction
-{
-public:
- NegativeAndSOAAction(bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection): d_zone(zone), d_mname(mname), d_rname(rname), d_ttl(ttl), d_serial(serial), d_refresh(refresh), d_retry(retry), d_expire(expire), d_minimum(minimum), d_nxd(nxd), d_soaInAuthoritySection(soaInAuthoritySection)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (!setNegativeAndAdditionalSOA(*dq, d_nxd, d_zone, d_ttl, d_mname, d_rname, d_serial, d_refresh, d_retry, d_expire, d_minimum, d_soaInAuthoritySection)) {
- return Action::None;
- }
-
- setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
-
- return Action::Allow;
- }
-
- std::string toString() const override
- {
- return std::string(d_nxd ? "NXD " : "NODATA") + " with SOA";
- }
-
- ResponseConfig d_responseConfig;
-
-private:
- DNSName d_zone;
- DNSName d_mname;
- DNSName d_rname;
- uint32_t d_ttl;
- uint32_t d_serial;
- uint32_t d_refresh;
- uint32_t d_retry;
- uint32_t d_expire;
- uint32_t d_minimum;
- bool d_nxd;
- bool d_soaInAuthoritySection;
-};
-
-class SetProxyProtocolValuesAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetProxyProtocolValuesAction(const std::vector<std::pair<uint8_t, std::string>>& values)
- {
- d_values.reserve(values.size());
- for (const auto& value : values) {
- d_values.push_back({value.second, value.first});
- }
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (!dq->proxyProtocolValues) {
- dq->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
- }
-
- *(dq->proxyProtocolValues) = d_values;
-
- return Action::None;
- }
-
- std::string toString() const override
- {
- return "set Proxy-Protocol values";
- }
-
-private:
- std::vector<ProxyProtocolValue> d_values;
-};
-
-class SetAdditionalProxyProtocolValueAction : public DNSAction
-{
-public:
- // this action does not stop the processing
- SetAdditionalProxyProtocolValueAction(uint8_t type, const std::string& value): d_value(value), d_type(type)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
- {
- if (!dq->proxyProtocolValues) {
- dq->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
- }
-
- dq->proxyProtocolValues->push_back({ d_value, d_type });
-
- return Action::None;
- }
-
- std::string toString() const override
- {
- return "add a Proxy-Protocol value of type " + std::to_string(d_type);
- }
-
-private:
- std::string d_value;
- uint8_t d_type;
-};
-
-class SetReducedTTLResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
- // this action does not stop the processing
- SetReducedTTLResponseAction(uint8_t percentage) : d_ratio(percentage / 100.0)
- {
- }
-
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
- return ttl * d_ratio;
- };
- editDNSPacketTTL(reinterpret_cast<char *>(dr->getMutableData().data()), dr->getData().size(), visitor);
- return DNSResponseAction::Action::None;
- }
-
- std::string toString() const override
- {
- return "reduce ttl to " + std::to_string(d_ratio * 100) + " percent of its value";
- }
-
-private:
- double d_ratio{1.0};
-};
-
-template<typename T, typename ActionT>
-static void addAction(GlobalStateHolder<vector<T> > *someRuleActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params) {
- setLuaSideEffect();
-
- std::string name;
- boost::uuids::uuid uuid;
- uint64_t creationOrder;
- parseRuleParams(params, uuid, name, creationOrder);
- checkAllParametersConsumed("addAction", params);
-
- auto rule = makeRule(var);
- someRuleActions->modify([&rule, &action, &uuid, creationOrder, &name](vector<T>& ruleactions){
- ruleactions.push_back({std::move(rule), std::move(action), std::move(name), std::move(uuid), creationOrder});
- });
-}
-
-typedef std::unordered_map<std::string, boost::variant<bool, uint32_t> > responseParams_t;
-
-static void parseResponseConfig(boost::optional<responseParams_t>& vars, ResponseConfig& config)
-{
- getOptionalValue<uint32_t>(vars, "ttl", config.ttl);
- getOptionalValue<bool>(vars, "aa", config.setAA);
- getOptionalValue<bool>(vars, "ad", config.setAD);
- getOptionalValue<bool>(vars, "ra", config.setRA);
-}
-
-void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config)
-{
- if (config.setAA) {
- dh.aa = *config.setAA;
- }
- if (config.setAD) {
- dh.ad = *config.setAD;
- }
- else {
- dh.ad = false;
- }
- if (config.setRA) {
- dh.ra = *config.setRA;
- }
- else {
- dh.ra = dh.rd; // for good measure
- }
-}
-
-void setupLuaActions(LuaContext& luaCtx)
-{
- luaCtx.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
- boost::uuids::uuid uuid;
- uint64_t creationOrder;
- std::string name;
- parseRuleParams(params, uuid, name, creationOrder);
- checkAllParametersConsumed("newRuleAction", params);
-
- auto rule = makeRule(dnsrule);
- DNSDistRuleAction ra({std::move(rule), action, std::move(name), uuid, creationOrder});
- return std::make_shared<DNSDistRuleAction>(ra);
- });
-
- luaCtx.writeFunction("addAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era, boost::optional<luaruleparams_t> params) {
- if (era.type() != typeid(std::shared_ptr<DNSAction>)) {
- throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?");
- }
-
- addAction(&g_ruleactions, var, boost::get<std::shared_ptr<DNSAction> >(era), params);
- });
-
- luaCtx.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era, boost::optional<luaruleparams_t> params) {
- if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
- throw std::runtime_error("addResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
- }
-
- addAction(&g_respruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
- });
-
- luaCtx.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
- if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
- throw std::runtime_error("addCacheHitResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
- }
-
- addAction(&g_cachehitrespruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
- });
-
- luaCtx.writeFunction("addCacheInsertedResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
- if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
- throw std::runtime_error("addCacheInsertedResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
- }
-
- addAction(&g_cacheInsertedRespRuleActions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
- });
-
- luaCtx.writeFunction("addSelfAnsweredResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
- if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
- throw std::runtime_error("addSelfAnsweredResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
- }
-
- addAction(&g_selfansweredrespruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
- });
-
- luaCtx.registerFunction<void(DNSAction::*)()const>("printStats", [](const DNSAction& ta) {
- setLuaNoSideEffect();
- auto stats = ta.getStats();
- for(const auto& s : stats) {
- g_outputBuffer+=s.first+"\t";
- if((uint64_t)s.second == s.second)
- g_outputBuffer += std::to_string((uint64_t)s.second)+"\n";
- else
- g_outputBuffer += std::to_string(s.second)+"\n";
- }
- });
-
- luaCtx.writeFunction("getAction", [](unsigned int num) {
- setLuaNoSideEffect();
- boost::optional<std::shared_ptr<DNSAction>> ret;
- auto ruleactions = g_ruleactions.getCopy();
- if(num < ruleactions.size())
- ret=ruleactions[num].d_action;
- return ret;
- });
-
- luaCtx.registerFunction("getStats", &DNSAction::getStats);
- luaCtx.registerFunction("reload", &DNSAction::reload);
- luaCtx.registerFunction("reload", &DNSResponseAction::reload);
-
- luaCtx.writeFunction("LuaAction", [](LuaAction::func_t func) {
- setLuaSideEffect();
- return std::shared_ptr<DNSAction>(new LuaAction(func));
- });
-
- luaCtx.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) {
- setLuaSideEffect();
- return std::shared_ptr<DNSAction>(new LuaFFIAction(func));
- });
-
- luaCtx.writeFunction("LuaFFIPerThreadAction", [](const std::string& code) {
- setLuaSideEffect();
- return std::shared_ptr<DNSAction>(new LuaFFIPerThreadAction(code));
- });
-
- luaCtx.writeFunction("SetNoRecurseAction", []() {
- return std::shared_ptr<DNSAction>(new SetNoRecurseAction);
- });
-
- luaCtx.writeFunction("SetMacAddrAction", [](int code) {
- return std::shared_ptr<DNSAction>(new SetMacAddrAction(code));
- });
-
- luaCtx.writeFunction("SetEDNSOptionAction", [](int code, const std::string& data) {
- return std::shared_ptr<DNSAction>(new SetEDNSOptionAction(code, data));
- });
-
- luaCtx.writeFunction("PoolAction", [](const std::string& a, boost::optional<bool> stopProcessing) {
- return std::shared_ptr<DNSAction>(new PoolAction(a, stopProcessing ? *stopProcessing : true));
- });
-
- luaCtx.writeFunction("QPSAction", [](int limit) {
- return std::shared_ptr<DNSAction>(new QPSAction(limit));
- });
-
- luaCtx.writeFunction("QPSPoolAction", [](int limit, const std::string& a, boost::optional<bool> stopProcessing) {
- return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a, stopProcessing ? *stopProcessing : true));
- });
-
- luaCtx.writeFunction("SpoofAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
- vector<ComboAddress> addrs;
- if(auto s = boost::get<std::string>(&inp)) {
- addrs.push_back(ComboAddress(*s));
- } else {
- const auto& v = boost::get<LuaArray<std::string>>(inp);
- for(const auto& a: v) {
- addrs.push_back(ComboAddress(a.second));
- }
- }
-
- auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
- auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
- parseResponseConfig(vars, sa->d_responseConfig);
- checkAllParametersConsumed("SpoofAction", vars);
- return ret;
- });
-
- luaCtx.writeFunction("SpoofSVCAction", [](const LuaArray<SVCRecordParameters>& parameters, boost::optional<responseParams_t> vars) {
- auto ret = std::shared_ptr<DNSAction>(new SpoofSVCAction(parameters));
- auto sa = std::dynamic_pointer_cast<SpoofSVCAction>(ret);
- parseResponseConfig(vars, sa->d_responseConfig);
- return ret;
- });
-
- luaCtx.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<responseParams_t> vars) {
- auto ret = std::shared_ptr<DNSAction>(new SpoofAction(DNSName(a)));
- auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
- parseResponseConfig(vars, sa->d_responseConfig);
- checkAllParametersConsumed("SpoofCNAMEAction", vars);
- return ret;
- });
-
- luaCtx.writeFunction("SpoofRawAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
- vector<string> raws;
- if(auto s = boost::get<std::string>(&inp)) {
- raws.push_back(*s);
- } else {
- const auto& v = boost::get<LuaArray<std::string>>(inp);
- for(const auto& raw: v) {
- raws.push_back(raw.second);
- }
- }
-
- auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws));
- auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
- parseResponseConfig(vars, sa->d_responseConfig);
- checkAllParametersConsumed("SpoofRawAction", vars);
- return ret;
- });
-
- luaCtx.writeFunction("SpoofPacketAction", [](const std::string& response, size_t len) {
- if (len < sizeof(dnsheader)) {
- throw std::runtime_error(std::string("SpoofPacketAction: given packet len is too small"));
- }
- auto ret = std::shared_ptr<DNSAction>(new SpoofAction(response.c_str(), len));
- return ret;
- });
-
- luaCtx.writeFunction("DropAction", []() {
- return std::shared_ptr<DNSAction>(new DropAction);
- });
-
- luaCtx.writeFunction("AllowAction", []() {
- return std::shared_ptr<DNSAction>(new AllowAction);
- });
-
- luaCtx.writeFunction("NoneAction", []() {
- return std::shared_ptr<DNSAction>(new NoneAction);
- });
-
- luaCtx.writeFunction("DelayAction", [](int msec) {
- return std::shared_ptr<DNSAction>(new DelayAction(msec));
- });
-
- luaCtx.writeFunction("TCAction", []() {
- return std::shared_ptr<DNSAction>(new TCAction);
- });
-
- luaCtx.writeFunction("SetDisableValidationAction", []() {
- return std::shared_ptr<DNSAction>(new SetDisableValidationAction);
- });
-
- luaCtx.writeFunction("LogAction", [](boost::optional<std::string> fname, boost::optional<bool> binary, boost::optional<bool> append, boost::optional<bool> buffered, boost::optional<bool> verboseOnly, boost::optional<bool> includeTimestamp) {
- return std::shared_ptr<DNSAction>(new LogAction(fname ? *fname : "", binary ? *binary : true, append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
- });
-
- luaCtx.writeFunction("LogResponseAction", [](boost::optional<std::string> fname, boost::optional<bool> append, boost::optional<bool> buffered, boost::optional<bool> verboseOnly, boost::optional<bool> includeTimestamp) {
- return std::shared_ptr<DNSResponseAction>(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
- });
-
- luaCtx.writeFunction("LimitTTLResponseAction", [](uint32_t min, uint32_t max, boost::optional<LuaArray<uint16_t>> types) {
- std::unordered_set<QType> capTypes;
- if (types) {
- capTypes.reserve(types->size());
- for (const auto& [idx, type] : *types) {
- capTypes.insert(QType(type));
- }
- }
- return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min, max, capTypes));
- });
-
- luaCtx.writeFunction("SetMinTTLResponseAction", [](uint32_t min) {
- return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min));
- });
-
- luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) {
- return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(0, max));
- });
-
- luaCtx.writeFunction("SetMaxReturnedTTLAction", [](uint32_t max) {
- return std::shared_ptr<DNSAction>(new MaxReturnedTTLAction(max));
- });
-
- luaCtx.writeFunction("SetMaxReturnedTTLResponseAction", [](uint32_t max) {
- return std::shared_ptr<DNSResponseAction>(new MaxReturnedTTLResponseAction(max));
- });
-
- luaCtx.writeFunction("SetReducedTTLResponseAction", [](uint8_t percentage) {
- if (percentage > 100) {
- throw std::runtime_error(std::string("SetReducedTTLResponseAction takes a percentage between 0 and 100."));
- }
- return std::shared_ptr<DNSResponseAction>(new SetReducedTTLResponseAction(percentage));
- });
-
- luaCtx.writeFunction("ClearRecordTypesResponseAction", [](LuaTypeOrArrayOf<int> types) {
- std::unordered_set<QType> qtypes{};
- if (types.type() == typeid(int)) {
- qtypes.insert(boost::get<int>(types));
- } else if (types.type() == typeid(LuaArray<int>)) {
- const auto& v = boost::get<LuaArray<int>>(types);
- for (const auto& tpair: v) {
- qtypes.insert(tpair.second);
- }
- }
- return std::shared_ptr<DNSResponseAction>(new ClearRecordTypesResponseAction(qtypes));
- });
-
- luaCtx.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
- auto ret = std::shared_ptr<DNSAction>(new RCodeAction(rcode));
- auto rca = std::dynamic_pointer_cast<RCodeAction>(ret);
- parseResponseConfig(vars, rca->d_responseConfig);
- checkAllParametersConsumed("RCodeAction", vars);
- return ret;
- });
-
- luaCtx.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
- auto ret = std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
- auto erca = std::dynamic_pointer_cast<ERCodeAction>(ret);
- parseResponseConfig(vars, erca->d_responseConfig);
- checkAllParametersConsumed("ERCodeAction", vars);
- return ret;
- });
-
- luaCtx.writeFunction("SetSkipCacheAction", []() {
- return std::shared_ptr<DNSAction>(new SetSkipCacheAction);
- });
-
- luaCtx.writeFunction("SetSkipCacheResponseAction", []() {
- return std::shared_ptr<DNSResponseAction>(new SetSkipCacheResponseAction);
- });
-
- luaCtx.writeFunction("SetTempFailureCacheTTLAction", [](int maxTTL) {
- return std::shared_ptr<DNSAction>(new SetTempFailureCacheTTLAction(maxTTL));
- });
-
- luaCtx.writeFunction("DropResponseAction", []() {
- return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
- });
-
- luaCtx.writeFunction("AllowResponseAction", []() {
- return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
- });
-
- luaCtx.writeFunction("DelayResponseAction", [](int msec) {
- return std::shared_ptr<DNSResponseAction>(new DelayResponseAction(msec));
- });
-
- luaCtx.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) {
- setLuaSideEffect();
- return std::shared_ptr<DNSResponseAction>(new LuaResponseAction(func));
- });
-
- luaCtx.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) {
- setLuaSideEffect();
- return std::shared_ptr<DNSResponseAction>(new LuaFFIResponseAction(func));
- });
-
- luaCtx.writeFunction("LuaFFIPerThreadResponseAction", [](const std::string& code) {
- setLuaSideEffect();
- return std::shared_ptr<DNSResponseAction>(new LuaFFIPerThreadResponseAction(code));
- });
-
-#ifndef DISABLE_PROTOBUF
- luaCtx.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
- if (logger) {
- // avoids potentially-evaluated-expression warning with clang.
- RemoteLoggerInterface& rl = *logger.get();
- if (typeid(rl) != typeid(RemoteLogger)) {
- // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong.
- throw std::runtime_error(std::string("RemoteLogAction only takes RemoteLogger. For other types, please look at DnstapLogAction."));
- }
- }
-
- std::string serverID;
- std::string ipEncryptKey;
- std::string tags;
- getOptionalValue<std::string>(vars, "serverID", serverID);
- getOptionalValue<std::string>(vars, "ipEncryptKey", ipEncryptKey);
- getOptionalValue<std::string>(vars, "exportTags", tags);
-
- std::vector<std::pair<std::string, ProtoBufMetaKey>> metaOptions;
- if (metas) {
- for (const auto& [key, value] : *metas) {
- metaOptions.push_back({key, ProtoBufMetaKey(value)});
- }
- }
-
- std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
- if (!tags.empty()) {
- tagsToExport = std::unordered_set<std::string>();
- if (tags != "*") {
- std::vector<std::string> tokens;
- stringtok(tokens, tags, ",");
- for (auto& token : tokens) {
- tagsToExport->insert(std::move(token));
- }
- }
- }
-
- checkAllParametersConsumed("RemoteLogAction", vars);
-
- return std::shared_ptr<DNSAction>(new RemoteLogAction(logger, alterFunc, serverID, ipEncryptKey, std::move(metaOptions), std::move(tagsToExport)));
- });
-
- luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<bool> includeCNAME, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
- if (logger) {
- // avoids potentially-evaluated-expression warning with clang.
- RemoteLoggerInterface& rl = *logger.get();
- if (typeid(rl) != typeid(RemoteLogger)) {
- // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong.
- throw std::runtime_error("RemoteLogResponseAction only takes RemoteLogger. For other types, please look at DnstapLogResponseAction.");
- }
- }
-
- std::string serverID;
- std::string ipEncryptKey;
- std::string tags;
- getOptionalValue<std::string>(vars, "serverID", serverID);
- getOptionalValue<std::string>(vars, "ipEncryptKey", ipEncryptKey);
- getOptionalValue<std::string>(vars, "exportTags", tags);
-
- std::vector<std::pair<std::string, ProtoBufMetaKey>> metaOptions;
- if (metas) {
- for (const auto& [key, value] : *metas) {
- metaOptions.push_back({key, ProtoBufMetaKey(value)});
- }
- }
-
- std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
- if (!tags.empty()) {
- tagsToExport = std::unordered_set<std::string>();
- if (tags != "*") {
- std::vector<std::string> tokens;
- stringtok(tokens, tags, ",");
- for (auto& token : tokens) {
- tagsToExport->insert(std::move(token));
- }
- }
- }
-
- checkAllParametersConsumed("RemoteLogResponseAction", vars);
-
- return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(logger, alterFunc, serverID, ipEncryptKey, includeCNAME ? *includeCNAME : false, std::move(metaOptions), std::move(tagsToExport)));
- });
-
- luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > alterFunc) {
- return std::shared_ptr<DNSAction>(new DnstapLogAction(identity, logger, alterFunc));
- });
-
- luaCtx.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > alterFunc) {
- return std::shared_ptr<DNSResponseAction>(new DnstapLogResponseAction(identity, logger, alterFunc));
- });
-#endif /* DISABLE_PROTOBUF */
-
- luaCtx.writeFunction("TeeAction", [](const std::string& remote, boost::optional<bool> addECS, boost::optional<std::string> local) {
- boost::optional<ComboAddress> localAddr{boost::none};
- if (local) {
- localAddr = ComboAddress(*local, 0);
- }
-
- return std::shared_ptr<DNSAction>(new TeeAction(ComboAddress(remote, 53), localAddr, addECS ? *addECS : false));
- });
-
- luaCtx.writeFunction("SetECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) {
- return std::shared_ptr<DNSAction>(new SetECSPrefixLengthAction(v4PrefixLength, v6PrefixLength));
- });
-
- luaCtx.writeFunction("SetECSOverrideAction", [](bool ecsOverride) {
- return std::shared_ptr<DNSAction>(new SetECSOverrideAction(ecsOverride));
- });
-
- luaCtx.writeFunction("SetDisableECSAction", []() {
- return std::shared_ptr<DNSAction>(new SetDisableECSAction());
- });
-
- luaCtx.writeFunction("SetECSAction", [](const std::string& v4, boost::optional<std::string> v6) {
- if (v6) {
- return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4), Netmask(*v6)));
- }
- return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4)));
- });
-
-#ifdef HAVE_NET_SNMP
- luaCtx.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
- return std::shared_ptr<DNSAction>(new SNMPTrapAction(reason ? *reason : ""));
- });
-
- luaCtx.writeFunction("SNMPTrapResponseAction", [](boost::optional<std::string> reason) {
- return std::shared_ptr<DNSResponseAction>(new SNMPTrapResponseAction(reason ? *reason : ""));
- });
-#endif /* HAVE_NET_SNMP */
-
- luaCtx.writeFunction("SetTagAction", [](const std::string& tag, const std::string& value) {
- return std::shared_ptr<DNSAction>(new SetTagAction(tag, value));
- });
-
- luaCtx.writeFunction("SetTagResponseAction", [](const std::string& tag, const std::string& value) {
- return std::shared_ptr<DNSResponseAction>(new SetTagResponseAction(tag, value));
- });
-
- luaCtx.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
- return std::shared_ptr<DNSAction>(new ContinueAction(action));
- });
-
-#ifdef HAVE_DNS_OVER_HTTPS
- luaCtx.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType, boost::optional<responseParams_t> vars) {
- auto ret = std::shared_ptr<DNSAction>(new HTTPStatusAction(status, PacketBuffer(body.begin(), body.end()), contentType ? *contentType : ""));
- auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
- parseResponseConfig(vars, hsa->d_responseConfig);
- checkAllParametersConsumed("HTTPStatusAction", vars);
- return ret;
- });
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-#if defined(HAVE_LMDB) || defined(HAVE_CDB)
- luaCtx.writeFunction("KeyValueStoreLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
- return std::shared_ptr<DNSAction>(new KeyValueStoreLookupAction(kvs, lookupKey, destinationTag));
- });
-
- luaCtx.writeFunction("KeyValueStoreRangeLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
- return std::shared_ptr<DNSAction>(new KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag));
- });
-#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
-
- luaCtx.writeFunction("NegativeAndSOAAction", [](bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, boost::optional<responseParams_t> vars) {
- bool soaInAuthoritySection = false;
- getOptionalValue<bool>(vars, "soaInAuthoritySection", soaInAuthoritySection);
- auto ret = std::shared_ptr<DNSAction>(new NegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum, soaInAuthoritySection));
- auto action = std::dynamic_pointer_cast<NegativeAndSOAAction>(ret);
- parseResponseConfig(vars, action->d_responseConfig);
- checkAllParametersConsumed("NegativeAndSOAAction", vars);
- return ret;
- });
-
- luaCtx.writeFunction("SetProxyProtocolValuesAction", [](const std::vector<std::pair<uint8_t, std::string>>& values) {
- return std::shared_ptr<DNSAction>(new SetProxyProtocolValuesAction(values));
- });
-
- luaCtx.writeFunction("SetAdditionalProxyProtocolValueAction", [](uint8_t type, const std::string& value) {
- return std::shared_ptr<DNSAction>(new SetAdditionalProxyProtocolValueAction(type, value));
- });
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "dnsdist.hh"
-#include "dnsdist-async.hh"
-#include "dnsdist-dnsparser.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-internal-queries.hh"
-#include "dnsdist-lua.hh"
-#include "dnsparser.hh"
-
-void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
-{
-#ifndef DISABLE_NON_FFI_DQ_BINDINGS
- /* DNSQuestion */
- /* PowerDNS DNSQuestion compat */
- luaCtx.registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return dq.ids.origDest; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; });
- luaCtx.registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName { return dq.ids.qname; }, [](DNSQuestion& dq, const DNSName& newName) { (void) newName; });
- luaCtx.registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.ids.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; });
- luaCtx.registerMember<uint16_t (DNSQuestion::*)>("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.ids.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; });
- luaCtx.registerMember<int (DNSQuestion::*)>("rcode", [](const DNSQuestion& dq) -> int { return dq.getHeader()->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.getHeader()->rcode = newRCode; });
- luaCtx.registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress { return dq.ids.origRemote; }, [](DNSQuestion& dq, const ComboAddress newRemote) { (void) newRemote; });
- /* DNSDist DNSQuestion */
- luaCtx.registerMember<dnsheader* (DNSQuestion::*)>("dh", [](const DNSQuestion& dq) -> dnsheader* { return const_cast<DNSQuestion&>(dq).getHeader(); }, [](DNSQuestion& dq, const dnsheader* dh) { *(dq.getHeader()) = *dh; });
- luaCtx.registerMember<uint16_t (DNSQuestion::*)>("len", [](const DNSQuestion& dq) -> uint16_t { return dq.getData().size(); }, [](DNSQuestion& dq, uint16_t newlen) { dq.getMutableData().resize(newlen); });
- luaCtx.registerMember<uint8_t (DNSQuestion::*)>("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.getHeader()->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; });
- luaCtx.registerMember<bool (DNSQuestion::*)>("tcp", [](const DNSQuestion& dq) -> bool { return dq.overTCP(); }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; });
- luaCtx.registerMember<bool (DNSQuestion::*)>("skipCache", [](const DNSQuestion& dq) -> bool { return dq.ids.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.ids.skipCache = newSkipCache; });
- luaCtx.registerMember<std::string (DNSQuestion::*)>("pool", [](const DNSQuestion& dq) -> std::string { return dq.ids.poolName; }, [](DNSQuestion& dq, const std::string& newPoolName) { dq.ids.poolName = newPoolName; });
- luaCtx.registerMember<bool (DNSQuestion::*)>("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; });
- luaCtx.registerMember<bool (DNSQuestion::*)>("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; });
- luaCtx.registerMember<uint16_t (DNSQuestion::*)>("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; });
- luaCtx.registerMember<boost::optional<uint32_t> (DNSQuestion::*)>("tempFailureTTL",
- [](const DNSQuestion& dq) -> boost::optional<uint32_t> {
- return dq.ids.tempFailureTTL;
- },
- [](DNSQuestion& dq, boost::optional<uint32_t> newValue) {
- dq.ids.tempFailureTTL = newValue;
- }
- );
- luaCtx.registerMember<std::string (DNSQuestion::*)>("deviceID", [](const DNSQuestion& dq) -> std::string {
- if (dq.ids.d_protoBufData) {
- return dq.ids.d_protoBufData->d_deviceID;
- }
- return std::string();
- }, [](DNSQuestion& dq, const std::string& newValue) {
- if (!dq.ids.d_protoBufData) {
- dq.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
- }
- dq.ids.d_protoBufData->d_deviceID = newValue;
- });
- luaCtx.registerMember<std::string (DNSQuestion::*)>("deviceName", [](const DNSQuestion& dq) -> std::string {
- if (dq.ids.d_protoBufData) {
- return dq.ids.d_protoBufData->d_deviceName;
- }
- return std::string();
- }, [](DNSQuestion& dq, const std::string& newValue) {
- if (!dq.ids.d_protoBufData) {
- dq.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
- }
- dq.ids.d_protoBufData->d_deviceName = newValue;
- });
- luaCtx.registerMember<std::string (DNSQuestion::*)>("requestorID", [](const DNSQuestion& dq) -> std::string {
- if (dq.ids.d_protoBufData) {
- return dq.ids.d_protoBufData->d_requestorID;
- }
- return std::string();
- }, [](DNSQuestion& dq, const std::string& newValue) {
- if (!dq.ids.d_protoBufData) {
- dq.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
- }
- dq.ids.d_protoBufData->d_requestorID = newValue;
- });
- luaCtx.registerFunction<bool(DNSQuestion::*)()const>("getDO", [](const DNSQuestion& dq) {
- return getEDNSZ(dq) & EDNS_HEADER_FLAG_DO;
- });
- luaCtx.registerFunction<std::string(DNSQuestion::*)()const>("getContent", [](const DNSQuestion& dq) {
- return std::string(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size());
- });
- luaCtx.registerFunction<void(DNSQuestion::*)(const std::string&)>("setContent", [](DNSQuestion& dq, const std::string& raw) {
- uint16_t oldID = dq.getHeader()->id;
- auto& buffer = dq.getMutableData();
- buffer.clear();
- buffer.insert(buffer.begin(), raw.begin(), raw.end());
- reinterpret_cast<dnsheader*>(buffer.data())->id = oldID;
- });
- luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView>(DNSQuestion::*)()const>("getEDNSOptions", [](const DNSQuestion& dq) {
- if (dq.ednsOptions == nullptr) {
- parseEDNSOptions(dq);
- if (dq.ednsOptions == nullptr) {
- throw std::runtime_error("parseEDNSOptions should have populated the EDNS options");
- }
- }
-
- return *dq.ednsOptions;
- });
- luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getTrailingData", [](const DNSQuestion& dq) {
- return dq.getTrailingData();
- });
- luaCtx.registerFunction<bool(DNSQuestion::*)(std::string)>("setTrailingData", [](DNSQuestion& dq, const std::string& tail) {
- return dq.setTrailingData(tail);
- });
-
- luaCtx.registerFunction<std::string(DNSQuestion::*)()const>("getServerNameIndication", [](const DNSQuestion& dq) {
- return dq.sni;
- });
-
- luaCtx.registerFunction<std::string (DNSQuestion::*)()const>("getProtocol", [](const DNSQuestion& dq) {
- return dq.getProtocol().toPrettyString();
- });
-
- luaCtx.registerFunction<timespec(DNSQuestion::*)()const>("getQueryTime", [](const DNSQuestion& dq) {
- return dq.ids.queryRealTime.getStartTime();
- });
-
- luaCtx.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) {
-#ifdef HAVE_NET_SNMP
- if (g_snmpAgent && g_snmpTrapsEnabled) {
- g_snmpAgent->sendDNSTrap(dq, reason ? *reason : "");
- }
-#endif /* HAVE_NET_SNMP */
- });
-
- luaCtx.registerFunction<void(DNSQuestion::*)(std::string, std::string)>("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) {
- dq.setTag(strLabel, strValue);
- });
- luaCtx.registerFunction<void(DNSQuestion::*)(LuaAssociativeTable<std::string>)>("setTagArray", [](DNSQuestion& dq, const LuaAssociativeTable<std::string>&tags) {
- for (const auto& tag : tags) {
- dq.setTag(tag.first, tag.second);
- }
- });
- luaCtx.registerFunction<string(DNSQuestion::*)(std::string)const>("getTag", [](const DNSQuestion& dq, const std::string& strLabel) {
- if (!dq.ids.qTag) {
- return string();
- }
-
- std::string strValue;
- const auto it = dq.ids.qTag->find(strLabel);
- if (it == dq.ids.qTag->cend()) {
- return string();
- }
- return it->second;
- });
- luaCtx.registerFunction<QTag(DNSQuestion::*)(void)const>("getTagArray", [](const DNSQuestion& dq) {
- if (!dq.ids.qTag) {
- QTag empty;
- return empty;
- }
-
- return *dq.ids.qTag;
- });
-
- luaCtx.registerFunction<void(DNSQuestion::*)(LuaArray<std::string>)>("setProxyProtocolValues", [](DNSQuestion& dq, const LuaArray<std::string>& values) {
- if (!dq.proxyProtocolValues) {
- dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
- }
-
- dq.proxyProtocolValues->clear();
- dq.proxyProtocolValues->reserve(values.size());
- for (const auto& value : values) {
- checkParameterBound("setProxyProtocolValues", value.first, std::numeric_limits<uint8_t>::max());
- dq.proxyProtocolValues->push_back({value.second, static_cast<uint8_t>(value.first)});
- }
- });
-
- luaCtx.registerFunction<void(DNSQuestion::*)(uint64_t, std::string)>("addProxyProtocolValue", [](DNSQuestion& dq, uint64_t type, std::string value) {
- checkParameterBound("addProxyProtocolValue", type, std::numeric_limits<uint8_t>::max());
- if (!dq.proxyProtocolValues) {
- dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
- }
-
- dq.proxyProtocolValues->push_back({value, static_cast<uint8_t>(type)});
- });
-
- luaCtx.registerFunction<LuaArray<std::string>(DNSQuestion::*)()>("getProxyProtocolValues", [](const DNSQuestion& dq) {
- LuaArray<std::string> result;
- if (!dq.proxyProtocolValues) {
- return result;
- }
-
- result.resize(dq.proxyProtocolValues->size());
- for (const auto& value : *dq.proxyProtocolValues) {
- result.push_back({ value.type, value.content });
- }
-
- return result;
- });
-
- luaCtx.registerFunction<bool(DNSQuestion::*)(const DNSName& newName)>("changeName", [](DNSQuestion& dq, const DNSName& newName) -> bool {
- if (!dnsdist::changeNameInDNSPacket(dq.getMutableData(), dq.ids.qname, newName)) {
- return false;
- }
- dq.ids.qname = newName;
- return true;
- });
-
- luaCtx.registerFunction<void(DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response)>("spoof", [](DNSQuestion& dq, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response) {
- if (response.type() == typeid(LuaArray<ComboAddress>)) {
- std::vector<ComboAddress> data;
- auto responses = boost::get<LuaArray<ComboAddress>>(response);
- data.reserve(responses.size());
- for (const auto& resp : responses) {
- data.push_back(resp.second);
- }
- std::string result;
- SpoofAction sa(data);
- sa(&dq, &result);
- return;
- }
- if (response.type() == typeid(LuaArray<std::string>)) {
- std::vector<std::string> data;
- auto responses = boost::get<LuaArray<std::string>>(response);
- data.reserve(responses.size());
- for (const auto& resp : responses) {
- data.push_back(resp.second);
- }
- std::string result;
- SpoofAction sa(data);
- sa(&dq, &result);
- return;
- }
- });
-
- luaCtx.registerFunction<void(DNSQuestion::*)(uint16_t code, const std::string&)>("setEDNSOption", [](DNSQuestion& dq, uint16_t code, const std::string& data) {
- setEDNSOption(dq, code, data);
- });
-
- luaCtx.registerFunction<bool(DNSQuestion::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSQuestion& dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
- dq.asynchronous = true;
- return dnsdist::suspendQuery(dq, asyncID, queryID, timeoutMs);
- });
-
- luaCtx.registerFunction<bool(DNSQuestion::*)()>("setRestartable", [](DNSQuestion& dq) {
- dq.ids.d_packet = std::make_unique<PacketBuffer>(dq.getData());
- return true;
- });
-
-class AsynchronousObject
-{
-public:
- AsynchronousObject(std::unique_ptr<CrossProtocolQuery>&& obj_): object(std::move(obj_))
- {
- }
-
- DNSQuestion getDQ() const
- {
- return object->getDQ();
- }
-
- DNSResponse getDR() const
- {
- return object->getDR();
- }
-
- bool resume()
- {
- return dnsdist::queueQueryResumptionEvent(std::move(object));
- }
-
- bool drop()
- {
- auto sender = object->getTCPQuerySender();
- if (!sender) {
- return false;
- }
-
- struct timeval now;
- gettimeofday(&now, nullptr);
- sender->notifyIOError(std::move(object->query.d_idstate), now);
- return true;
- }
-
- bool setRCode(uint8_t rcode, bool clearAnswers)
- {
- return dnsdist::setInternalQueryRCode(object->query.d_idstate, object->query.d_buffer, rcode, clearAnswers);
- }
-
-private:
- std::unique_ptr<CrossProtocolQuery> object;
-};
-
- luaCtx.registerFunction<DNSQuestion(AsynchronousObject::*)(void) const>("getDQ", [](const AsynchronousObject& obj) {
- return obj.getDQ();
- });
-
- luaCtx.registerFunction<DNSQuestion(AsynchronousObject::*)(void) const>("getDR", [](const AsynchronousObject& obj) {
- return obj.getDR();
- });
-
- luaCtx.registerFunction<bool(AsynchronousObject::*)(void)>("resume", [](AsynchronousObject& obj) {
- return obj.resume();
- });
-
- luaCtx.registerFunction<bool(AsynchronousObject::*)(void)>("drop", [](AsynchronousObject& obj) {
- return obj.drop();
- });
-
- luaCtx.registerFunction<bool(AsynchronousObject::*)(uint8_t, bool)>("setRCode", [](AsynchronousObject& obj, uint8_t rcode, bool clearAnswers) {
- return obj.setRCode(rcode, clearAnswers);
- });
-
- luaCtx.writeFunction("getAsynchronousObject", [](uint16_t asyncID, uint16_t queryID) -> AsynchronousObject {
- if (!dnsdist::g_asyncHolder) {
- throw std::runtime_error("Unable to resume, no asynchronous holder");
- }
- auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
- if (!query) {
- throw std::runtime_error("Unable to find asynchronous object");
- }
- return AsynchronousObject(std::move(query));
- });
-
- /* LuaWrapper doesn't support inheritance */
- luaCtx.registerMember<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return dq.ids.origDest; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
- luaCtx.registerMember<const DNSName (DNSResponse::*)>("qname", [](const DNSResponse& dq) -> const DNSName { return dq.ids.qname; }, [](DNSResponse& dq, const DNSName& newName) { (void) newName; });
- luaCtx.registerMember<uint16_t (DNSResponse::*)>("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.ids.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; });
- luaCtx.registerMember<uint16_t (DNSResponse::*)>("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.ids.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; });
- luaCtx.registerMember<int (DNSResponse::*)>("rcode", [](const DNSResponse& dq) -> int { return dq.getHeader()->rcode; }, [](DNSResponse& dq, int newRCode) { dq.getHeader()->rcode = newRCode; });
- luaCtx.registerMember<const ComboAddress (DNSResponse::*)>("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return dq.ids.origRemote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; });
- luaCtx.registerMember<dnsheader* (DNSResponse::*)>("dh", [](const DNSResponse& dr) -> dnsheader* { return const_cast<DNSResponse&>(dr).getHeader(); }, [](DNSResponse& dr, const dnsheader* dh) { *(dr.getHeader()) = *dh; });
- luaCtx.registerMember<uint16_t (DNSResponse::*)>("len", [](const DNSResponse& dq) -> uint16_t { return dq.getData().size(); }, [](DNSResponse& dq, uint16_t newlen) { dq.getMutableData().resize(newlen); });
- luaCtx.registerMember<uint8_t (DNSResponse::*)>("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.getHeader()->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; });
- luaCtx.registerMember<bool (DNSResponse::*)>("tcp", [](const DNSResponse& dq) -> bool { return dq.overTCP(); }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; });
- luaCtx.registerMember<bool (DNSResponse::*)>("skipCache", [](const DNSResponse& dq) -> bool { return dq.ids.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.ids.skipCache = newSkipCache; });
- luaCtx.registerMember<std::string (DNSResponse::*)>("pool", [](const DNSResponse& dq) -> std::string { return dq.ids.poolName; }, [](DNSResponse& dq, const std::string& newPoolName) { dq.ids.poolName = newPoolName; });
- luaCtx.registerFunction<void(DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](DNSResponse& dr, std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc) {
- editDNSPacketTTL(reinterpret_cast<char *>(dr.getMutableData().data()), dr.getData().size(), editFunc);
- });
- luaCtx.registerFunction<bool(DNSResponse::*)()const>("getDO", [](const DNSResponse& dq) {
- return getEDNSZ(dq) & EDNS_HEADER_FLAG_DO;
- });
- luaCtx.registerFunction<std::string(DNSResponse::*)()const>("getContent", [](const DNSResponse& dq) {
- return std::string(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size());
- });
- luaCtx.registerFunction<void(DNSResponse::*)(const std::string&)>("setContent", [](DNSResponse& dr, const std::string& raw) {
- uint16_t oldID = dr.getHeader()->id;
- auto& buffer = dr.getMutableData();
- buffer.clear();
- buffer.insert(buffer.begin(), raw.begin(), raw.end());
- reinterpret_cast<dnsheader*>(buffer.data())->id = oldID;
- });
-
- luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView>(DNSResponse::*)()const>("getEDNSOptions", [](const DNSResponse& dq) {
- if (dq.ednsOptions == nullptr) {
- parseEDNSOptions(dq);
- if (dq.ednsOptions == nullptr) {
- throw std::runtime_error("parseEDNSOptions should have populated the EDNS options");
- }
- }
-
- return *dq.ednsOptions;
- });
- luaCtx.registerFunction<std::string(DNSResponse::*)(void)const>("getTrailingData", [](const DNSResponse& dq) {
- return dq.getTrailingData();
- });
- luaCtx.registerFunction<bool(DNSResponse::*)(std::string)>("setTrailingData", [](DNSResponse& dq, const std::string& tail) {
- return dq.setTrailingData(tail);
- });
-
- luaCtx.registerFunction<void(DNSResponse::*)(std::string, std::string)>("setTag", [](DNSResponse& dr, const std::string& strLabel, const std::string& strValue) {
- dr.setTag(strLabel, strValue);
- });
-
- luaCtx.registerFunction<void(DNSResponse::*)(LuaAssociativeTable<std::string>)>("setTagArray", [](DNSResponse& dr, const LuaAssociativeTable<string>&tags) {
- for (const auto& tag : tags) {
- dr.setTag(tag.first, tag.second);
- }
- });
- luaCtx.registerFunction<string(DNSResponse::*)(std::string)const>("getTag", [](const DNSResponse& dr, const std::string& strLabel) {
- if (!dr.ids.qTag) {
- return string();
- }
-
- std::string strValue;
- const auto it = dr.ids.qTag->find(strLabel);
- if (it == dr.ids.qTag->cend()) {
- return string();
- }
- return it->second;
- });
- luaCtx.registerFunction<QTag(DNSResponse::*)(void)const>("getTagArray", [](const DNSResponse& dr) {
- if (!dr.ids.qTag) {
- QTag empty;
- return empty;
- }
-
- return *dr.ids.qTag;
- });
-
- luaCtx.registerFunction<std::string (DNSResponse::*)()const>("getProtocol", [](const DNSResponse& dr) {
- return dr.getProtocol().toPrettyString();
- });
-
- luaCtx.registerFunction<timespec(DNSResponse::*)()const>("getQueryTime", [](const DNSResponse& dr) {
- return dr.ids.queryRealTime.getStartTime();
- });
-
- luaCtx.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> reason) {
-#ifdef HAVE_NET_SNMP
- if (g_snmpAgent && g_snmpTrapsEnabled) {
- g_snmpAgent->sendDNSTrap(dr, reason ? *reason : "");
- }
-#endif /* HAVE_NET_SNMP */
- });
-
-#ifdef HAVE_DNS_OVER_HTTPS
- luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPPath", [](const DNSQuestion& dq) {
- if (dq.ids.du == nullptr) {
- return std::string();
- }
- return dq.ids.du->getHTTPPath();
- });
-
- luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPQueryString", [](const DNSQuestion& dq) {
- if (dq.ids.du == nullptr) {
- return std::string();
- }
- return dq.ids.du->getHTTPQueryString();
- });
-
- luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPHost", [](const DNSQuestion& dq) {
- if (dq.ids.du == nullptr) {
- return std::string();
- }
- return dq.ids.du->getHTTPHost();
- });
-
- luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPScheme", [](const DNSQuestion& dq) {
- if (dq.ids.du == nullptr) {
- return std::string();
- }
- return dq.ids.du->getHTTPScheme();
- });
-
- luaCtx.registerFunction<LuaAssociativeTable<std::string>(DNSQuestion::*)(void)const>("getHTTPHeaders", [](const DNSQuestion& dq) {
- if (dq.ids.du == nullptr) {
- return LuaAssociativeTable<std::string>();
- }
- return dq.ids.du->getHTTPHeaders();
- });
-
- luaCtx.registerFunction<void(DNSQuestion::*)(uint64_t statusCode, const std::string& body, const boost::optional<std::string> contentType)>("setHTTPResponse", [](DNSQuestion& dq, uint64_t statusCode, const std::string& body, const boost::optional<std::string> contentType) {
- if (dq.ids.du == nullptr) {
- return;
- }
- checkParameterBound("DNSQuestion::setHTTPResponse", statusCode, std::numeric_limits<uint16_t>::max());
- PacketBuffer vect(body.begin(), body.end());
- dq.ids.du->setHTTPResponse(statusCode, std::move(vect), contentType ? *contentType : "");
- });
-#endif /* HAVE_DNS_OVER_HTTPS */
-
- luaCtx.registerFunction<bool(DNSQuestion::*)(bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum)>("setNegativeAndAdditionalSOA", [](DNSQuestion& dq, bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum) {
- checkParameterBound("setNegativeAndAdditionalSOA", ttl, std::numeric_limits<uint32_t>::max());
- checkParameterBound("setNegativeAndAdditionalSOA", serial, std::numeric_limits<uint32_t>::max());
- checkParameterBound("setNegativeAndAdditionalSOA", refresh, std::numeric_limits<uint32_t>::max());
- checkParameterBound("setNegativeAndAdditionalSOA", retry, std::numeric_limits<uint32_t>::max());
- checkParameterBound("setNegativeAndAdditionalSOA", expire, std::numeric_limits<uint32_t>::max());
- checkParameterBound("setNegativeAndAdditionalSOA", minimum, std::numeric_limits<uint32_t>::max());
-
- return setNegativeAndAdditionalSOA(dq, nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum, false);
- });
-
- luaCtx.registerFunction<bool(DNSResponse::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSResponse& dr, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
- dr.asynchronous = true;
- return dnsdist::suspendResponse(dr, asyncID, queryID, timeoutMs);
- });
-
- luaCtx.registerFunction<bool(DNSResponse::*)(const DNSName& newName)>("changeName", [](DNSResponse& dr, const DNSName& newName) -> bool {
- if (!dnsdist::changeNameInDNSPacket(dr.getMutableData(), dr.ids.qname, newName)) {
- return false;
- }
- dr.ids.qname = newName;
- return true;
- });
-
- luaCtx.registerFunction<bool(DNSResponse::*)()>("restart", [](DNSResponse& dr) {
- if (!dr.ids.d_packet) {
- return false;
- }
- dr.asynchronous = true;
- dr.getMutableData() = *dr.ids.d_packet;
- auto query = dnsdist::getInternalQueryFromDQ(dr, false);
- return dnsdist::queueQueryResumptionEvent(std::move(query));
- });
-#endif /* DISABLE_NON_FFI_DQ_BINDINGS */
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "bpf-filter.hh"
-#include "config.h"
-#include "dnsdist.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-svc.hh"
-
-#include "dolog.hh"
-
-void setupLuaBindings(LuaContext& luaCtx, bool client)
-{
- luaCtx.writeFunction("vinfolog", [](const string& arg) {
- vinfolog("%s", arg);
- });
- luaCtx.writeFunction("infolog", [](const string& arg) {
- infolog("%s", arg);
- });
- luaCtx.writeFunction("errlog", [](const string& arg) {
- errlog("%s", arg);
- });
- luaCtx.writeFunction("warnlog", [](const string& arg) {
- warnlog("%s", arg);
- });
- luaCtx.writeFunction("show", [](const string& arg) {
- g_outputBuffer+=arg;
- g_outputBuffer+="\n";
- });
-
- /* Exceptions */
- luaCtx.registerFunction<string(std::exception_ptr::*)()const>("__tostring", [](const std::exception_ptr& eptr) {
- try {
- if (eptr) {
- std::rethrow_exception(eptr);
- }
- } catch(const std::exception& e) {
- return string(e.what());
- } catch(const PDNSException& e) {
- return e.reason;
- } catch(...) {
- return string("Unknown exception");
- }
- return string("No exception");
- });
-#ifndef DISABLE_POLICIES_BINDINGS
- /* ServerPolicy */
- luaCtx.writeFunction("newServerPolicy", [](string name, ServerPolicy::policyfunc_t policy) { return std::make_shared<ServerPolicy>(name, policy, true);});
- luaCtx.registerMember("name", &ServerPolicy::d_name);
- luaCtx.registerMember("policy", &ServerPolicy::d_policy);
- luaCtx.registerMember("ffipolicy", &ServerPolicy::d_ffipolicy);
- luaCtx.registerMember("isLua", &ServerPolicy::d_isLua);
- luaCtx.registerMember("isFFI", &ServerPolicy::d_isFFI);
- luaCtx.registerMember("isPerThread", &ServerPolicy::d_isPerThread);
- luaCtx.registerFunction("toString", &ServerPolicy::toString);
- luaCtx.registerFunction("__tostring", &ServerPolicy::toString);
-
- ServerPolicy policies[] = {
- ServerPolicy{"firstAvailable", firstAvailable, false},
- ServerPolicy{"roundrobin", roundrobin, false},
- ServerPolicy{"wrandom", wrandom, false},
- ServerPolicy{"whashed", whashed, false},
- ServerPolicy{"chashed", chashed, false},
- ServerPolicy{"leastOutstanding", leastOutstanding, false}
- };
- for (auto& policy : policies) {
- luaCtx.writeVariable(policy.d_name, policy);
- }
-
-#endif /* DISABLE_POLICIES_BINDINGS */
-
- /* ServerPool */
- luaCtx.registerFunction<void(std::shared_ptr<ServerPool>::*)(std::shared_ptr<DNSDistPacketCache>)>("setCache", [](std::shared_ptr<ServerPool> pool, std::shared_ptr<DNSDistPacketCache> cache) {
- if (pool) {
- pool->packetCache = cache;
- }
- });
- luaCtx.registerFunction("getCache", &ServerPool::getCache);
- luaCtx.registerFunction<void(std::shared_ptr<ServerPool>::*)()>("unsetCache", [](std::shared_ptr<ServerPool> pool) {
- if (pool) {
- pool->packetCache = nullptr;
- }
- });
- luaCtx.registerFunction("getECS", &ServerPool::getECS);
- luaCtx.registerFunction("setECS", &ServerPool::setECS);
-
-#ifndef DISABLE_DOWNSTREAM_BINDINGS
- /* DownstreamState */
- luaCtx.registerFunction<void(DownstreamState::*)(int)>("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); });
- luaCtx.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("addPool", [](std::shared_ptr<DownstreamState> s, string pool) {
- auto localPools = g_pools.getCopy();
- addServerToPool(localPools, pool, s);
- g_pools.setState(localPools);
- s->d_config.pools.insert(pool);
- });
- luaCtx.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](std::shared_ptr<DownstreamState> s, string pool) {
- auto localPools = g_pools.getCopy();
- removeServerFromPool(localPools, pool, s);
- g_pools.setState(localPools);
- s->d_config.pools.erase(pool);
- });
- luaCtx.registerFunction<uint64_t(DownstreamState::*)()const>("getOutstanding", [](const DownstreamState& s) { return s.outstanding.load(); });
- luaCtx.registerFunction<uint64_t(DownstreamState::*)()const>("getDrops", [](const DownstreamState& s) { return s.reuseds.load(); });
- luaCtx.registerFunction<double(DownstreamState::*)()const>("getLatency", [](const DownstreamState& s) { return s.getRelevantLatencyUsec(); });
- luaCtx.registerFunction("isUp", &DownstreamState::isUp);
- luaCtx.registerFunction("setDown", &DownstreamState::setDown);
- luaCtx.registerFunction("setUp", &DownstreamState::setUp);
- luaCtx.registerFunction<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setAuto", [](DownstreamState& s, boost::optional<bool> newStatus) {
- if (newStatus) {
- s.setUpStatus(*newStatus);
- }
- s.setAuto();
- });
- luaCtx.registerFunction<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setLazyAuto", [](DownstreamState& s, boost::optional<bool> newStatus) {
- if (newStatus) {
- s.setUpStatus(*newStatus);
- }
- s.setLazyAuto();
- });
- luaCtx.registerFunction<std::string(DownstreamState::*)()const>("getName", [](const DownstreamState& s) { return s.getName(); });
- luaCtx.registerFunction<std::string(DownstreamState::*)()const>("getNameWithAddr", [](const DownstreamState& s) { return s.getNameWithAddr(); });
- luaCtx.registerMember("upStatus", &DownstreamState::upStatus);
- luaCtx.registerMember<int (DownstreamState::*)>("weight",
- [](const DownstreamState& s) -> int {return s.d_config.d_weight;},
- [](DownstreamState& s, int newWeight) { s.setWeight(newWeight); }
- );
- luaCtx.registerMember<int (DownstreamState::*)>("order",
- [](const DownstreamState& s) -> int {return s.d_config.order; },
- [](DownstreamState& s, int newOrder) { s.d_config.order = newOrder; }
- );
- luaCtx.registerMember<const std::string(DownstreamState::*)>("name", [](const DownstreamState& backend) -> const std::string { return backend.getName(); }, [](DownstreamState& backend, const std::string& newName) { backend.setName(newName); });
- luaCtx.registerFunction<std::string(DownstreamState::*)()const>("getID", [](const DownstreamState& s) { return boost::uuids::to_string(*s.d_config.id); });
-#endif /* DISABLE_DOWNSTREAM_BINDINGS */
-
-#ifndef DISABLE_DNSHEADER_BINDINGS
- /* dnsheader */
- luaCtx.registerFunction<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) {
- dh.rd=v;
- });
-
- luaCtx.registerFunction<bool(dnsheader::*)()const>("getRD", [](const dnsheader& dh) {
- return (bool)dh.rd;
- });
-
- luaCtx.registerFunction<void(dnsheader::*)(bool)>("setRA", [](dnsheader& dh, bool v) {
- dh.ra=v;
- });
-
- luaCtx.registerFunction<bool(dnsheader::*)()const>("getRA", [](const dnsheader& dh) {
- return (bool)dh.ra;
- });
-
- luaCtx.registerFunction<void(dnsheader::*)(bool)>("setAD", [](dnsheader& dh, bool v) {
- dh.ad=v;
- });
-
- luaCtx.registerFunction<bool(dnsheader::*)()const>("getAD", [](const dnsheader& dh) {
- return (bool)dh.ad;
- });
-
- luaCtx.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
- dh.aa=v;
- });
-
- luaCtx.registerFunction<bool(dnsheader::*)()const>("getAA", [](const dnsheader& dh) {
- return (bool)dh.aa;
- });
-
- luaCtx.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
- dh.cd=v;
- });
-
- luaCtx.registerFunction<bool(dnsheader::*)()const >("getCD", [](const dnsheader& dh) {
- return (bool)dh.cd;
- });
-
- luaCtx.registerFunction<uint16_t(dnsheader::*)()const>("getID", [](const dnsheader& dh) {
- return ntohs(dh.id);
- });
-
- luaCtx.registerFunction<void(dnsheader::*)(bool)>("setTC", [](dnsheader& dh, bool v) {
- dh.tc=v;
- if(v) dh.ra = dh.rd; // you'll always need this, otherwise TC=1 gets ignored
- });
-
- luaCtx.registerFunction<void(dnsheader::*)(bool)>("setQR", [](dnsheader& dh, bool v) {
- dh.qr=v;
- });
-#endif /* DISABLE_DNSHEADER_BINDINGS */
-
-#ifndef DISABLE_COMBO_ADDR_BINDINGS
- /* ComboAddress */
- luaCtx.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); });
- luaCtx.writeFunction("newCAFromRaw", [](const std::string& raw, boost::optional<uint16_t> port) {
- if (raw.size() == 4) {
- struct sockaddr_in sin4;
- memset(&sin4, 0, sizeof(sin4));
- sin4.sin_family = AF_INET;
- memcpy(&sin4.sin_addr.s_addr, raw.c_str(), raw.size());
- if (port) {
- sin4.sin_port = htons(*port);
- }
- return ComboAddress(&sin4);
- }
- else if (raw.size() == 16) {
- struct sockaddr_in6 sin6;
- memset(&sin6, 0, sizeof(sin6));
- sin6.sin6_family = AF_INET6;
- memcpy(&sin6.sin6_addr.s6_addr, raw.c_str(), raw.size());
- if (port) {
- sin6.sin6_port = htons(*port);
- }
- return ComboAddress(&sin6);
- }
- return ComboAddress();
- });
- luaCtx.registerFunction<string(ComboAddress::*)()const>("tostring", [](const ComboAddress& ca) { return ca.toString(); });
- luaCtx.registerFunction<string(ComboAddress::*)()const>("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
- luaCtx.registerFunction<string(ComboAddress::*)()const>("__tostring", [](const ComboAddress& ca) { return ca.toString(); });
- luaCtx.registerFunction<string(ComboAddress::*)()const>("toString", [](const ComboAddress& ca) { return ca.toString(); });
- luaCtx.registerFunction<string(ComboAddress::*)()const>("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
- luaCtx.registerFunction<uint16_t(ComboAddress::*)()const>("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } );
- luaCtx.registerFunction<void(ComboAddress::*)(unsigned int)>("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); });
- luaCtx.registerFunction<bool(ComboAddress::*)()const>("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; });
- luaCtx.registerFunction<bool(ComboAddress::*)()const>("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; });
- luaCtx.registerFunction<bool(ComboAddress::*)()const>("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); });
- luaCtx.registerFunction<ComboAddress(ComboAddress::*)()const>("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); });
- luaCtx.registerFunction<bool(nmts_t::*)(const ComboAddress&)>("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); });
-#endif /* DISABLE_COMBO_ADDR_BINDINGS */
-
-#ifndef DISABLE_DNSNAME_BINDINGS
- /* DNSName */
- luaCtx.registerFunction("isPartOf", &DNSName::isPartOf);
- luaCtx.registerFunction<bool(DNSName::*)()>("chopOff", [](DNSName&dn ) { return dn.chopOff(); });
- luaCtx.registerFunction<unsigned int(DNSName::*)()const>("countLabels", [](const DNSName& name) { return name.countLabels(); });
- luaCtx.registerFunction<size_t(DNSName::*)()const>("hash", [](const DNSName& name) { return name.hash(); });
- luaCtx.registerFunction<size_t(DNSName::*)()const>("wirelength", [](const DNSName& name) { return name.wirelength(); });
- luaCtx.registerFunction<string(DNSName::*)()const>("tostring", [](const DNSName&dn ) { return dn.toString(); });
- luaCtx.registerFunction<string(DNSName::*)()const>("toString", [](const DNSName&dn ) { return dn.toString(); });
- luaCtx.registerFunction<string(DNSName::*)()const>("toStringNoDot", [](const DNSName&dn ) { return dn.toStringNoDot(); });
- luaCtx.registerFunction<string(DNSName::*)()const>("__tostring", [](const DNSName&dn ) { return dn.toString(); });
- luaCtx.registerFunction<string(DNSName::*)()const>("toDNSString", [](const DNSName&dn ) { return dn.toDNSString(); });
- luaCtx.registerFunction<DNSName(DNSName::*)(const DNSName&)const>("makeRelative", [](const DNSName& dn, const DNSName& to) { return dn.makeRelative(to); });
- luaCtx.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); });
- luaCtx.writeFunction("newDNSNameFromRaw", [](const std::string& name) { return DNSName(name.c_str(), name.size(), 0, false); });
- luaCtx.writeFunction("newSuffixMatchNode", []() { return SuffixMatchNode(); });
- luaCtx.writeFunction("newDNSNameSet", []() { return DNSNameSet(); });
-
- /* DNSNameSet */
- luaCtx.registerFunction<string(DNSNameSet::*)()const>("toString", [](const DNSNameSet&dns ) { return dns.toString(); });
- luaCtx.registerFunction<string(DNSNameSet::*)()const>("__tostring", [](const DNSNameSet&dns ) { return dns.toString(); });
- luaCtx.registerFunction<void(DNSNameSet::*)(DNSName&)>("add", [](DNSNameSet& dns, DNSName& dn) { dns.insert(dn); });
- luaCtx.registerFunction<bool(DNSNameSet::*)(DNSName&)>("check", [](DNSNameSet& dns, DNSName& dn) { return dns.find(dn) != dns.end(); });
- luaCtx.registerFunction("delete",(size_t (DNSNameSet::*)(const DNSName&)) &DNSNameSet::erase);
- luaCtx.registerFunction("size",(size_t (DNSNameSet::*)() const) &DNSNameSet::size);
- luaCtx.registerFunction("clear",(void (DNSNameSet::*)()) &DNSNameSet::clear);
- luaCtx.registerFunction("empty",(bool (DNSNameSet::*)() const) &DNSNameSet::empty);
-#endif /* DISABLE_DNSNAME_BINDINGS */
-
-#ifndef DISABLE_SUFFIX_MATCH_BINDINGS
- /* SuffixMatchNode */
- luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, std::string, LuaArray<DNSName>, LuaArray<std::string>> &name)>("add", [](SuffixMatchNode &smn, const boost::variant<DNSName, std::string, LuaArray<DNSName>, LuaArray<std::string>> &name) {
- if (name.type() == typeid(DNSName)) {
- auto n = boost::get<DNSName>(name);
- smn.add(n);
- return;
- }
- if (name.type() == typeid(std::string)) {
- auto n = boost::get<std::string>(name);
- smn.add(n);
- return;
- }
- if (name.type() == typeid(LuaArray<DNSName>)) {
- auto names = boost::get<LuaArray<DNSName>>(name);
- for (const auto& n : names) {
- smn.add(n.second);
- }
- return;
- }
- if (name.type() == typeid(LuaArray<std::string>)) {
- auto names = boost::get<LuaArray<string>>(name);
- for (const auto& n : names) {
- smn.add(n.second);
- }
- return;
- }
- });
- luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, string, LuaArray<DNSName>, LuaArray<std::string>> &name)>("remove", [](SuffixMatchNode &smn, const boost::variant<DNSName, string, LuaArray<DNSName>, LuaArray<std::string>> &name) {
- if (name.type() == typeid(DNSName)) {
- auto n = boost::get<DNSName>(name);
- smn.remove(n);
- return;
- }
- if (name.type() == typeid(string)) {
- auto n = boost::get<string>(name);
- DNSName d(n);
- smn.remove(d);
- return;
- }
- if (name.type() == typeid(LuaArray<DNSName>)) {
- auto names = boost::get<LuaArray<DNSName>>(name);
- for (const auto& n : names) {
- smn.remove(n.second);
- }
- return;
- }
- if (name.type() == typeid(LuaArray<std::string>)) {
- auto names = boost::get<LuaArray<std::string>>(name);
- for (const auto& n : names) {
- DNSName d(n.second);
- smn.remove(d);
- }
- return;
- }
- });
-
- luaCtx.registerFunction("check", (bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
- luaCtx.registerFunction<boost::optional<DNSName> (SuffixMatchNode::*)(const DNSName&) const>("getBestMatch", [](const SuffixMatchNode& smn, const DNSName& needle) {
- boost::optional<DNSName> result{boost::none};
- auto res = smn.getBestMatch(needle);
- if (res) {
- result = *res;
- }
- return result;
- });
-#endif /* DISABLE_SUFFIX_MATCH_BINDINGS */
-
-#ifndef DISABLE_NETMASK_BINDINGS
- /* Netmask */
- luaCtx.writeFunction("newNetmask", [](boost::variant<std::string,ComboAddress> s, boost::optional<uint8_t> bits) {
- if (s.type() == typeid(ComboAddress)) {
- auto ca = boost::get<ComboAddress>(s);
- if (bits) {
- return Netmask(ca, *bits);
- }
- return Netmask(ca);
- }
- else if (s.type() == typeid(std::string)) {
- auto str = boost::get<std::string>(s);
- return Netmask(str);
- }
- throw std::runtime_error("Invalid parameter passed to 'newNetmask()'");
- });
- luaCtx.registerFunction("empty", &Netmask::empty);
- luaCtx.registerFunction("getBits", &Netmask::getBits);
- luaCtx.registerFunction<ComboAddress(Netmask::*)()const>("getNetwork", [](const Netmask& nm) { return nm.getNetwork(); } ); // const reference makes this necessary
- luaCtx.registerFunction<ComboAddress(Netmask::*)()const>("getMaskedNetwork", [](const Netmask& nm) { return nm.getMaskedNetwork(); } );
- luaCtx.registerFunction("isIpv4", &Netmask::isIPv4);
- luaCtx.registerFunction("isIPv4", &Netmask::isIPv4);
- luaCtx.registerFunction("isIpv6", &Netmask::isIPv6);
- luaCtx.registerFunction("isIPv6", &Netmask::isIPv6);
- luaCtx.registerFunction("match", (bool (Netmask::*)(const string&) const)&Netmask::match);
- luaCtx.registerFunction("toString", &Netmask::toString);
- luaCtx.registerFunction("__tostring", &Netmask::toString);
- luaCtx.registerEqFunction(&Netmask::operator==);
- luaCtx.registerToStringFunction(&Netmask::toString);
-
- /* NetmaskGroup */
- luaCtx.writeFunction("newNMG", []() { return NetmaskGroup(); });
- luaCtx.registerFunction<void(NetmaskGroup::*)(const std::string&mask)>("addMask", [](NetmaskGroup&nmg, const std::string& mask)
- {
- nmg.addMask(mask);
- });
- luaCtx.registerFunction<void(NetmaskGroup::*)(const std::map<ComboAddress,int>& map)>("addMasks", [](NetmaskGroup&nmg, const std::map<ComboAddress,int>& map)
- {
- for (const auto& entry : map) {
- nmg.addMask(Netmask(entry.first));
- }
- });
-
- luaCtx.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match);
- luaCtx.registerFunction("size", &NetmaskGroup::size);
- luaCtx.registerFunction("clear", &NetmaskGroup::clear);
- luaCtx.registerFunction<string(NetmaskGroup::*)()const>("toString", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); });
- luaCtx.registerFunction<string(NetmaskGroup::*)()const>("__tostring", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); });
-#endif /* DISABLE_NETMASK_BINDINGS */
-
-#ifndef DISABLE_QPS_LIMITER_BINDINGS
- /* QPSLimiter */
- luaCtx.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); });
- luaCtx.registerFunction("check", &QPSLimiter::check);
-#endif /* DISABLE_QPS_LIMITER_BINDINGS */
-
-#ifndef DISABLE_CLIENT_STATE_BINDINGS
- /* ClientState */
- luaCtx.registerFunction<std::string(ClientState::*)()const>("toString", [](const ClientState& fe) {
- setLuaNoSideEffect();
- return fe.local.toStringWithPort();
- });
- luaCtx.registerFunction<std::string(ClientState::*)()const>("__tostring", [](const ClientState& fe) {
- setLuaNoSideEffect();
- return fe.local.toStringWithPort();
- });
- luaCtx.registerFunction<std::string(ClientState::*)()const>("getType", [](const ClientState& fe) {
- setLuaNoSideEffect();
- return fe.getType();
- });
- luaCtx.registerFunction<std::string(ClientState::*)()const>("getConfiguredTLSProvider", [](const ClientState& fe) {
- setLuaNoSideEffect();
- if (fe.tlsFrontend != nullptr) {
- return fe.tlsFrontend->getRequestedProvider();
- }
- else if (fe.dohFrontend != nullptr) {
- return std::string("openssl");
- }
- return std::string();
- });
- luaCtx.registerFunction<std::string(ClientState::*)()const>("getEffectiveTLSProvider", [](const ClientState& fe) {
- setLuaNoSideEffect();
- if (fe.tlsFrontend != nullptr) {
- return fe.tlsFrontend->getEffectiveProvider();
- }
- else if (fe.dohFrontend != nullptr) {
- return std::string("openssl");
- }
- return std::string();
- });
- luaCtx.registerMember("muted", &ClientState::muted);
-#ifdef HAVE_EBPF
- luaCtx.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
- if (bpf) {
- frontend.attachFilter(bpf, frontend.getSocket());
- }
- });
- luaCtx.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
- frontend.detachFilter(frontend.getSocket());
- });
-#endif /* HAVE_EBPF */
-#endif /* DISABLE_CLIENT_STATE_BINDINGS */
-
- /* BPF Filter */
-#ifdef HAVE_EBPF
- using bpfopts_t = LuaAssociativeTable<boost::variant<bool, uint32_t, std::string>>;
- luaCtx.writeFunction("newBPFFilter", [client](bpfopts_t opts) {
- if (client) {
- return std::shared_ptr<BPFFilter>(nullptr);
- }
- std::unordered_map<std::string, BPFFilter::MapConfiguration> mapsConfig;
-
- const auto convertParamsToConfig = [&](const std::string& name, BPFFilter::MapType type) {
- BPFFilter::MapConfiguration config;
- config.d_type = type;
- if (const string key = name + "MaxItems"; opts.count(key)) {
- const auto& tmp = opts.at(key);
- if (tmp.type() != typeid(uint32_t)) {
- throw std::runtime_error("params is invalid");
- }
- const auto& params = boost::get<uint32_t>(tmp);
- config.d_maxItems = params;
- }
-
- if (const string key = name + "PinnedPath"; opts.count(key)) {
- auto& tmp = opts.at(key);
- if (tmp.type() != typeid(string)) {
- throw std::runtime_error("params is invalid");
- }
- auto& params = boost::get<string>(tmp);
- config.d_pinnedPath = std::move(params);
- }
- mapsConfig[name] = config;
- };
-
- convertParamsToConfig("ipv4", BPFFilter::MapType::IPv4);
- convertParamsToConfig("ipv6", BPFFilter::MapType::IPv6);
- convertParamsToConfig("qnames", BPFFilter::MapType::QNames);
- convertParamsToConfig("cidr4", BPFFilter::MapType::CIDR4);
- convertParamsToConfig("cidr6", BPFFilter::MapType::CIDR6);
-
- BPFFilter::MapFormat format = BPFFilter::MapFormat::Legacy;
- bool external = false;
- if (opts.count("external")) {
- const auto& tmp = opts.at("external");
- if (tmp.type() != typeid(bool)) {
- throw std::runtime_error("params is invalid");
- }
- if ((external = boost::get<bool>(tmp))) {
- format = BPFFilter::MapFormat::WithActions;
- }
- }
-
- return std::make_shared<BPFFilter>(mapsConfig, format, external);
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca, boost::optional<uint32_t> action)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca, boost::optional<uint32_t> action) {
- if (bpf) {
- if (!action) {
- return bpf->block(ca, BPFFilter::MatchAction::Drop);
- }
- else {
- BPFFilter::MatchAction match;
-
- switch (*action) {
- case 0:
- match = BPFFilter::MatchAction::Pass;
- break;
- case 1:
- match = BPFFilter::MatchAction::Drop;
- break;
- case 2:
- match = BPFFilter::MatchAction::Truncate;
- break;
- default:
- throw std::runtime_error("Unsupported action for BPFFilter::block");
- }
- return bpf->block(ca, match);
- }
- }
- });
- luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const string& range, uint32_t action, boost::optional<bool> force)>("addRangeRule", [](std::shared_ptr<BPFFilter> bpf, const string& range, uint32_t action, boost::optional<bool> force) {
- if (!bpf) {
- return;
- }
- BPFFilter::MatchAction match;
- switch (action) {
- case 0:
- match = BPFFilter::MatchAction::Pass;
- break;
- case 1:
- match = BPFFilter::MatchAction::Drop;
- break;
- case 2:
- match = BPFFilter::MatchAction::Truncate;
- break;
- default:
- throw std::runtime_error("Unsupported action for BPFFilter::block");
- }
- return bpf->addRangeRule(Netmask(range), force ? *force : false, match);
- });
- luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action) {
- if (bpf) {
- if (!action) {
- return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype ? *qtype : 255);
- }
- else {
- BPFFilter::MatchAction match;
-
- switch (*action) {
- case 0:
- match = BPFFilter::MatchAction::Pass;
- break;
- case 1:
- match = BPFFilter::MatchAction::Drop;
- break;
- case 2:
- match = BPFFilter::MatchAction::Truncate;
- break;
- default:
- throw std::runtime_error("Unsupported action for BPFFilter::blockQName");
- }
- return bpf->block(qname, match, qtype ? *qtype : 255);
- }
- }
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
- if (bpf) {
- return bpf->unblock(ca);
- }
- });
- luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const string& range)>("rmRangeRule", [](std::shared_ptr<BPFFilter> bpf, const string& range) {
- if (!bpf) {
- return;
- }
- bpf->rmRangeRule(Netmask(range));
- });
- luaCtx.registerFunction<std::string (std::shared_ptr<BPFFilter>::*)() const>("lsRangeRule", [](const std::shared_ptr<BPFFilter> bpf) {
- setLuaNoSideEffect();
- std::string res;
- if (!bpf) {
- return res;
- }
- const auto rangeStat = bpf->getRangeRule();
- for (const auto& value : rangeStat) {
- if (value.first.isIPv4()) {
- res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + "\n";
- }
- else if (value.first.isIPv6()) {
- res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]\n";
- }
- }
- return res;
- });
- luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
- if (bpf) {
- return bpf->unblock(qname, qtype ? *qtype : 255);
- }
- });
-
- luaCtx.registerFunction<std::string(std::shared_ptr<BPFFilter>::*)()const>("getStats", [](const std::shared_ptr<BPFFilter> bpf) {
- setLuaNoSideEffect();
- std::string res;
- if (bpf) {
- auto stats = bpf->getAddrStats();
- for (const auto& value : stats) {
- if (value.first.sin4.sin_family == AF_INET) {
- res += value.first.toString() + ": " + std::to_string(value.second) + "\n";
- }
- else if (value.first.sin4.sin_family == AF_INET6) {
- res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n";
- }
- }
- const auto rangeStat = bpf->getRangeRule();
- for (const auto& value : rangeStat) {
- if (value.first.isIPv4()) {
- res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + ": " + std::to_string(value.second.counter) + "\n";
- }
- else if (value.first.isIPv6()) {
- res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]: " + std::to_string(value.second.counter) + "\n";
- }
- }
- auto qstats = bpf->getQNameStats();
- for (const auto& value : qstats) {
- res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n";
- }
- }
- return res;
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> bpf) {
- std::string res;
- if (!g_configurationDone) {
- throw std::runtime_error("attachToAllBinds() cannot be used at configuration time!");
- return;
- }
- if (bpf) {
- for (const auto& frontend : g_frontends) {
- frontend->attachFilter(bpf, frontend->getSocket());
- }
- }
- });
-
- luaCtx.writeFunction("newDynBPFFilter", [client](std::shared_ptr<BPFFilter> bpf) {
- if (client) {
- return std::shared_ptr<DynBPFFilter>(nullptr);
- }
- return std::make_shared<DynBPFFilter>(bpf);
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](std::shared_ptr<DynBPFFilter> dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
- if (dbpf) {
- struct timespec until;
- clock_gettime(CLOCK_MONOTONIC, &until);
- until.tv_sec += seconds ? *seconds : 10;
- dbpf->block(addr, until);
- }
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](std::shared_ptr<DynBPFFilter> dbpf) {
- if (dbpf) {
- struct timespec now;
- clock_gettime(CLOCK_MONOTONIC, &now);
- dbpf->purgeExpired(now);
- }
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(LuaTypeOrArrayOf<std::string>)>("excludeRange", [](std::shared_ptr<DynBPFFilter> dbpf, LuaTypeOrArrayOf<std::string> ranges) {
- if (!dbpf) {
- return;
- }
-
- if (ranges.type() == typeid(LuaArray<std::string>)) {
- for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
- dbpf->excludeRange(Netmask(range.second));
- }
- }
- else {
- dbpf->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
- }
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(LuaTypeOrArrayOf<std::string>)>("includeRange", [](std::shared_ptr<DynBPFFilter> dbpf, LuaTypeOrArrayOf<std::string> ranges) {
- if (!dbpf) {
- return;
- }
-
- if (ranges.type() == typeid(LuaArray<std::string>)) {
- for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
- dbpf->includeRange(Netmask(range.second));
- }
- }
- else {
- dbpf->includeRange(Netmask(*boost::get<std::string>(&ranges)));
- }
- });
-#endif /* HAVE_EBPF */
-
- /* EDNSOptionView */
- luaCtx.registerFunction<size_t(EDNSOptionView::*)()const>("count", [](const EDNSOptionView& option) {
- return option.values.size();
- });
- luaCtx.registerFunction<std::vector<string>(EDNSOptionView::*)()const>("getValues", [] (const EDNSOptionView& option) {
- std::vector<string> values;
- for (const auto& value : option.values) {
- values.push_back(std::string(value.content, value.size));
- }
- return values;
- });
-
- luaCtx.writeFunction("newDOHResponseMapEntry", [](const std::string& regex, uint64_t status, const std::string& content, boost::optional<LuaAssociativeTable<std::string>> customHeaders) {
- checkParameterBound("newDOHResponseMapEntry", status, std::numeric_limits<uint16_t>::max());
- boost::optional<LuaAssociativeTable<std::string>> headers{boost::none};
- if (customHeaders) {
- headers = LuaAssociativeTable<std::string>();
- for (const auto& header : *customHeaders) {
- (*headers)[boost::to_lower_copy(header.first)] = header.second;
- }
- }
- return std::make_shared<DOHResponseMapEntry>(regex, status, PacketBuffer(content.begin(), content.end()), headers);
- });
-
- luaCtx.writeFunction("newSVCRecordParameters", [](uint64_t priority, const std::string& target, boost::optional<svcParamsLua_t> additionalParameters)
- {
- checkParameterBound("newSVCRecordParameters", priority, std::numeric_limits<uint16_t>::max());
- SVCRecordParameters parameters;
- if (additionalParameters) {
- parameters = parseSVCParameters(*additionalParameters);
- }
- parameters.priority = priority;
- parameters.target = DNSName(target);
-
- return parameters;
- });
-
- luaCtx.writeFunction("getListOfNetworkInterfaces", []() {
- LuaArray<std::string> result;
- auto itfs = getListOfNetworkInterfaces();
- int counter = 1;
- for (const auto& itf : itfs) {
- result.push_back({counter++, itf});
- }
- return result;
- });
-
- luaCtx.writeFunction("getListOfAddressesOfNetworkInterface", [](const std::string& itf) {
- LuaArray<std::string> result;
- auto addrs = getListOfAddressesOfNetworkInterface(itf);
- int counter = 1;
- for (const auto& addr : addrs) {
- result.push_back({counter++, addr.toString()});
- }
- return result;
- });
-
- luaCtx.writeFunction("getListOfRangesOfNetworkInterface", [](const std::string& itf) {
- LuaArray<std::string> result;
- auto addrs = getListOfRangesOfNetworkInterface(itf);
- int counter = 1;
- for (const auto& addr : addrs) {
- result.push_back({counter++, addr.toString()});
- }
- return result;
- });
-
- luaCtx.writeFunction("getMACAddress", [](const std::string& ip) {
- return getMACAddress(ComboAddress(ip));
- });
-
- luaCtx.writeFunction("getCurrentTime", []() -> timespec {
- timespec now;
- if (gettime(&now, true) < 0) {
- unixDie("Getting timestamp");
- }
- return now;
- });
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include <fcntl.h>
-
-#include "dnsdist.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-dynblocks.hh"
-#include "dnsdist-nghttp2.hh"
-#include "dnsdist-rings.hh"
-#include "dnsdist-tcp.hh"
-
-#include "statnode.hh"
-
-#ifndef DISABLE_TOP_N_BINDINGS
-static LuaArray<std::vector<boost::variant<string,double>>> getGenResponses(uint64_t top, boost::optional<int> labels, std::function<bool(const Rings::Response&)> pred)
-{
- setLuaNoSideEffect();
- map<DNSName, unsigned int> counts;
- unsigned int total=0;
- {
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->respRing.lock();
- if (!labels) {
- for(const auto& a : *rl) {
- if(!pred(a))
- continue;
- counts[a.name]++;
- total++;
- }
- }
- else {
- unsigned int lab = *labels;
- for(const auto& a : *rl) {
- if(!pred(a))
- continue;
-
- DNSName temp(a.name);
- temp.trimToLabels(lab);
- counts[temp]++;
- total++;
- }
- }
- }
- }
- // cout<<"Looked at "<<total<<" responses, "<<counts.size()<<" different ones"<<endl;
- vector<pair<unsigned int, DNSName>> rcounts;
- rcounts.reserve(counts.size());
- for (const auto& c : counts)
- rcounts.emplace_back(c.second, c.first.makeLowerCase());
-
- sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a,
- const decltype(rcounts)::value_type& b) {
- return b.first < a.first;
- });
-
- LuaArray<vector<boost::variant<string,double>>> ret;
- ret.reserve(std::min(rcounts.size(), static_cast<size_t>(top + 1U)));
- int count = 1;
- unsigned int rest = 0;
- for (const auto& rc : rcounts) {
- if (count == static_cast<int>(top + 1)) {
- rest+=rc.first;
- }
- else {
- ret.push_back({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}});
- }
- }
-
- if (total > 0) {
- ret.push_back({count, {"Rest", rest, 100.0*rest/total}});
- }
- else {
- ret.push_back({count, {"Rest", rest, 100.0 }});
- }
-
- return ret;
-}
-#endif /* DISABLE_TOP_N_BINDINGS */
-
-#ifndef DISABLE_DYNBLOCKS
-#ifndef DISABLE_DEPRECATED_DYNBLOCK
-
-typedef std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
-
-static counts_t filterScore(const counts_t& counts,
- double delta, unsigned int rate)
-{
- counts_t ret;
-
- double lim = delta*rate;
- for(const auto& c : counts) {
- if (c.second > lim) {
- ret[c.first] = c.second;
- }
- }
-
- return ret;
-}
-
-using statvisitor_t = std::function<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
-
-static void statNodeRespRing(statvisitor_t visitor, uint64_t seconds)
-{
- struct timespec cutoff, now;
- gettime(&now);
- cutoff = now;
- cutoff.tv_sec -= seconds;
-
- StatNode root;
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->respRing.lock();
-
- for(const auto& c : *rl) {
- if (now < c.when){
- continue;
- }
-
- if (seconds && c.when < cutoff) {
- continue;
- }
-
- bool hit = c.ds.sin4.sin_family == 0;
- if (!hit && c.ds.isIPv4() && c.ds.sin4.sin_addr.s_addr == 0 && c.ds.sin4.sin_port == 0) {
- hit = true;
- }
-
- root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, hit, boost::none);
- }
- }
-
- StatNode::Stat node;
- root.visit([visitor](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
- visitor(*node_, self, children);}, node);
-}
-
-static LuaArray<LuaAssociativeTable<std::string>> getRespRing(boost::optional<int> rcode)
-{
- typedef LuaAssociativeTable<std::string> entry_t;
- LuaArray<entry_t> ret;
-
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->respRing.lock();
-
- int count = 1;
- for (const auto& c : *rl) {
- if (rcode && (rcode.get() != c.dh.rcode)) {
- continue;
- }
- entry_t e;
- e["qname"] = c.name.toString();
- e["rcode"] = std::to_string(c.dh.rcode);
- ret.emplace_back(count, std::move(e));
- count++;
- }
- }
-
- return ret;
-}
-
-static counts_t exceedRespGen(unsigned int rate, int seconds, std::function<void(counts_t&, const Rings::Response&)> T)
-{
- counts_t counts;
- struct timespec cutoff, mintime, now;
- gettime(&now);
- cutoff = mintime = now;
- cutoff.tv_sec -= seconds;
-
- counts.reserve(g_rings.getNumberOfResponseEntries());
-
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->respRing.lock();
- for(const auto& c : *rl) {
-
- if(seconds && c.when < cutoff)
- continue;
- if(now < c.when)
- continue;
-
- T(counts, c);
- if(c.when < mintime)
- mintime = c.when;
- }
- }
-
- double delta = seconds ? seconds : DiffTime(now, mintime);
- return filterScore(counts, delta, rate);
-}
-
-static counts_t exceedQueryGen(unsigned int rate, int seconds, std::function<void(counts_t&, const Rings::Query&)> T)
-{
- counts_t counts;
- struct timespec cutoff, mintime, now;
- gettime(&now);
- cutoff = mintime = now;
- cutoff.tv_sec -= seconds;
-
- counts.reserve(g_rings.getNumberOfQueryEntries());
-
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->queryRing.lock();
- for(const auto& c : *rl) {
- if(seconds && c.when < cutoff)
- continue;
- if(now < c.when)
- continue;
- T(counts, c);
- if(c.when < mintime)
- mintime = c.when;
- }
- }
-
- double delta = seconds ? seconds : DiffTime(now, mintime);
- return filterScore(counts, delta, rate);
-}
-
-
-static counts_t exceedRCode(unsigned int rate, int seconds, int rcode)
-{
- return exceedRespGen(rate, seconds, [rcode](counts_t& counts, const Rings::Response& r)
- {
- if(r.dh.rcode == rcode)
- counts[r.requestor]++;
- });
-}
-
-static counts_t exceedRespByterate(unsigned int rate, int seconds)
-{
- return exceedRespGen(rate, seconds, [](counts_t& counts, const Rings::Response& r)
- {
- counts[r.requestor]+=r.size;
- });
-}
-
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
-#endif /* DISABLE_DYNBLOCKS */
-
-void setupLuaInspection(LuaContext& luaCtx)
-{
-#ifndef DISABLE_TOP_N_BINDINGS
- luaCtx.writeFunction("topClients", [](boost::optional<uint64_t> top_) {
- setLuaNoSideEffect();
- uint64_t top = top_ ? *top_ : 10U;
- map<ComboAddress, unsigned int,ComboAddress::addressOnlyLessThan > counts;
- unsigned int total=0;
- {
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->queryRing.lock();
- for(const auto& c : *rl) {
- counts[c.requestor]++;
- total++;
- }
- }
- }
- vector<pair<unsigned int, ComboAddress>> rcounts;
- rcounts.reserve(counts.size());
- for(const auto& c : counts)
- rcounts.emplace_back(c.second, c.first);
-
- sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a,
- const decltype(rcounts)::value_type& b) {
- return b.first < a.first;
- });
- unsigned int count=1, rest=0;
- boost::format fmt("%4d %-40s %4d %4.1f%%\n");
- for(const auto& rc : rcounts) {
- if(count==top+1)
- rest+=rc.first;
- else
- g_outputBuffer += (fmt % (count++) % rc.second.toString() % rc.first % (100.0*rc.first/total)).str();
- }
- g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0*rest/total : 100.0)).str();
- });
-
- luaCtx.writeFunction("getTopQueries", [](uint64_t top, boost::optional<int> labels) {
- setLuaNoSideEffect();
- map<DNSName, unsigned int> counts;
- unsigned int total=0;
- if(!labels) {
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->queryRing.lock();
- for(const auto& a : *rl) {
- counts[a.name]++;
- total++;
- }
- }
- }
- else {
- unsigned int lab = *labels;
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->queryRing.lock();
- // coverity[auto_causes_copy]
- for (auto a : *rl) {
- a.name.trimToLabels(lab);
- counts[a.name]++;
- total++;
- }
- }
- }
- // cout<<"Looked at "<<total<<" queries, "<<counts.size()<<" different ones"<<endl;
- vector<pair<unsigned int, DNSName>> rcounts;
- rcounts.reserve(counts.size());
- for(const auto& c : counts)
- rcounts.emplace_back(c.second, c.first.makeLowerCase());
-
- sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a,
- const decltype(rcounts)::value_type& b) {
- return b.first < a.first;
- });
-
- std::unordered_map<unsigned int, vector<boost::variant<string,double>>> ret;
- unsigned int count=1, rest=0;
- for(const auto& rc : rcounts) {
- if(count==top+1)
- rest+=rc.first;
- else
- ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}});
- }
-
- if (total > 0) {
- ret.insert({count, {"Rest", rest, 100.0*rest/total}});
- }
- else {
- ret.insert({count, {"Rest", rest, 100.0}});
- }
-
- return ret;
-
- });
-
- luaCtx.executeCode(R"(function topQueries(top, labels) top = top or 10; for k,v in ipairs(getTopQueries(top,labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2], v[3])) end end)");
-
- luaCtx.writeFunction("getResponseRing", []() {
- setLuaNoSideEffect();
- size_t totalEntries = 0;
- std::vector<boost::circular_buffer<Rings::Response>> rings;
- rings.reserve(g_rings.getNumberOfShards());
- for (const auto& shard : g_rings.d_shards) {
- {
- auto rl = shard->respRing.lock();
- rings.push_back(*rl);
- }
- totalEntries += rings.back().size();
- }
- vector<std::unordered_map<string, boost::variant<string, unsigned int> > > ret;
- ret.reserve(totalEntries);
- decltype(ret)::value_type item;
- for (size_t idx = 0; idx < rings.size(); idx++) {
- for(const auto& r : rings[idx]) {
- item["name"]=r.name.toString();
- item["qtype"]=r.qtype;
- item["rcode"]=r.dh.rcode;
- item["usec"]=r.usec;
- ret.push_back(item);
- }
- }
- return ret;
- });
-
- luaCtx.writeFunction("getTopResponses", [](uint64_t top, uint64_t kind, boost::optional<int> labels) {
- return getGenResponses(top, labels, [kind](const Rings::Response& r) { return r.dh.rcode == kind; });
- });
-
- luaCtx.executeCode(R"(function topResponses(top, kind, labels) top = top or 10; kind = kind or 0; for k,v in ipairs(getTopResponses(top, kind, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
-
-
- luaCtx.writeFunction("getSlowResponses", [](uint64_t top, uint64_t msec, boost::optional<int> labels) {
- return getGenResponses(top, labels, [msec](const Rings::Response& r) { return r.usec > msec*1000; });
- });
-
-
- luaCtx.executeCode(R"(function topSlow(top, msec, labels) top = top or 10; msec = msec or 500; for k,v in ipairs(getSlowResponses(top, msec, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
-
- luaCtx.writeFunction("getTopBandwidth", [](uint64_t top) {
- setLuaNoSideEffect();
- return g_rings.getTopBandwidth(top);
- });
-
- luaCtx.executeCode(R"(function topBandwidth(top) top = top or 10; for k,v in ipairs(getTopBandwidth(top)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
-#endif /* DISABLE_TOP_N_BINDINGS */
-
- luaCtx.writeFunction("delta", []() {
- setLuaNoSideEffect();
- // we hold the lua lock already!
- for(const auto& d : g_confDelta) {
- struct tm tm;
- localtime_r(&d.first.tv_sec, &tm);
- char date[80];
- strftime(date, sizeof(date)-1, "-- %a %b %d %Y %H:%M:%S %Z\n", &tm);
- g_outputBuffer += date;
- g_outputBuffer += d.second + "\n";
- }
- });
-
- luaCtx.writeFunction("grepq", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<unsigned int> limit, boost::optional<LuaAssociativeTable<std::string>> options) {
- setLuaNoSideEffect();
- boost::optional<Netmask> nm;
- boost::optional<DNSName> dn;
- int msec = -1;
- std::unique_ptr<FILE, decltype(&fclose)> outputFile{nullptr, fclose};
-
- if (options) {
- std::string outputFileName;
- if (getOptionalValue<std::string>(options, "outputFile", outputFileName) > 0) {
- int fd = open(outputFileName.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600);
- if (fd < 0) {
- g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
- return;
- }
- outputFile = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(fd, "w"), fclose);
- if (outputFile == nullptr) {
- g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
- close(fd);
- return;
- }
- }
- checkAllParametersConsumed("grepq", options);
- }
-
- vector<string> vec;
- auto str = boost::get<string>(&inp);
- if (str) {
- vec.push_back(*str);
- }
- else {
- auto v = boost::get<LuaArray<std::string>>(inp);
- for (const auto& a: v) {
- vec.push_back(a.second);
- }
- }
-
- for (const auto& s : vec) {
- try {
- nm = Netmask(s);
- }
- catch (...) {
- if (boost::ends_with(s,"ms") && sscanf(s.c_str(), "%ums", &msec)) {
- ;
- }
- else {
- try {
- dn = DNSName(s);
- }
- catch (...) {
- g_outputBuffer = "Could not parse '"+s+"' as domain name or netmask";
- return;
- }
- }
- }
- }
-
- std::vector<Rings::Query> qr;
- std::vector<Rings::Response> rr;
- qr.reserve(g_rings.getNumberOfQueryEntries());
- rr.reserve(g_rings.getNumberOfResponseEntries());
- for (const auto& shard : g_rings.d_shards) {
- {
- auto rl = shard->queryRing.lock();
- for (const auto& entry : *rl) {
- qr.push_back(entry);
- }
- }
- {
- auto rl = shard->respRing.lock();
- for (const auto& entry : *rl) {
- rr.push_back(entry);
- }
- }
- }
-
- sort(qr.begin(), qr.end(), [](const decltype(qr)::value_type& a, const decltype(qr)::value_type& b) {
- return b.when < a.when;
- });
-
- sort(rr.begin(), rr.end(), [](const decltype(rr)::value_type& a, const decltype(rr)::value_type& b) {
- return b.when < a.when;
- });
-
- unsigned int num=0;
- struct timespec now;
- gettime(&now);
-
- std::multimap<struct timespec, string> out;
-
- boost::format fmt("%-7.1f %-47s %-12s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %-s\n");
- const auto headLine = (fmt % "Time" % "Client" % "Protocol" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str();
- if (!outputFile) {
- g_outputBuffer += headLine;
- }
- else {
- fprintf(outputFile.get(), "%s", headLine.c_str());
- }
-
- if (msec == -1) {
- for (const auto& c : qr) {
- bool nmmatch = true;
- bool dnmatch = true;
- if (nm) {
- nmmatch = nm->match(c.requestor);
- }
- if (dn) {
- if (c.name.empty()) {
- dnmatch = false;
- }
- else {
- dnmatch = c.name.isPartOf(*dn);
- }
- }
- if (nmmatch && dnmatch) {
- QType qt(c.qtype);
- std::string extra;
- if (c.dh.opcode != 0) {
- extra = " (" + Opcode::to_s(c.dh.opcode) + ")";
- }
- out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % dnsdist::Protocol(c.protocol).toString() % "" % htons(c.dh.id) % c.name.toString() % qt.toString() % "" % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % ("Question" + extra)).str());
-
- if (limit && *limit == ++num) {
- break;
- }
- }
- }
- }
- num = 0;
-
- string extra;
- for (const auto& c : rr) {
- bool nmmatch = true;
- bool dnmatch = true;
- bool msecmatch = true;
- if (nm) {
- nmmatch = nm->match(c.requestor);
- }
- if (dn) {
- if (c.name.empty()) {
- dnmatch = false;
- }
- else {
- dnmatch = c.name.isPartOf(*dn);
- }
- }
- if (msec != -1) {
- msecmatch = (c.usec/1000 > (unsigned int)msec);
- }
-
- if (nmmatch && dnmatch && msecmatch) {
- QType qt(c.qtype);
- if (!c.dh.rcode) {
- extra = ". " +std::to_string(htons(c.dh.ancount)) + " answers";
- }
- else {
- extra.clear();
- }
-
- std::string server = c.ds.toStringWithPort();
- std::string protocol = dnsdist::Protocol(c.protocol).toString();
- if (server == "0.0.0.0:0") {
- server = "Cache";
- protocol = "-";
- }
- if (c.usec != std::numeric_limits<decltype(c.usec)>::max()) {
- out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % protocol % server % htons(c.dh.id) % c.name.toString() % qt.toString() % (c.usec / 1000.0) % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str());
- }
- else {
- out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % protocol % server % htons(c.dh.id) % c.name.toString() % qt.toString() % "T.O" % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str());
- }
-
- if (limit && *limit == ++num) {
- break;
- }
- }
- }
-
- for (const auto& p : out) {
- if (!outputFile) {
- g_outputBuffer += p.second;
- }
- else {
- fprintf(outputFile.get(), "%s", p.second.c_str());
- }
- }
- });
-
- luaCtx.writeFunction("showResponseLatency", []() {
- setLuaNoSideEffect();
- map<double, unsigned int> histo;
- double bin=100;
- for(int i=0; i < 15; ++i) {
- histo[bin];
- bin*=2;
- }
-
- double totlat=0;
- unsigned int size=0;
- {
- for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->respRing.lock();
- for(const auto& r : *rl) {
- /* skip actively discovered timeouts */
- if (r.usec == std::numeric_limits<unsigned int>::max())
- continue;
-
- ++size;
- auto iter = histo.lower_bound(r.usec);
- if(iter != histo.end())
- iter->second++;
- else
- histo.rbegin()++;
- totlat+=r.usec;
- }
- }
- }
-
- if (size == 0) {
- g_outputBuffer = "No traffic yet.\n";
- return;
- }
-
- g_outputBuffer = (boost::format("Average response latency: %.02f ms\n") % (0.001*totlat/size)).str();
- double highest=0;
-
- for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) {
- highest=std::max(highest, iter->second*1.0);
- }
- boost::format fmt("%7.2f\t%s\n");
- g_outputBuffer += (fmt % "ms" % "").str();
-
- for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) {
- int stars = (70.0 * iter->second/highest);
- char c='*';
- if(!stars && iter->second) {
- stars=1; // you get 1 . to show something is there..
- if(70.0*iter->second/highest > 0.5)
- c=':';
- else
- c='.';
- }
- g_outputBuffer += (fmt % (iter->first/1000.0) % string(stars, c)).str();
- }
- });
-
- luaCtx.writeFunction("showTCPStats", [] {
- setLuaNoSideEffect();
- ostringstream ret;
- boost::format fmt("%-12d %-12d %-12d %-12d");
- ret << (fmt % "Workers" % "Max Workers" % "Queued" % "Max Queued") << endl;
- ret << (fmt % g_tcpclientthreads->getThreadsCount() % (g_maxTCPClientThreads ? *g_maxTCPClientThreads : 0) % g_tcpclientthreads->getQueuedCount() % g_maxTCPQueuedConnections) << endl;
- ret << endl;
-
- ret << "Frontends:" << endl;
- fmt = boost::format("%-3d %-20.20s %-20d %-20d %-20d %-25d %-20d %-20d %-20d %-20f %-20f %-20d %-20d %-25d %-25d %-15d %-15d %-15d %-15d %-15d");
- ret << (fmt % "#" % "Address" % "Connections" % "Max concurrent conn" % "Died reading query" % "Died sending response" % "Gave up" % "Client timeouts" % "Downstream timeouts" % "Avg queries/conn" % "Avg duration" % "TLS new sessions" % "TLS Resumptions" % "TLS unknown ticket keys" % "TLS inactive ticket keys" % "TLS 1.0" % "TLS 1.1" % "TLS 1.2" % "TLS 1.3" % "TLS other") << endl;
-
- size_t counter = 0;
- for(const auto& f : g_frontends) {
- ret << (fmt % counter % f->local.toStringWithPort() % f->tcpCurrentConnections % f->tcpMaxConcurrentConnections % f->tcpDiedReadingQuery % f->tcpDiedSendingResponse % f->tcpGaveUp % f->tcpClientTimeouts % f->tcpDownstreamTimeouts % f->tcpAvgQueriesPerConnection % f->tcpAvgConnectionDuration % f->tlsNewSessions % f->tlsResumptions % f->tlsUnknownTicketKey % f->tlsInactiveTicketKey % f->tls10queries % f->tls11queries % f->tls12queries % f->tls13queries % f->tlsUnknownqueries) << endl;
- ++counter;
- }
- ret << endl;
-
- ret << "Backends:" << endl;
- fmt = boost::format("%-3d %-20.20s %-20.20s %-20d %-20d %-25d %-25d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20f %-20f");
- ret << (fmt % "#" % "Name" % "Address" % "Connections" % "Max concurrent conn" % "Died sending query" % "Died reading response" % "Gave up" % "Read timeouts" % "Write timeouts" % "Connect timeouts" % "Too many conn" % "Total connections" % "Reused connections" % "TLS resumptions" % "Avg queries/conn" % "Avg duration") << endl;
-
- auto states = g_dstates.getLocal();
- counter = 0;
- for(const auto& s : *states) {
- ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % s->tcpCurrentConnections % s->tcpMaxConcurrentConnections % s->tcpDiedSendingQuery % s->tcpDiedReadingResponse % s->tcpGaveUp % s->tcpReadTimeouts % s->tcpWriteTimeouts % s->tcpConnectTimeouts % s->tcpTooManyConcurrentConnections % s->tcpNewConnections % s->tcpReusedConnections % s->tlsResumptions % s->tcpAvgQueriesPerConnection % s->tcpAvgConnectionDuration) << endl;
- ++counter;
- }
-
- g_outputBuffer=ret.str();
- });
-
- luaCtx.writeFunction("showTLSErrorCounters", [] {
- setLuaNoSideEffect();
- ostringstream ret;
- boost::format fmt("%-3d %-20.20s %-23d %-23d %-23d %-23d %-23d %-23d %-23d %-23d");
-
- ret << (fmt % "#" % "Address" % "DH key too small" % "Inappropriate fallback" % "No shared cipher" % "Unknown cipher type" % "Unknown exchange type" % "Unknown protocol" % "Unsupported EC" % "Unsupported protocol") << endl;
-
- size_t counter = 0;
- for(const auto& f : g_frontends) {
- if (!f->hasTLS()) {
- continue;
- }
- const TLSErrorCounters* errorCounters = nullptr;
- if (f->tlsFrontend != nullptr) {
- errorCounters = &f->tlsFrontend->d_tlsCounters;
- }
- else if (f->dohFrontend != nullptr) {
- errorCounters = &f->dohFrontend->d_tlsCounters;
- }
- if (errorCounters == nullptr) {
- continue;
- }
-
- ret << (fmt % counter % f->local.toStringWithPort() % errorCounters->d_dhKeyTooSmall % errorCounters->d_inappropriateFallBack % errorCounters->d_noSharedCipher % errorCounters->d_unknownCipherType % errorCounters->d_unknownKeyExchangeType % errorCounters->d_unknownProtocol % errorCounters->d_unsupportedEC % errorCounters->d_unsupportedProtocol) << endl;
- ++counter;
- }
- ret << endl;
-
- g_outputBuffer=ret.str();
- });
-
- luaCtx.writeFunction("requestTCPStatesDump", [] {
- setLuaNoSideEffect();
- extern std::atomic<uint64_t> g_tcpStatesDumpRequested;
- g_tcpStatesDumpRequested += g_tcpclientthreads->getThreadsCount();
- });
-
- luaCtx.writeFunction("requestDoHStatesDump", [] {
- setLuaNoSideEffect();
- g_dohStatesDumpRequested += g_dohClientThreads->getThreadsCount();
- });
-
- luaCtx.writeFunction("dumpStats", [] {
- setLuaNoSideEffect();
- vector<string> leftcolumn, rightcolumn;
-
- boost::format fmt("%-35s\t%+11s");
- g_outputBuffer.clear();
- auto entries = g_stats.entries;
- sort(entries.begin(), entries.end(),
- [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) {
- return a.first < b.first;
- });
- boost::format flt(" %9.1f");
- for (const auto& e : entries) {
- string second;
- if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
- second = std::to_string((*val)->load());
- }
- else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
- second = (flt % (*adval)->load()).str();
- }
- else if (const auto& dval = boost::get<double*>(&e.second)) {
- second = (flt % (**dval)).str();
- }
- else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
- second = std::to_string((*func)(e.first));
- }
-
- if (leftcolumn.size() < g_stats.entries.size()/2) {
- leftcolumn.push_back((fmt % e.first % second).str());
- }
- else {
- rightcolumn.push_back((fmt % e.first % second).str());
- }
- }
-
- auto leftiter=leftcolumn.begin(), rightiter=rightcolumn.begin();
- boost::format clmn("%|0t|%1% %|51t|%2%\n");
-
- for(;leftiter != leftcolumn.end() || rightiter != rightcolumn.end();) {
- string lentry, rentry;
- if(leftiter!= leftcolumn.end()) {
- lentry = *leftiter;
- leftiter++;
- }
- if(rightiter!= rightcolumn.end()) {
- rentry = *rightiter;
- rightiter++;
- }
- g_outputBuffer += (clmn % lentry % rentry).str();
- }
- });
-
-#ifndef DISABLE_DYNBLOCKS
-#ifndef DISABLE_DEPRECATED_DYNBLOCK
- luaCtx.writeFunction("exceedServFails", [](unsigned int rate, int seconds) {
- setLuaNoSideEffect();
- return exceedRCode(rate, seconds, RCode::ServFail);
- });
- luaCtx.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) {
- setLuaNoSideEffect();
- return exceedRCode(rate, seconds, RCode::NXDomain);
- });
-
- luaCtx.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) {
- setLuaNoSideEffect();
- return exceedRespByterate(rate, seconds);
- });
-
- luaCtx.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) {
- setLuaNoSideEffect();
- return exceedQueryGen(rate, seconds, [type](counts_t& counts, const Rings::Query& q) {
- if(q.qtype==type)
- counts[q.requestor]++;
- });
- });
-
- luaCtx.writeFunction("exceedQRate", [](unsigned int rate, int seconds) {
- setLuaNoSideEffect();
- return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& q) {
- counts[q.requestor]++;
- });
- });
-
- luaCtx.writeFunction("getRespRing", getRespRing);
-
- /* StatNode */
- luaCtx.registerFunction<StatNode, unsigned int()>("numChildren",
- [](StatNode& sn) -> unsigned int {
- return sn.children.size();
- } );
- luaCtx.registerMember("fullname", &StatNode::fullname);
- luaCtx.registerMember("labelsCount", &StatNode::labelsCount);
- luaCtx.registerMember("servfails", &StatNode::Stat::servfails);
- luaCtx.registerMember("nxdomains", &StatNode::Stat::nxdomains);
- luaCtx.registerMember("queries", &StatNode::Stat::queries);
- luaCtx.registerMember("noerrors", &StatNode::Stat::noerrors);
- luaCtx.registerMember("drops", &StatNode::Stat::drops);
- luaCtx.registerMember("bytes", &StatNode::Stat::bytes);
- luaCtx.registerMember("hits", &StatNode::Stat::hits);
-
- luaCtx.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional<uint64_t> seconds) {
- statNodeRespRing(visitor, seconds ? *seconds : 0U);
- });
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
-
- /* DynBlockRulesGroup */
- luaCtx.writeFunction("dynBlockRulesGroup", []() { return std::make_shared<DynBlockRulesGroup>(); });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQueryRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
- if (group) {
- group->setQueryRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setResponseByteRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
- if (group) {
- group->setResponseByteRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, DynBlockRulesGroup::smtVisitor_t)>("setSuffixMatchRule", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, DynBlockRulesGroup::smtVisitor_t visitor) {
- if (group) {
- group->setSuffixMatchRule(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, visitor);
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, dnsdist_ffi_stat_node_visitor_t)>("setSuffixMatchRuleFFI", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, dnsdist_ffi_stat_node_visitor_t visitor) {
- if (group) {
- group->setSuffixMatchRuleFFI(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, visitor);
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setRCodeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
- if (group) {
- group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, double, unsigned int, const std::string&, unsigned int, size_t, boost::optional<DNSAction::Action>, boost::optional<double>)>("setRCodeRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio) {
- if (group) {
- group->setRCodeRatio(rcode, ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses);
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQTypeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint16_t qtype, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
- if (group) {
- group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, uint8_t, uint8_t)>("setMasks", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t v4, uint8_t v6, uint8_t port) {
- if (group) {
- if (v4 > 32) {
- throw std::runtime_error("Trying to set an invalid IPv4 mask (" + std::to_string(v4) + ") to a Dynamic Block object");
- }
- if (v6 > 128) {
- throw std::runtime_error("Trying to set an invalid IPv6 mask (" + std::to_string(v6) + ") to a Dynamic Block object");
- }
- if (port > 16) {
- throw std::runtime_error("Trying to set an invalid port mask (" + std::to_string(port) + ") to a Dynamic Block object");
- }
- if (port > 0 && v4 != 32) {
- throw std::runtime_error("Setting a non-zero port mask for Dynamic Blocks while only considering parts of IPv4 addresses does not make sense");
- }
- group->setMasks(v4, v6, port);
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
- if (ranges.type() == typeid(LuaArray<std::string>)) {
- for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
- group->excludeRange(Netmask(range.second));
- }
- }
- else if (ranges.type() == typeid(NetmaskGroup)) {
- group->excludeRange(*boost::get<NetmaskGroup>(&ranges));
- }
- else {
- group->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("includeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
- if (ranges.type() == typeid(LuaArray<std::string>)) {
- for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
- group->includeRange(Netmask(range.second));
- }
- }
- else if (ranges.type() == typeid(NetmaskGroup)) {
- group->includeRange(*boost::get<NetmaskGroup>(&ranges));
- }
- else {
- group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(LuaTypeOrArrayOf<std::string>)>("excludeDomains", [](std::shared_ptr<DynBlockRulesGroup>& group, LuaTypeOrArrayOf<std::string> domains) {
- if (domains.type() == typeid(LuaArray<std::string>)) {
- for (const auto& range : *boost::get<LuaArray<std::string>>(&domains)) {
- group->excludeDomain(DNSName(range.second));
- }
- }
- else {
- group->excludeDomain(DNSName(*boost::get<std::string>(&domains)));
- }
- });
- luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
- group->apply();
- });
- luaCtx.registerFunction("setQuiet", &DynBlockRulesGroup::setQuiet);
- luaCtx.registerFunction("toString", &DynBlockRulesGroup::toString);
-#endif /* DISABLE_DYNBLOCKS */
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "dnsdist.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-rules.hh"
-
-std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var)
-{
- if (var.type() == typeid(std::shared_ptr<DNSRule>))
- return *boost::get<std::shared_ptr<DNSRule>>(&var);
-
- SuffixMatchNode smn;
- NetmaskGroup nmg;
- auto add=[&](string src) {
- try {
- nmg.addMask(src); // need to try mask first, all masks are domain names!
- } catch(...) {
- smn.add(DNSName(src));
- }
- };
-
- if (var.type() == typeid(string))
- add(*boost::get<string>(&var));
-
- else if (var.type() == typeid(LuaArray<std::string>))
- for(const auto& a : *boost::get<LuaArray<std::string>>(&var))
- add(a.second);
-
- else if (var.type() == typeid(DNSName))
- smn.add(*boost::get<DNSName>(&var));
-
- else if (var.type() == typeid(LuaArray<DNSName>))
- for(const auto& a : *boost::get<LuaArray<DNSName>>(&var))
- smn.add(a.second);
-
- if(nmg.empty())
- return std::make_shared<SuffixMatchNodeRule>(smn);
- else
- return std::make_shared<NetmaskGroupRule>(nmg, true);
-}
-
-static boost::uuids::uuid makeRuleID(std::string& id)
-{
- if (id.empty()) {
- return getUniqueID();
- }
-
- return getUniqueID(id);
-}
-
-void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder)
-{
- static uint64_t s_creationOrder = 0;
-
- string uuidStr;
-
- getOptionalValue<std::string>(params, "uuid", uuidStr);
- getOptionalValue<std::string>(params, "name", name);
-
- uuid = makeRuleID(uuidStr);
- creationOrder = s_creationOrder++;
-}
-
-typedef LuaAssociativeTable<boost::variant<bool, int, std::string, LuaArray<int> > > ruleparams_t;
-
-template<typename T>
-static std::string rulesToString(const std::vector<T>& rules, boost::optional<ruleparams_t> vars)
-{
- int num = 0;
- bool showUUIDs = false;
- size_t truncateRuleWidth = string::npos;
- std::string result;
-
- getOptionalValue<bool>(vars, "showUUIDs", showUUIDs);
- getOptionalValue<int>(vars, "truncateRuleWidth", truncateRuleWidth);
- checkAllParametersConsumed("rulesToString", vars);
-
- if (showUUIDs) {
- boost::format fmt("%-3d %-30s %-38s %9d %9d %-56s %s\n");
- result += (fmt % "#" % "Name" % "UUID" % "Cr. Order" % "Matches" % "Rule" % "Action").str();
- for(const auto& lim : rules) {
- string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
- result += (fmt % num % lim.d_name % boost::uuids::to_string(lim.d_id) % lim.d_creationOrder % lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
- ++num;
- }
- }
- else {
- boost::format fmt("%-3d %-30s %9d %-56s %s\n");
- result += (fmt % "#" % "Name" % "Matches" % "Rule" % "Action").str();
- for(const auto& lim : rules) {
- string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
- result += (fmt % num % lim.d_name % lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
- ++num;
- }
- }
- return result;
-}
-
-template<typename T>
-static void showRules(GlobalStateHolder<vector<T> > *someRuleActions, boost::optional<ruleparams_t> vars) {
- setLuaNoSideEffect();
-
- auto rules = someRuleActions->getLocal();
- g_outputBuffer += rulesToString(*rules, vars);
-}
-
-template<typename T>
-static void rmRule(GlobalStateHolder<vector<T> > *someRuleActions, boost::variant<unsigned int, std::string> id) {
- setLuaSideEffect();
- auto rules = someRuleActions->getCopy();
- if (auto str = boost::get<std::string>(&id)) {
- try {
- const auto uuid = getUniqueID(*str);
- if (rules.erase(std::remove_if(rules.begin(),
- rules.end(),
- [uuid](const T& a) { return a.d_id == uuid; }),
- rules.end()) == rules.end()) {
- g_outputBuffer = "Error: no rule matched\n";
- return;
- }
- }
- catch (const std::runtime_error& e) {
- /* it was not an UUID, let's see if it was a name instead */
- if (rules.erase(std::remove_if(rules.begin(),
- rules.end(),
- [&str](const T& a) { return a.d_name == *str; }),
- rules.end()) == rules.end()) {
- g_outputBuffer = "Error: no rule matched\n";
- return;
- }
- }
- }
- else if (auto pos = boost::get<unsigned int>(&id)) {
- if (*pos >= rules.size()) {
- g_outputBuffer = "Error: attempt to delete non-existing rule\n";
- return;
- }
- rules.erase(rules.begin()+*pos);
- }
- someRuleActions->setState(std::move(rules));
-}
-
-template<typename T>
-static void moveRuleToTop(GlobalStateHolder<vector<T> > *someRuleActions) {
- setLuaSideEffect();
- auto rules = someRuleActions->getCopy();
- if(rules.empty())
- return;
- auto subject = *rules.rbegin();
- rules.erase(std::prev(rules.end()));
- rules.insert(rules.begin(), subject);
- someRuleActions->setState(std::move(rules));
-}
-
-template<typename T>
-static void mvRule(GlobalStateHolder<vector<T> > *someRespRuleActions, unsigned int from, unsigned int to) {
- setLuaSideEffect();
- auto rules = someRespRuleActions->getCopy();
- if(from >= rules.size() || to > rules.size()) {
- g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
- return;
- }
- auto subject = rules[from];
- rules.erase(rules.begin()+from);
- if(to > rules.size())
- rules.push_back(subject);
- else {
- if(from < to)
- --to;
- rules.insert(rules.begin()+to, subject);
- }
- someRespRuleActions->setState(std::move(rules));
-}
-
-template<typename T>
-static std::vector<T> getTopRules(const std::vector<T>& rules, unsigned int top)
-{
- std::vector<std::pair<size_t, size_t>> counts;
- counts.reserve(rules.size());
-
- size_t pos = 0;
- for (const auto& rule : rules) {
- counts.push_back({rule.d_rule->d_matches.load(), pos});
- pos++;
- }
-
- sort(counts.begin(), counts.end(), [](const decltype(counts)::value_type& a,
- const decltype(counts)::value_type& b) {
- return b.first < a.first;
- });
-
- std::vector<T> results;
- results.reserve(top);
-
- size_t count = 0;
- for (const auto& entry : counts) {
- results.emplace_back(rules.at(entry.second));
- ++count;
- if (count == top) {
- break;
- }
- }
-
- return results;
-}
-
-void setupLuaRules(LuaContext& luaCtx)
-{
- luaCtx.writeFunction("makeRule", makeRule);
-
- luaCtx.registerFunction<string(std::shared_ptr<DNSRule>::*)()const>("toString", [](const std::shared_ptr<DNSRule>& rule) { return rule->toString(); });
-
- luaCtx.writeFunction("showResponseRules", [](boost::optional<ruleparams_t> vars) {
- showRules(&g_respruleactions, vars);
- });
-
- luaCtx.writeFunction("rmResponseRule", [](boost::variant<unsigned int, std::string> id) {
- rmRule(&g_respruleactions, id);
- });
-
- luaCtx.writeFunction("mvResponseRuleToTop", []() {
- moveRuleToTop(&g_respruleactions);
- });
-
- luaCtx.writeFunction("mvResponseRule", [](unsigned int from, unsigned int to) {
- mvRule(&g_respruleactions, from, to);
- });
-
- luaCtx.writeFunction("showCacheHitResponseRules", [](boost::optional<ruleparams_t> vars) {
- showRules(&g_cachehitrespruleactions, vars);
- });
-
- luaCtx.writeFunction("rmCacheHitResponseRule", [](boost::variant<unsigned int, std::string> id) {
- rmRule(&g_cachehitrespruleactions, id);
- });
-
- luaCtx.writeFunction("mvCacheHitResponseRuleToTop", []() {
- moveRuleToTop(&g_cachehitrespruleactions);
- });
-
- luaCtx.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) {
- mvRule(&g_cachehitrespruleactions, from, to);
- });
-
- luaCtx.writeFunction("showCacheInsertedResponseRules", [](boost::optional<ruleparams_t> vars) {
- showRules(&g_cacheInsertedRespRuleActions, vars);
- });
-
- luaCtx.writeFunction("rmCacheInsertedResponseRule", [](boost::variant<unsigned int, std::string> id) {
- rmRule(&g_cacheInsertedRespRuleActions, id);
- });
-
- luaCtx.writeFunction("mvCacheInsertedResponseRuleToTop", []() {
- moveRuleToTop(&g_cacheInsertedRespRuleActions);
- });
-
- luaCtx.writeFunction("mvCacheInsertedResponseRule", [](unsigned int from, unsigned int to) {
- mvRule(&g_cacheInsertedRespRuleActions, from, to);
- });
-
- luaCtx.writeFunction("showSelfAnsweredResponseRules", [](boost::optional<ruleparams_t> vars) {
- showRules(&g_selfansweredrespruleactions, vars);
- });
-
- luaCtx.writeFunction("rmSelfAnsweredResponseRule", [](boost::variant<unsigned int, std::string> id) {
- rmRule(&g_selfansweredrespruleactions, id);
- });
-
- luaCtx.writeFunction("mvSelfAnsweredResponseRuleToTop", []() {
- moveRuleToTop(&g_selfansweredrespruleactions);
- });
-
- luaCtx.writeFunction("mvSelfAnsweredResponseRule", [](unsigned int from, unsigned int to) {
- mvRule(&g_selfansweredrespruleactions, from, to);
- });
-
- luaCtx.writeFunction("rmRule", [](boost::variant<unsigned int, std::string> id) {
- rmRule(&g_ruleactions, id);
- });
-
- luaCtx.writeFunction("mvRuleToTop", []() {
- moveRuleToTop(&g_ruleactions);
- });
-
- luaCtx.writeFunction("mvRule", [](unsigned int from, unsigned int to) {
- mvRule(&g_ruleactions, from, to);
- });
-
- luaCtx.writeFunction("clearRules", []() {
- setLuaSideEffect();
- g_ruleactions.modify([](decltype(g_ruleactions)::value_type& ruleactions) {
- ruleactions.clear();
- });
- });
-
- luaCtx.writeFunction("setRules", [](const LuaArray<std::shared_ptr<DNSDistRuleAction>>& newruleactions) {
- setLuaSideEffect();
- g_ruleactions.modify([newruleactions](decltype(g_ruleactions)::value_type& gruleactions) {
- gruleactions.clear();
- for (const auto& pair : newruleactions) {
- const auto& newruleaction = pair.second;
- if (newruleaction->d_action) {
- auto rule = makeRule(newruleaction->d_rule);
- gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_name, newruleaction->d_id, newruleaction->d_creationOrder});
- }
- }
- });
- });
-
- luaCtx.writeFunction("getTopRules", [](boost::optional<unsigned int> top) {
- setLuaNoSideEffect();
- auto rules = g_ruleactions.getLocal();
- return getTopRules(*rules, (top ? *top : 10));
- });
-
- luaCtx.writeFunction("topRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
- setLuaNoSideEffect();
- auto rules = g_ruleactions.getLocal();
- return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
- });
-
- luaCtx.writeFunction("getTopCacheHitResponseRules", [](boost::optional<unsigned int> top) {
- setLuaNoSideEffect();
- auto rules = g_cachehitrespruleactions.getLocal();
- return getTopRules(*rules, (top ? *top : 10));
- });
-
- luaCtx.writeFunction("topCacheHitResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
- setLuaNoSideEffect();
- auto rules = g_cachehitrespruleactions.getLocal();
- return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
- });
-
- luaCtx.writeFunction("getTopCacheInsertedResponseRules", [](boost::optional<unsigned int> top) {
- setLuaNoSideEffect();
- auto rules = g_cacheInsertedRespRuleActions.getLocal();
- return getTopRules(*rules, (top ? *top : 10));
- });
-
- luaCtx.writeFunction("topCacheInsertedResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
- setLuaNoSideEffect();
- auto rules = g_cacheInsertedRespRuleActions.getLocal();
- return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
- });
-
- luaCtx.writeFunction("getTopResponseRules", [](boost::optional<unsigned int> top) {
- setLuaNoSideEffect();
- auto rules = g_respruleactions.getLocal();
- return getTopRules(*rules, (top ? *top : 10));
- });
-
- luaCtx.writeFunction("topResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
- setLuaNoSideEffect();
- auto rules = g_respruleactions.getLocal();
- return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
- });
-
- luaCtx.writeFunction("getTopSelfAnsweredResponseRules", [](boost::optional<unsigned int> top) {
- setLuaNoSideEffect();
- auto rules = g_selfansweredrespruleactions.getLocal();
- return getTopRules(*rules, (top ? *top : 10));
- });
-
- luaCtx.writeFunction("topSelfAnsweredResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
- setLuaNoSideEffect();
- auto rules = g_selfansweredrespruleactions.getLocal();
- return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
- });
-
- luaCtx.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<unsigned int> ipv4trunc, boost::optional<unsigned int> ipv6trunc, boost::optional<unsigned int> burst, boost::optional<unsigned int> expiration, boost::optional<unsigned int> cleanupDelay, boost::optional<unsigned int> scanFraction, boost::optional<unsigned int> shards) {
- return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, (burst ? *burst : qps), (ipv4trunc ? *ipv4trunc : 32), (ipv6trunc ? *ipv6trunc : 64), (expiration ? *expiration : 300), (cleanupDelay ? *cleanupDelay : 60), (scanFraction ? *scanFraction : 10), (shards ? *shards : 10)));
- });
-
- luaCtx.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional<unsigned int> burst) {
- if(!burst)
- return std::shared_ptr<DNSRule>(new MaxQPSRule(qps));
- else
- return std::shared_ptr<DNSRule>(new MaxQPSRule(qps, *burst));
- });
-
- luaCtx.writeFunction("RegexRule", [](const std::string& str) {
- return std::shared_ptr<DNSRule>(new RegexRule(str));
- });
-
-#ifdef HAVE_DNS_OVER_HTTPS
- luaCtx.writeFunction("HTTPHeaderRule", [](const std::string& header, const std::string& regex) {
- return std::shared_ptr<DNSRule>(new HTTPHeaderRule(header, regex));
- });
- luaCtx.writeFunction("HTTPPathRule", [](const std::string& path) {
- return std::shared_ptr<DNSRule>(new HTTPPathRule(path));
- });
- luaCtx.writeFunction("HTTPPathRegexRule", [](const std::string& regex) {
- return std::shared_ptr<DNSRule>(new HTTPPathRegexRule(regex));
- });
-#endif
-
-#ifdef HAVE_RE2
- luaCtx.writeFunction("RE2Rule", [](const std::string& str) {
- return std::shared_ptr<DNSRule>(new RE2Rule(str));
- });
-#endif
-
- luaCtx.writeFunction("SNIRule", [](const std::string& name) {
- return std::shared_ptr<DNSRule>(new SNIRule(name));
- });
-
- luaCtx.writeFunction("SuffixMatchNodeRule", [](const SuffixMatchNode& smn, boost::optional<bool> quiet) {
- return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
- });
-
- luaCtx.writeFunction("NetmaskGroupRule", [](const NetmaskGroup& nmg, boost::optional<bool> src, boost::optional<bool> quiet) {
- return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false));
- });
-
- luaCtx.writeFunction("benchRule", [](std::shared_ptr<DNSRule> rule, boost::optional<unsigned int> times_, boost::optional<string> suffix_) {
- setLuaNoSideEffect();
- unsigned int times = times_ ? *times_ : 100000;
- DNSName suffix(suffix_ ? *suffix_ : "powerdns.com");
- struct item {
- PacketBuffer packet;
- InternalQueryState ids;
- };
- vector<item> items;
- items.reserve(1000);
- for (int n = 0; n < 1000; ++n) {
- struct item i;
- i.ids.qname = DNSName(std::to_string(random()));
- i.ids.qname += suffix;
- i.ids.qtype = random() % 0xff;
- i.ids.qclass = QClass::IN;
- i.ids.protocol = dnsdist::Protocol::DoUDP;
- i.ids.origRemote = ComboAddress("127.0.0.1");
- i.ids.origRemote.sin4.sin_addr.s_addr = random();
- i.ids.queryRealTime.start();
- GenericDNSPacketWriter<PacketBuffer> pw(i.packet, i.ids.qname, i.ids.qtype);
- items.push_back(std::move(i));
- }
-
- int matches = 0;
- ComboAddress dummy("127.0.0.1");
- StopWatch sw;
- sw.start();
- for (unsigned int n = 0; n < times; ++n) {
- item& i = items[n % items.size()];
- DNSQuestion dq(i.ids, i.packet);
-
- if (rule->matches(&dq)) {
- matches++;
- }
- }
- double udiff = sw.udiff();
- g_outputBuffer=(boost::format("Had %d matches out of %d, %.1f qps, in %.1f us\n") % matches % times % (1000000*(1.0*times/udiff)) % udiff).str();
-
- });
-
- luaCtx.writeFunction("AllRule", []() {
- return std::shared_ptr<DNSRule>(new AllRule());
- });
-
- luaCtx.writeFunction("ProbaRule", [](double proba) {
- return std::shared_ptr<DNSRule>(new ProbaRule(proba));
- });
-
- luaCtx.writeFunction("QNameRule", [](const std::string& qname) {
- return std::shared_ptr<DNSRule>(new QNameRule(DNSName(qname)));
- });
-
- luaCtx.writeFunction("QTypeRule", [](boost::variant<unsigned int, std::string> str) {
- uint16_t qtype;
- if (auto dir = boost::get<unsigned int>(&str)) {
- qtype = *dir;
- }
- else {
- string val = boost::get<string>(str);
- qtype = QType::chartocode(val.c_str());
- if (!qtype) {
- throw std::runtime_error("Unable to convert '"+val+"' to a DNS type");
- }
- }
- return std::shared_ptr<DNSRule>(new QTypeRule(qtype));
- });
-
- luaCtx.writeFunction("QClassRule", [](uint64_t c) {
- checkParameterBound("QClassRule", c, std::numeric_limits<uint16_t>::max());
- return std::shared_ptr<DNSRule>(new QClassRule(c));
- });
-
- luaCtx.writeFunction("OpcodeRule", [](uint64_t code) {
- checkParameterBound("OpcodeRule", code, std::numeric_limits<uint8_t>::max());
- return std::shared_ptr<DNSRule>(new OpcodeRule(code));
- });
-
- luaCtx.writeFunction("AndRule", [](const LuaArray<std::shared_ptr<DNSRule>>& a) {
- return std::shared_ptr<DNSRule>(new AndRule(a));
- });
-
- luaCtx.writeFunction("OrRule", [](const LuaArray<std::shared_ptr<DNSRule>>& a) {
- return std::shared_ptr<DNSRule>(new OrRule(a));
- });
-
- luaCtx.writeFunction("DSTPortRule", [](uint64_t port) {
- checkParameterBound("DSTPortRule", port, std::numeric_limits<uint16_t>::max());
- return std::shared_ptr<DNSRule>(new DSTPortRule(port));
- });
-
- luaCtx.writeFunction("TCPRule", [](bool tcp) {
- return std::shared_ptr<DNSRule>(new TCPRule(tcp));
- });
-
- luaCtx.writeFunction("DNSSECRule", []() {
- return std::shared_ptr<DNSRule>(new DNSSECRule());
- });
-
- luaCtx.writeFunction("NotRule", [](const std::shared_ptr<DNSRule>& rule) {
- return std::shared_ptr<DNSRule>(new NotRule(rule));
- });
-
- luaCtx.writeFunction("RecordsCountRule", [](uint64_t section, uint64_t minCount, uint64_t maxCount) {
- checkParameterBound("RecordsCountRule", section, std::numeric_limits<uint8_t>::max());
- checkParameterBound("RecordsCountRule", minCount, std::numeric_limits<uint16_t>::max());
- checkParameterBound("RecordsCountRule", maxCount, std::numeric_limits<uint16_t>::max());
- return std::shared_ptr<DNSRule>(new RecordsCountRule(section, minCount, maxCount));
- });
-
- luaCtx.writeFunction("RecordsTypeCountRule", [](uint64_t section, uint64_t type, uint64_t minCount, uint64_t maxCount) {
- checkParameterBound("RecordsTypeCountRule", section, std::numeric_limits<uint8_t>::max());
- checkParameterBound("RecordsTypeCountRule", type, std::numeric_limits<uint16_t>::max());
- checkParameterBound("RecordsTypeCountRule", minCount, std::numeric_limits<uint16_t>::max());
- checkParameterBound("RecordsTypeCountRule", maxCount, std::numeric_limits<uint16_t>::max());
- return std::shared_ptr<DNSRule>(new RecordsTypeCountRule(section, type, minCount, maxCount));
- });
-
- luaCtx.writeFunction("TrailingDataRule", []() {
- return std::shared_ptr<DNSRule>(new TrailingDataRule());
- });
-
- luaCtx.writeFunction("QNameLabelsCountRule", [](uint64_t minLabelsCount, uint64_t maxLabelsCount) {
- checkParameterBound("QNameLabelsCountRule", minLabelsCount, std::numeric_limits<unsigned int>::max());
- checkParameterBound("QNameLabelsCountRule", maxLabelsCount, std::numeric_limits<unsigned int>::max());
- return std::shared_ptr<DNSRule>(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount));
- });
-
- luaCtx.writeFunction("QNameWireLengthRule", [](uint64_t min, uint64_t max) {
- return std::shared_ptr<DNSRule>(new QNameWireLengthRule(min, max));
- });
-
- luaCtx.writeFunction("RCodeRule", [](uint64_t rcode) {
- checkParameterBound("RCodeRule", rcode, std::numeric_limits<uint8_t>::max());
- return std::shared_ptr<DNSRule>(new RCodeRule(rcode));
- });
-
- luaCtx.writeFunction("ERCodeRule", [](uint64_t rcode) {
- checkParameterBound("ERCodeRule", rcode, std::numeric_limits<uint8_t>::max());
- return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
- });
-
- luaCtx.writeFunction("EDNSVersionRule", [](uint64_t version) {
- checkParameterBound("EDNSVersionRule", version, std::numeric_limits<uint8_t>::max());
- return std::shared_ptr<DNSRule>(new EDNSVersionRule(version));
- });
-
- luaCtx.writeFunction("EDNSOptionRule", [](uint64_t optcode) {
- checkParameterBound("EDNSOptionRule", optcode, std::numeric_limits<uint16_t>::max());
- return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
- });
-
- luaCtx.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
- showRules(&g_ruleactions, vars);
- });
-
- luaCtx.writeFunction("RDRule", []() {
- return std::shared_ptr<DNSRule>(new RDRule());
- });
-
- luaCtx.writeFunction("TagRule", [](const std::string& tag, boost::optional<std::string> value) {
- return std::shared_ptr<DNSRule>(new TagRule(tag, value));
- });
-
- luaCtx.writeFunction("TimedIPSetRule", []() {
- return std::shared_ptr<TimedIPSetRule>(new TimedIPSetRule());
- });
-
- luaCtx.writeFunction("PoolAvailableRule", [](const std::string& poolname) {
- return std::shared_ptr<DNSRule>(new PoolAvailableRule(poolname));
- });
-
- luaCtx.writeFunction("PoolOutstandingRule", [](const std::string& poolname, uint64_t limit) {
- return std::shared_ptr<DNSRule>(new PoolOutstandingRule(poolname, limit));
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("clear", [](std::shared_ptr<TimedIPSetRule> tisr) {
- tisr->clear();
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("cleanup", [](std::shared_ptr<TimedIPSetRule> tisr) {
- tisr->cleanup();
- });
-
- luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)(const ComboAddress& ca, int t)>("add", [](std::shared_ptr<TimedIPSetRule> tisr, const ComboAddress& ca, int t) {
- tisr->add(ca, time(0)+t);
- });
-
- luaCtx.registerFunction<std::shared_ptr<DNSRule>(std::shared_ptr<TimedIPSetRule>::*)()>("slice", [](std::shared_ptr<TimedIPSetRule> tisr) {
- return std::dynamic_pointer_cast<DNSRule>(tisr);
- });
- luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("__tostring", [](std::shared_ptr<TimedIPSetRule> tisr) {
- tisr->toString();
- });
-
- luaCtx.writeFunction("QNameSetRule", [](const DNSNameSet& names) {
- return std::shared_ptr<DNSRule>(new QNameSetRule(names));
- });
-
-#if defined(HAVE_LMDB) || defined(HAVE_CDB)
- luaCtx.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
- return std::shared_ptr<DNSRule>(new KeyValueStoreLookupRule(kvs, lookupKey));
- });
-
- luaCtx.writeFunction("KeyValueStoreRangeLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
- return std::shared_ptr<DNSRule>(new KeyValueStoreRangeLookupRule(kvs, lookupKey));
- });
-#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
-
- luaCtx.writeFunction("LuaRule", [](LuaRule::func_t func) {
- return std::shared_ptr<DNSRule>(new LuaRule(func));
- });
-
- luaCtx.writeFunction("LuaFFIRule", [](LuaFFIRule::func_t func) {
- return std::shared_ptr<DNSRule>(new LuaFFIRule(func));
- });
-
- luaCtx.writeFunction("LuaFFIPerThreadRule", [](const std::string& code) {
- return std::shared_ptr<DNSRule>(new LuaFFIPerThreadRule(code));
- });
-
- luaCtx.writeFunction("ProxyProtocolValueRule", [](uint8_t type, boost::optional<std::string> value) {
- return std::shared_ptr<DNSRule>(new ProxyProtocolValueRule(type, value));
- });
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "dnsdist.hh"
-#include "dnsdist-lua.hh"
-#include "ednsoptions.hh"
-
-#undef BADSIG // signal.h SIG_ERR
-
-void setupLuaVars(LuaContext& luaCtx)
-{
- luaCtx.writeVariable("DNSAction", LuaAssociativeTable<int>{
- {"Drop", (int)DNSAction::Action::Drop},
- {"Nxdomain", (int)DNSAction::Action::Nxdomain},
- {"Refused", (int)DNSAction::Action::Refused},
- {"Spoof", (int)DNSAction::Action::Spoof},
- {"SpoofPacket", (int)DNSAction::Action::SpoofPacket},
- {"SpoofRaw", (int)DNSAction::Action::SpoofRaw},
- {"Allow", (int)DNSAction::Action::Allow},
- {"HeaderModify", (int)DNSAction::Action::HeaderModify},
- {"Pool", (int)DNSAction::Action::Pool},
- {"None",(int)DNSAction::Action::None},
- {"NoOp",(int)DNSAction::Action::NoOp},
- {"Delay", (int)DNSAction::Action::Delay},
- {"Truncate", (int)DNSAction::Action::Truncate},
- {"ServFail", (int)DNSAction::Action::ServFail},
- {"NoRecurse", (int)DNSAction::Action::NoRecurse}
- });
-
- luaCtx.writeVariable("DNSResponseAction", LuaAssociativeTable<int>{
- {"Allow", (int)DNSResponseAction::Action::Allow },
- {"Delay", (int)DNSResponseAction::Action::Delay },
- {"Drop", (int)DNSResponseAction::Action::Drop },
- {"HeaderModify", (int)DNSResponseAction::Action::HeaderModify },
- {"ServFail", (int)DNSResponseAction::Action::ServFail },
- {"None", (int)DNSResponseAction::Action::None }
- });
-
- luaCtx.writeVariable("DNSClass", LuaAssociativeTable<int>{
- {"IN", QClass::IN },
- {"CHAOS", QClass::CHAOS },
- {"NONE", QClass::NONE },
- {"ANY", QClass::ANY }
- });
-
- luaCtx.writeVariable("DNSOpcode", LuaAssociativeTable<int>{
- {"Query", Opcode::Query },
- {"IQuery", Opcode::IQuery },
- {"Status", Opcode::Status },
- {"Notify", Opcode::Notify },
- {"Update", Opcode::Update }
- });
-
- luaCtx.writeVariable("DNSSection", LuaAssociativeTable<int>{
- {"Question", 0 },
- {"Answer", 1 },
- {"Authority", 2 },
- {"Additional",3 }
- });
-
- luaCtx.writeVariable("EDNSOptionCode", LuaAssociativeTable<int>{
- {"NSID", EDNSOptionCode::NSID },
- {"DAU", EDNSOptionCode::DAU },
- {"DHU", EDNSOptionCode::DHU },
- {"N3U", EDNSOptionCode::N3U },
- {"ECS", EDNSOptionCode::ECS },
- {"EXPIRE", EDNSOptionCode::EXPIRE },
- {"COOKIE", EDNSOptionCode::COOKIE },
- {"TCPKEEPALIVE", EDNSOptionCode::TCPKEEPALIVE },
- {"PADDING", EDNSOptionCode::PADDING },
- {"CHAIN", EDNSOptionCode::CHAIN },
- {"KEYTAG", EDNSOptionCode::KEYTAG }
- });
-
- luaCtx.writeVariable("DNSRCode", LuaAssociativeTable<int>{
- {"NOERROR", RCode::NoError },
- {"FORMERR", RCode::FormErr },
- {"SERVFAIL", RCode::ServFail },
- {"NXDOMAIN", RCode::NXDomain },
- {"NOTIMP", RCode::NotImp },
- {"REFUSED", RCode::Refused },
- {"YXDOMAIN", RCode::YXDomain },
- {"YXRRSET", RCode::YXRRSet },
- {"NXRRSET", RCode::NXRRSet },
- {"NOTAUTH", RCode::NotAuth },
- {"NOTZONE", RCode::NotZone },
- {"BADVERS", ERCode::BADVERS },
- {"BADSIG", ERCode::BADSIG },
- {"BADKEY", ERCode::BADKEY },
- {"BADTIME", ERCode::BADTIME },
- {"BADMODE", ERCode::BADMODE },
- {"BADNAME", ERCode::BADNAME },
- {"BADALG", ERCode::BADALG },
- {"BADTRUNC", ERCode::BADTRUNC },
- {"BADCOOKIE",ERCode::BADCOOKIE }
- });
-
- LuaAssociativeTable<int> dd;
- for (const auto& n : QType::names) {
- dd[n.first] = n.second;
- }
- luaCtx.writeVariable("DNSQType", dd);
-
-#ifdef HAVE_DNSCRYPT
- luaCtx.writeVariable("DNSCryptExchangeVersion", LuaAssociativeTable<int>{
- { "VERSION1", DNSCryptExchangeVersion::VERSION1 },
- { "VERSION2", DNSCryptExchangeVersion::VERSION2 },
- });
-#endif
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <cstdint>
-#include <dirent.h>
-#include <fstream>
-#include <cinttypes>
-
-// for OpenBSD, sys/socket.h needs to come before net/if.h
-#include <sys/socket.h>
-#include <net/if.h>
-
-#include <regex>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <thread>
-#include <vector>
-
-#include "dnsdist.hh"
-#include "dnsdist-carbon.hh"
-#include "dnsdist-concurrent-connections.hh"
-#include "dnsdist-console.hh"
-#include "dnsdist-dynblocks.hh"
-#include "dnsdist-discovery.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-healthchecks.hh"
-#include "dnsdist-lua.hh"
-#ifdef LUAJIT_VERSION
-#include "dnsdist-lua-ffi.hh"
-#endif /* LUAJIT_VERSION */
-#include "dnsdist-nghttp2.hh"
-#include "dnsdist-proxy-protocol.hh"
-#include "dnsdist-rings.hh"
-#include "dnsdist-secpoll.hh"
-#include "dnsdist-session-cache.hh"
-#include "dnsdist-tcp-downstream.hh"
-#include "dnsdist-web.hh"
-
-#include "base64.hh"
-#include "dolog.hh"
-#include "sodcrypto.hh"
-#include "threadname.hh"
-
-#ifdef HAVE_LIBSSL
-#include "libssl.hh"
-#endif
-
-#include <boost/logic/tribool.hpp>
-#include <boost/uuid/string_generator.hpp>
-
-#ifdef HAVE_SYSTEMD
-#include <systemd/sd-daemon.h>
-#endif
-
-using std::thread;
-
-static boost::optional<std::vector<std::function<void(void)>>> g_launchWork = boost::none;
-
-boost::tribool g_noLuaSideEffect;
-static bool g_included{false};
-
-/* this is a best effort way to prevent logging calls with no side-effects in the output of delta()
- Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing
- has done so before on this invocation, this call won't be part of delta() output */
-void setLuaNoSideEffect()
-{
- if (g_noLuaSideEffect == false) // there has been a side effect already
- return;
- g_noLuaSideEffect = true;
-}
-
-void setLuaSideEffect()
-{
- g_noLuaSideEffect = false;
-}
-
-bool getLuaNoSideEffect()
-{
- if (g_noLuaSideEffect) {
- return true;
- }
- return false;
-}
-
-void resetLuaSideEffect()
-{
- g_noLuaSideEffect = boost::logic::indeterminate;
-}
-
-using localbind_t = LuaAssociativeTable<boost::variant<bool, int, std::string, LuaArray<int>, LuaArray<std::string>, LuaAssociativeTable<std::string>>>;
-
-static void parseLocalBindVars(boost::optional<localbind_t>& vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus, int& tcpListenQueueSize, uint64_t& maxInFlightQueriesPerConnection, uint64_t& tcpMaxConcurrentConnections)
-{
- if (vars) {
- LuaArray<int> setCpus;
-
- getOptionalValue<bool>(vars, "reusePort", reusePort);
- getOptionalValue<int>(vars, "tcpFastOpenQueueSize", tcpFastOpenQueueSize);
- getOptionalValue<int>(vars, "tcpListenQueueSize", tcpListenQueueSize);
- getOptionalValue<int>(vars, "maxConcurrentTCPConnections", tcpMaxConcurrentConnections);
- getOptionalValue<int>(vars, "maxInFlight", maxInFlightQueriesPerConnection);
- getOptionalValue<std::string>(vars, "interface", interface);
- if (getOptionalValue<decltype(setCpus)>(vars, "cpus", setCpus) > 0) {
- for (const auto& cpu : setCpus) {
- cpus.insert(cpu.second);
- }
- }
- }
-}
-
-#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
-static bool loadTLSCertificateAndKeys(const std::string& context, std::vector<TLSCertKeyPair>& pairs, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, LuaTypeOrArrayOf<std::string> keyFiles)
-{
- if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
- auto certFile = boost::get<std::string>(certFiles);
- auto keyFile = boost::get<std::string>(keyFiles);
- pairs.clear();
- pairs.emplace_back(certFile, keyFile);
- }
- else if (certFiles.type() == typeid(std::shared_ptr<TLSCertKeyPair>)) {
- auto cert = boost::get<std::shared_ptr<TLSCertKeyPair>>(certFiles);
- pairs.clear();
- pairs.emplace_back(*cert);
- }
- else if (certFiles.type() == typeid(LuaArray<std::shared_ptr<TLSCertKeyPair>>)) {
- auto certs = boost::get<LuaArray<std::shared_ptr<TLSCertKeyPair>>>(certFiles);
- pairs.clear();
- for (const auto& cert : certs) {
- pairs.emplace_back(*(cert.second));
- }
- }
- else if (certFiles.type() == typeid(LuaArray<std::string>) && keyFiles.type() == typeid(LuaArray<std::string>)) {
- auto certFilesVect = boost::get<LuaArray<std::string>>(certFiles);
- auto keyFilesVect = boost::get<LuaArray<std::string>>(keyFiles);
- if (certFilesVect.size() == keyFilesVect.size()) {
- pairs.clear();
- for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
- pairs.emplace_back(certFilesVect.at(idx).second, keyFilesVect.at(idx).second);
- }
- }
- else {
- errlog("Error, mismatching number of certificates and keys in call to %s()!", context);
- g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!";
- return false;
- }
- }
- else {
- errlog("Error, mismatching number of certificates and keys in call to %s()!", context);
- g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!";
- return false;
- }
-
- return true;
-}
-
-static void parseTLSConfig(TLSConfig& config, const std::string& context, boost::optional<localbind_t>& vars)
-{
- getOptionalValue<std::string>(vars, "ciphers", config.d_ciphers);
- getOptionalValue<std::string>(vars, "ciphersTLS13", config.d_ciphers13);
-
-#ifdef HAVE_LIBSSL
- std::string minVersion;
- if (getOptionalValue<std::string>(vars, "minTLSVersion", minVersion) > 0) {
- config.d_minTLSVersion = libssl_tls_version_from_string(minVersion);
- }
-#else /* HAVE_LIBSSL */
- if (vars->erase("minTLSVersion") > 0)
- warnlog("minTLSVersion has no effect with chosen TLS library");
-#endif /* HAVE_LIBSSL */
-
- getOptionalValue<std::string>(vars, "ticketKeyFile", config.d_ticketKeyFile);
- getOptionalValue<int>(vars, "ticketsKeysRotationDelay", config.d_ticketsKeyRotationDelay);
- getOptionalValue<int>(vars, "numberOfTicketsKeys", config.d_numberOfTicketsKeys);
- getOptionalValue<bool>(vars, "preferServerCiphers", config.d_preferServerCiphers);
- getOptionalValue<int>(vars, "sessionTimeout", config.d_sessionTimeout);
- getOptionalValue<bool>(vars, "sessionTickets", config.d_enableTickets);
- int numberOfStoredSessions{0};
- if (getOptionalValue<int>(vars, "numberOfStoredSessions", numberOfStoredSessions) > 0) {
- if (numberOfStoredSessions < 0) {
- errlog("Invalid value '%d' for %s() parameter 'numberOfStoredSessions', should be >= 0, dismissing", numberOfStoredSessions, context);
- g_outputBuffer = "Invalid value '" + std::to_string(numberOfStoredSessions) + "' for " + context + "() parameter 'numberOfStoredSessions', should be >= 0, dimissing";
- }
- else {
- config.d_maxStoredSessions = numberOfStoredSessions;
- }
- }
-
- LuaArray<std::string> files;
- if (getOptionalValue<decltype(files)>(vars, "ocspResponses", files) > 0) {
- for (const auto& file : files) {
- config.d_ocspFiles.push_back(file.second);
- }
- }
-
- if (vars->count("keyLogFile") > 0) {
-#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
- getOptionalValue<std::string>(vars, "keyLogFile", config.d_keyLogFile);
-#else
- errlog("TLS Key logging has been enabled using the 'keyLogFile' parameter to %s(), but this version of OpenSSL does not support it", context);
- g_outputBuffer = "TLS Key logging has been enabled using the 'keyLogFile' parameter to " + context + "(), but this version of OpenSSL does not support it";
-#endif
- }
-
- getOptionalValue<bool>(vars, "releaseBuffers", config.d_releaseBuffers);
- getOptionalValue<bool>(vars, "enableRenegotiation", config.d_enableRenegotiation);
- getOptionalValue<bool>(vars, "tlsAsyncMode", config.d_asyncMode);
- getOptionalValue<bool>(vars, "ktls", config.d_ktls);
-}
-
-#endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
-
-void checkParameterBound(const std::string& parameter, uint64_t value, size_t max)
-{
- if (value > max) {
- throw std::runtime_error("The value (" + std::to_string(value) + ") passed to " + parameter + " is too large, the maximum is " + std::to_string(max));
- }
-}
-
-static void LuaThread(const std::string& code)
-{
- setThreadName("dnsdist/lua-bg");
- LuaContext l;
-
- // mask SIGTERM on threads so the signal always comes to dnsdist itself
- sigset_t blockSignals;
-
- sigemptyset(&blockSignals);
- sigaddset(&blockSignals, SIGTERM);
-
- pthread_sigmask(SIG_BLOCK, &blockSignals, nullptr);
-
- // submitToMainThread is camelcased, threadmessage is not.
- // This follows our tradition of hooks we call being lowercased but functions the user can call being camelcased.
- l.writeFunction("submitToMainThread", [](std::string cmd, LuaAssociativeTable<std::string> data) {
- auto lua = g_lua.lock();
- // maybe offer more than `void`
- auto func = lua->readVariable<boost::optional<std::function<void(std::string cmd, LuaAssociativeTable<std::string> data)>>>("threadmessage");
- if (func) {
- func.get()(cmd, data);
- }
- else {
- errlog("Lua thread called submitToMainThread but no threadmessage receiver is defined");
- }
- });
-
- // function threadmessage(cmd, data) print("got thread data:", cmd) for k,v in pairs(data) do print(k,v) end end
-
- for (;;) {
- try {
- l.executeCode(code);
- errlog("Lua thread exited, restarting in 5 seconds");
- }
- catch (const std::exception& e) {
- errlog("Lua thread crashed, restarting in 5 seconds: %s", e.what());
- }
- catch (...) {
- errlog("Lua thread crashed, restarting in 5 seconds");
- }
- sleep(5);
- }
-}
-
-#ifdef COVERAGE
-extern "C"
-{
- void __gcov_dump(void);
-}
-#endif
-
-static bool checkConfigurationTime(const std::string& name)
-{
- if (!g_configurationDone) {
- return true;
- }
- g_outputBuffer = name + " cannot be used at runtime!\n";
- errlog("%s cannot be used at runtime!", name);
- return false;
-}
-
-static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
-{
- typedef LuaAssociativeTable<boost::variant<bool, std::string, LuaArray<std::string>, DownstreamState::checkfunc_t>> newserver_t;
- luaCtx.writeFunction("inClientStartup", [client]() {
- return client && !g_configurationDone;
- });
-
- luaCtx.writeFunction("inConfigCheck", [configCheck]() {
- return configCheck;
- });
-
- luaCtx.writeFunction("newServer",
- [client, configCheck](boost::variant<string, newserver_t> pvars, boost::optional<int> qps) {
- setLuaSideEffect();
-
- boost::optional<newserver_t> vars = newserver_t();
- DownstreamState::Config config;
-
- std::string serverAddressStr;
- if (auto addrStr = boost::get<string>(&pvars)) {
- serverAddressStr = *addrStr;
- if (qps) {
- (*vars)["qps"] = std::to_string(*qps);
- }
- }
- else {
- vars = boost::get<newserver_t>(pvars);
- getOptionalValue<std::string>(vars, "address", serverAddressStr);
- }
-
- std::string source;
- if (getOptionalValue<std::string>(vars, "source", source) > 0) {
- /* handle source in the following forms:
- - v4 address ("192.0.2.1")
- - v6 address ("2001:DB8::1")
- - interface name ("eth0")
- - v4 address and interface name ("192.0.2.1@eth0")
- - v6 address and interface name ("2001:DB8::1@eth0")
- */
- bool parsed = false;
- std::string::size_type pos = source.find("@");
- if (pos == std::string::npos) {
- /* no '@', try to parse that as a valid v4/v6 address */
- try {
- config.sourceAddr = ComboAddress(source);
- parsed = true;
- }
- catch (...) {
- }
- }
-
- if (parsed == false) {
- /* try to parse as interface name, or v4/v6@itf */
- config.sourceItfName = source.substr(pos == std::string::npos ? 0 : pos + 1);
- unsigned int itfIdx = if_nametoindex(config.sourceItfName.c_str());
- if (itfIdx != 0) {
- if (pos == 0 || pos == std::string::npos) {
- /* "eth0" or "@eth0" */
- config.sourceItf = itfIdx;
- }
- else {
- /* "192.0.2.1@eth0" */
- config.sourceAddr = ComboAddress(source.substr(0, pos));
- config.sourceItf = itfIdx;
- }
-#ifdef SO_BINDTODEVICE
- /* we need to retain CAP_NET_RAW to be able to set SO_BINDTODEVICE in the health checks */
- g_capabilitiesToRetain.insert("CAP_NET_RAW");
-#endif
- }
- else {
- warnlog("Dismissing source %s because '%s' is not a valid interface name", source, config.sourceItfName);
- }
- }
- }
-
- std::string valueStr;
- if (getOptionalValue<std::string>(vars, "sockets", valueStr) > 0) {
- config.d_numberOfSockets = std::stoul(valueStr);
- if (config.d_numberOfSockets == 0) {
- warnlog("Dismissing invalid number of sockets '%s', using 1 instead", valueStr);
- config.d_numberOfSockets = 1;
- }
- }
-
- getOptionalIntegerValue("newServer", vars, "qps", config.d_qpsLimit);
- getOptionalIntegerValue("newServer", vars, "order", config.order);
- getOptionalIntegerValue("newServer", vars, "weight", config.d_weight);
- if (config.d_weight < 1) {
- errlog("Error creating new server: downstream weight value must be greater than 0.");
- return std::shared_ptr<DownstreamState>();
- }
-
- getOptionalIntegerValue("newServer", vars, "retries", config.d_retries);
- getOptionalIntegerValue("newServer", vars, "tcpConnectTimeout", config.tcpConnectTimeout);
- getOptionalIntegerValue("newServer", vars, "tcpSendTimeout", config.tcpSendTimeout);
- getOptionalIntegerValue("newServer", vars, "tcpRecvTimeout", config.tcpRecvTimeout);
-
- if (getOptionalValue<std::string>(vars, "checkInterval", valueStr) > 0) {
- config.checkInterval = static_cast<unsigned int>(std::stoul(valueStr));
- }
-
- bool fastOpen{false};
- if (getOptionalValue<bool>(vars, "tcpFastOpen", fastOpen) > 0) {
- if (fastOpen) {
-#ifdef MSG_FASTOPEN
- config.tcpFastOpen = true;
-#else
- warnlog("TCP Fast Open has been configured on downstream server %s but is not supported", serverAddressStr);
-#endif
- }
- }
-
- getOptionalIntegerValue("newServer", vars, "maxInFlight", config.d_maxInFlightQueriesPerConn);
- getOptionalIntegerValue("newServer", vars, "maxConcurrentTCPConnections", config.d_tcpConcurrentConnectionsLimit);
-
- getOptionalValue<std::string>(vars, "name", config.name);
-
- if (getOptionalValue<std::string>(vars, "id", valueStr) > 0) {
- config.id = boost::uuids::string_generator()(valueStr);
- }
-
- if (getOptionalValue<std::string>(vars, "healthCheckMode", valueStr) > 0) {
- const auto& mode = valueStr;
- if (pdns_iequals(mode, "auto")) {
- config.availability = DownstreamState::Availability::Auto;
- }
- else if (pdns_iequals(mode, "lazy")) {
- config.availability = DownstreamState::Availability::Lazy;
- }
- else if (pdns_iequals(mode, "up")) {
- config.availability = DownstreamState::Availability::Up;
- }
- else if (pdns_iequals(mode, "down")) {
- config.availability = DownstreamState::Availability::Down;
- }
- else {
- warnlog("Ignoring unknown value '%s' for 'healthCheckMode' on 'newServer'", mode);
- }
- }
-
- if (getOptionalValue<std::string>(vars, "checkName", valueStr) > 0) {
- config.checkName = DNSName(valueStr);
- }
-
- getOptionalValue<std::string>(vars, "checkType", config.checkType);
- getOptionalIntegerValue("newServer", vars, "checkClass", config.checkClass);
- getOptionalValue<DownstreamState::checkfunc_t>(vars, "checkFunction", config.checkFunction);
- getOptionalIntegerValue("newServer", vars, "checkTimeout", config.checkTimeout);
- getOptionalValue<bool>(vars, "checkTCP", config.d_tcpCheck);
- getOptionalValue<bool>(vars, "setCD", config.setCD);
- getOptionalValue<bool>(vars, "mustResolve", config.mustResolve);
-
- if (getOptionalValue<std::string>(vars, "lazyHealthCheckSampleSize", valueStr) > 0) {
- const auto& value = std::stoi(valueStr);
- checkParameterBound("lazyHealthCheckSampleSize", value);
- config.d_lazyHealthCheckSampleSize = value;
- }
-
- if (getOptionalValue<std::string>(vars, "lazyHealthCheckMinSampleCount", valueStr) > 0) {
- const auto& value = std::stoi(valueStr);
- checkParameterBound("lazyHealthCheckMinSampleCount", value);
- config.d_lazyHealthCheckMinSampleCount = value;
- }
-
- if (getOptionalValue<std::string>(vars, "lazyHealthCheckThreshold", valueStr) > 0) {
- const auto& value = std::stoi(valueStr);
- checkParameterBound("lazyHealthCheckThreshold", value, std::numeric_limits<uint8_t>::max());
- config.d_lazyHealthCheckThreshold = value;
- }
-
- if (getOptionalValue<std::string>(vars, "lazyHealthCheckFailedInterval", valueStr) > 0) {
- const auto& value = std::stoi(valueStr);
- checkParameterBound("lazyHealthCheckFailedInterval", value);
- config.d_lazyHealthCheckFailedInterval = value;
- }
-
- getOptionalValue<bool>(vars, "lazyHealthCheckUseExponentialBackOff", config.d_lazyHealthCheckUseExponentialBackOff);
-
- if (getOptionalValue<std::string>(vars, "lazyHealthCheckMaxBackOff", valueStr) > 0) {
- const auto& value = std::stoi(valueStr);
- checkParameterBound("lazyHealthCheckMaxBackOff", value);
- config.d_lazyHealthCheckMaxBackOff = value;
- }
-
- if (getOptionalValue<std::string>(vars, "lazyHealthCheckMode", valueStr) > 0) {
- const auto& mode = valueStr;
- if (pdns_iequals(mode, "TimeoutOnly")) {
- config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOnly;
- }
- else if (pdns_iequals(mode, "TimeoutOrServFail")) {
- config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOrServFail;
- }
- else {
- warnlog("Ignoring unknown value '%s' for 'lazyHealthCheckMode' on 'newServer'", mode);
- }
- }
-
- getOptionalValue<bool>(vars, "lazyHealthCheckWhenUpgraded", config.d_upgradeToLazyHealthChecks);
-
- getOptionalValue<bool>(vars, "useClientSubnet", config.useECS);
- getOptionalValue<bool>(vars, "useProxyProtocol", config.useProxyProtocol);
- getOptionalValue<bool>(vars, "disableZeroScoping", config.disableZeroScope);
- getOptionalValue<bool>(vars, "ipBindAddrNoPort", config.ipBindAddrNoPort);
-
- getOptionalIntegerValue("newServer", vars, "addXPF", config.xpfRRCode);
- getOptionalIntegerValue("newServer", vars, "maxCheckFailures", config.maxCheckFailures);
- getOptionalIntegerValue("newServer", vars, "rise", config.minRiseSuccesses);
-
- getOptionalValue<bool>(vars, "reconnectOnUp", config.reconnectOnUp);
-
- LuaArray<string> cpuMap;
- if (getOptionalValue<decltype(cpuMap)>(vars, "cpus", cpuMap) > 0) {
- for (const auto& cpu : cpuMap) {
- config.d_cpus.insert(std::stoi(cpu.second));
- }
- }
-
- getOptionalValue<bool>(vars, "tcpOnly", config.d_tcpOnly);
-
- std::shared_ptr<TLSCtx> tlsCtx;
- getOptionalValue<std::string>(vars, "ciphers", config.d_tlsParams.d_ciphers);
- getOptionalValue<std::string>(vars, "ciphers13", config.d_tlsParams.d_ciphers13);
- getOptionalValue<std::string>(vars, "caStore", config.d_tlsParams.d_caStore);
- getOptionalValue<bool>(vars, "validateCertificates", config.d_tlsParams.d_validateCertificates);
- getOptionalValue<bool>(vars, "releaseBuffers", config.d_tlsParams.d_releaseBuffers);
- getOptionalValue<bool>(vars, "enableRenegotiation", config.d_tlsParams.d_enableRenegotiation);
- getOptionalValue<bool>(vars, "ktls", config.d_tlsParams.d_ktls);
- getOptionalValue<std::string>(vars, "subjectName", config.d_tlsSubjectName);
-
- if (getOptionalValue<std::string>(vars, "subjectAddr", valueStr) > 0) {
- try {
- ComboAddress ca(valueStr);
- config.d_tlsSubjectName = ca.toString();
- config.d_tlsSubjectIsAddr = true;
- }
- catch (const std::exception& e) {
- errlog("Error creating new server: downstream subjectAddr value must be a valid IP address");
- return std::shared_ptr<DownstreamState>();
- }
- }
-
- uint16_t serverPort = 53;
-
- if (getOptionalValue<std::string>(vars, "tls", valueStr) > 0) {
- serverPort = 853;
- config.d_tlsParams.d_provider = valueStr;
- tlsCtx = getTLSContext(config.d_tlsParams);
-
- if (getOptionalValue<std::string>(vars, "dohPath", valueStr) > 0) {
-#ifndef HAVE_NGHTTP2
- throw std::runtime_error("Outgoing DNS over HTTPS support requested (via 'dohPath' on newServer()) but nghttp2 support is not available");
-#endif
-
- serverPort = 443;
- config.d_dohPath = valueStr;
-
- getOptionalValue<bool>(vars, "addXForwardedHeaders", config.d_addXForwardedHeaders);
- }
- }
-
- try {
- config.remote = ComboAddress(serverAddressStr, serverPort);
- }
- catch (const PDNSException& e) {
- g_outputBuffer = "Error creating new server: " + string(e.reason);
- errlog("Error creating new server with address %s: %s", serverAddressStr, e.reason);
- return std::shared_ptr<DownstreamState>();
- }
- catch (const std::exception& e) {
- g_outputBuffer = "Error creating new server: " + string(e.what());
- errlog("Error creating new server with address %s: %s", serverAddressStr, e.what());
- return std::shared_ptr<DownstreamState>();
- }
-
- if (IsAnyAddress(config.remote)) {
- g_outputBuffer = "Error creating new server: invalid address for a downstream server.";
- errlog("Error creating new server: %s is not a valid address for a downstream server", serverAddressStr);
- return std::shared_ptr<DownstreamState>();
- }
-
- LuaArray<std::string> pools;
- if (getOptionalValue<std::string>(vars, "pool", valueStr, false) > 0) {
- config.pools.insert(valueStr);
- }
- else if (getOptionalValue<decltype(pools)>(vars, "pool", pools) > 0) {
- for (auto& p : pools) {
- config.pools.insert(p.second);
- }
- }
-
- bool autoUpgrade = false;
- bool keepAfterUpgrade = false;
- uint32_t upgradeInterval = 3600;
- uint16_t upgradeDoHKey = dnsdist::ServiceDiscovery::s_defaultDoHSVCKey;
- std::string upgradePool;
-
- getOptionalValue<bool>(vars, "autoUpgrade", autoUpgrade);
- if (autoUpgrade) {
- if (getOptionalValue<std::string>(vars, "autoUpgradeInterval", valueStr) > 0) {
- try {
- upgradeInterval = static_cast<uint32_t>(std::stoul(valueStr));
- }
- catch (const std::exception& e) {
- warnlog("Error parsing 'autoUpgradeInterval' value: %s", e.what());
- }
- }
- getOptionalValue<bool>(vars, "autoUpgradeKeep", keepAfterUpgrade);
- getOptionalValue<std::string>(vars, "autoUpgradePool", upgradePool);
- if (getOptionalValue<std::string>(vars, "autoUpgradeDoHKey", valueStr) > 0) {
- try {
- upgradeDoHKey = static_cast<uint16_t>(std::stoul(valueStr));
- }
- catch (const std::exception& e) {
- warnlog("Error parsing 'autoUpgradeDoHKey' value: %s", e.what());
- }
- }
- }
-
- // create but don't connect the socket in client or check-config modes
- auto ret = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), !(client || configCheck));
- if (!(client || configCheck)) {
- infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort());
- }
-
- if (autoUpgrade && ret->getProtocol() != dnsdist::Protocol::DoT && ret->getProtocol() != dnsdist::Protocol::DoH) {
- dnsdist::ServiceDiscovery::addUpgradeableServer(ret, upgradeInterval, upgradePool, upgradeDoHKey, keepAfterUpgrade);
- }
-
- /* this needs to be done _AFTER_ the order has been set,
- since the server are kept ordered inside the pool */
- auto localPools = g_pools.getCopy();
- if (!ret->d_config.pools.empty()) {
- for (const auto& poolName : ret->d_config.pools) {
- addServerToPool(localPools, poolName, ret);
- }
- }
- else {
- addServerToPool(localPools, "", ret);
- }
- g_pools.setState(localPools);
-
- if (ret->connected) {
- if (g_launchWork) {
- g_launchWork->push_back([ret]() {
- ret->start();
- });
- }
- else {
- ret->start();
- }
- }
-
- auto states = g_dstates.getCopy();
- states.push_back(ret);
- std::stable_sort(states.begin(), states.end(), [](const decltype(ret)& a, const decltype(ret)& b) {
- return a->d_config.order < b->d_config.order;
- });
- g_dstates.setState(states);
- checkAllParametersConsumed("newServer", vars);
- return ret;
- });
-
- luaCtx.writeFunction("rmServer",
- [](boost::variant<std::shared_ptr<DownstreamState>, int, std::string> var) {
- setLuaSideEffect();
- shared_ptr<DownstreamState> server = nullptr;
- auto states = g_dstates.getCopy();
- if (auto* rem = boost::get<shared_ptr<DownstreamState>>(&var)) {
- server = *rem;
- }
- else if (auto str = boost::get<std::string>(&var)) {
- const auto uuid = getUniqueID(*str);
- for (auto& state : states) {
- if (*state->d_config.id == uuid) {
- server = state;
- }
- }
- }
- else {
- int idx = boost::get<int>(var);
- server = states.at(idx);
- }
- if (!server) {
- throw std::runtime_error("unable to locate the requested server");
- }
- auto localPools = g_pools.getCopy();
- for (const string& poolName : server->d_config.pools) {
- removeServerFromPool(localPools, poolName, server);
- }
- /* the server might also be in the default pool */
- removeServerFromPool(localPools, "", server);
- g_pools.setState(localPools);
- states.erase(remove(states.begin(), states.end(), server), states.end());
- g_dstates.setState(states);
- server->stop();
- });
-
- luaCtx.writeFunction("truncateTC", [](bool tc) { setLuaSideEffect(); g_truncateTC=tc; });
- luaCtx.writeFunction("fixupCase", [](bool fu) { setLuaSideEffect(); g_fixupCase=fu; });
-
- luaCtx.writeFunction("addACL", [](const std::string& domain) {
- setLuaSideEffect();
- g_ACL.modify([domain](NetmaskGroup& nmg) { nmg.addMask(domain); });
- });
-
- luaCtx.writeFunction("rmACL", [](const std::string& netmask) {
- setLuaSideEffect();
- g_ACL.modify([netmask](NetmaskGroup& nmg) { nmg.deleteMask(netmask); });
- });
-
- luaCtx.writeFunction("setLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
- setLuaSideEffect();
- if (client) {
- return;
- }
-
- if (!checkConfigurationTime("setLocal")) {
- return;
- }
-
- bool reusePort = false;
- int tcpFastOpenQueueSize = 0;
- int tcpListenQueueSize = 0;
- uint64_t maxInFlightQueriesPerConn = 0;
- uint64_t tcpMaxConcurrentConnections = 0;
- std::string interface;
- std::set<int> cpus;
-
- parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
-
- checkAllParametersConsumed("setLocal", vars);
-
- try {
- ComboAddress loc(addr, 53);
- for (auto it = g_frontends.begin(); it != g_frontends.end();) {
- /* DoH, DoT and DNSCrypt frontends are separate */
- if ((*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->dohFrontend == nullptr) {
- it = g_frontends.erase(it);
- }
- else {
- ++it;
- }
- }
-
- // only works pre-startup, so no sync necessary
- g_frontends.push_back(std::make_unique<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus));
- auto tcpCS = std::make_unique<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
- if (tcpListenQueueSize > 0) {
- tcpCS->tcpListenQueueSize = tcpListenQueueSize;
- }
- if (maxInFlightQueriesPerConn > 0) {
- tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
- }
- if (tcpMaxConcurrentConnections > 0) {
- tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
- }
-
- g_frontends.push_back(std::move(tcpCS));
- }
- catch (const std::exception& e) {
- g_outputBuffer = "Error: " + string(e.what()) + "\n";
- }
- });
-
- luaCtx.writeFunction("addLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
- setLuaSideEffect();
- if (client)
- return;
-
- if (!checkConfigurationTime("addLocal")) {
- return;
- }
- bool reusePort = false;
- int tcpFastOpenQueueSize = 0;
- int tcpListenQueueSize = 0;
- uint64_t maxInFlightQueriesPerConn = 0;
- uint64_t tcpMaxConcurrentConnections = 0;
- std::string interface;
- std::set<int> cpus;
-
- parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
- checkAllParametersConsumed("addLocal", vars);
-
- try {
- ComboAddress loc(addr, 53);
- // only works pre-startup, so no sync necessary
- g_frontends.push_back(std::make_unique<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus));
- auto tcpCS = std::make_unique<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
- if (tcpListenQueueSize > 0) {
- tcpCS->tcpListenQueueSize = tcpListenQueueSize;
- }
- if (maxInFlightQueriesPerConn > 0) {
- tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
- }
- if (tcpMaxConcurrentConnections > 0) {
- tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
- }
- g_frontends.push_back(std::move(tcpCS));
- }
- catch (std::exception& e) {
- g_outputBuffer = "Error: " + string(e.what()) + "\n";
- errlog("Error while trying to listen on %s: %s\n", addr, string(e.what()));
- }
- });
-
- luaCtx.writeFunction("setACL", [](LuaTypeOrArrayOf<std::string> inp) {
- setLuaSideEffect();
- NetmaskGroup nmg;
- if (auto str = boost::get<string>(&inp)) {
- nmg.addMask(*str);
- }
- else
- for (const auto& p : boost::get<LuaArray<std::string>>(inp)) {
- nmg.addMask(p.second);
- }
- g_ACL.setState(nmg);
- });
-
- luaCtx.writeFunction("setACLFromFile", [](const std::string& file) {
- setLuaSideEffect();
- NetmaskGroup nmg;
-
- ifstream ifs(file);
- if (!ifs) {
- throw std::runtime_error("Could not open '" + file + "': " + stringerror());
- }
-
- string::size_type pos;
- string line;
- while (getline(ifs, line)) {
- pos = line.find('#');
- if (pos != string::npos)
- line.resize(pos);
- boost::trim(line);
- if (line.empty())
- continue;
-
- nmg.addMask(line);
- }
-
- g_ACL.setState(nmg);
- });
-
- luaCtx.writeFunction("showACL", []() {
- setLuaNoSideEffect();
- vector<string> vec;
-
- g_ACL.getLocal()->toStringVector(&vec);
-
- for (const auto& s : vec)
- g_outputBuffer += s + "\n";
- });
-
- luaCtx.writeFunction("shutdown", []() {
-#ifdef HAVE_SYSTEMD
- sd_notify(0, "STOPPING=1");
-#endif /* HAVE_SYSTEMD */
-#if 0
- // Useful for debugging leaks, but might lead to race under load
- // since other threads are still running.
- for (auto& frontend : g_tlslocals) {
- frontend->cleanup();
- }
- g_tlslocals.clear();
- g_rings.clear();
-#endif /* 0 */
-#ifdef COVERAGE
- __gcov_dump();
-#endif
- _exit(0);
- });
-
- typedef LuaAssociativeTable<boost::variant<bool, std::string>> showserversopts_t;
-
- luaCtx.writeFunction("showServers", [](boost::optional<showserversopts_t> vars) {
- setLuaNoSideEffect();
- bool showUUIDs = false;
- getOptionalValue<bool>(vars, "showUUIDs", showUUIDs);
- checkAllParametersConsumed("showServers", vars);
-
- try {
- ostringstream ret;
- boost::format fmt;
-
- auto latFmt = boost::format("%5.1f");
- if (showUUIDs) {
- fmt = boost::format("%1$-3d %15$-36s %2$-20.20s %|62t|%3% %|107t|%4$5s %|88t|%5$7.1f %|103t|%6$7d %|106t|%7$10d %|115t|%8$10d %|117t|%9$10d %|123t|%10$7d %|128t|%11$5.1f %|146t|%12$5s %|152t|%16$5s %|158t|%13$11d %14%");
- // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (tcp latency)
- ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "UUID" % "TCP") << endl;
- }
- else {
- fmt = boost::format("%1$-3d %2$-20.20s %|25t|%3% %|70t|%4$5s %|51t|%5$7.1f %|66t|%6$7d %|69t|%7$10d %|78t|%8$10d %|80t|%9$10d %|86t|%10$7d %|91t|%11$5.1f %|109t|%12$5s %|115t|%15$5s %|121t|%13$11d %14%");
- ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "TCP") << endl;
- }
-
- uint64_t totQPS{0}, totQueries{0}, totDrops{0};
- int counter = 0;
- auto states = g_dstates.getLocal();
- for (const auto& s : *states) {
- string status = s->getStatus();
- string pools;
- for (const auto& p : s->d_config.pools) {
- if (!pools.empty()) {
- pools += " ";
- }
- pools += p;
- }
- const std::string latency = (s->latencyUsec == 0.0 ? "-" : boost::str(latFmt % (s->latencyUsec / 1000.0)));
- const std::string latencytcp = (s->latencyUsecTCP == 0.0 ? "-" : boost::str(latFmt % (s->latencyUsecTCP / 1000.0)));
- if (showUUIDs) {
- ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % status % s->queryLoad % s->qps.getRate() % s->d_config.order % s->d_config.d_weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % latency % s->outstanding.load() % pools % *s->d_config.id % latencytcp) << endl;
- }
- else {
- ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % status % s->queryLoad % s->qps.getRate() % s->d_config.order % s->d_config.d_weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % latency % s->outstanding.load() % pools % latencytcp) << endl;
- }
- totQPS += s->queryLoad;
- totQueries += s->queries.load();
- totDrops += s->reuseds.load();
- ++counter;
- }
- if (showUUIDs) {
- ret << (fmt % "All" % "" % "" % ""
- % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "" % "")
- << endl;
- }
- else {
- ret << (fmt % "All" % "" % "" % ""
- % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "")
- << endl;
- }
-
- g_outputBuffer = ret.str();
- }
- catch (std::exception& e) {
- g_outputBuffer = e.what();
- throw;
- }
- });
-
- luaCtx.writeFunction("getServers", []() {
- setLuaNoSideEffect();
- LuaArray<std::shared_ptr<DownstreamState>> ret;
- int count = 1;
- for (const auto& s : g_dstates.getCopy()) {
- ret.push_back(make_pair(count++, s));
- }
- return ret;
- });
-
- luaCtx.writeFunction("getPoolServers", [](const string& pool) {
- const auto poolServers = getDownstreamCandidates(g_pools.getCopy(), pool);
- return *poolServers;
- });
-
- luaCtx.writeFunction("getServer", [client](boost::variant<int, std::string> i) {
- if (client) {
- return std::make_shared<DownstreamState>(ComboAddress());
- }
- auto states = g_dstates.getCopy();
- if (auto str = boost::get<std::string>(&i)) {
- const auto uuid = getUniqueID(*str);
- for (auto& state : states) {
- if (*state->d_config.id == uuid) {
- return state;
- }
- }
- }
- else if (auto pos = boost::get<int>(&i)) {
- return states.at(*pos);
- }
-
- g_outputBuffer = "Error: no rule matched\n";
- return std::shared_ptr<DownstreamState>(nullptr);
- });
-
-#ifndef DISABLE_CARBON
- luaCtx.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName, boost::optional<uint64_t> interval, boost::optional<string> namespace_name, boost::optional<string> instance_name) {
- setLuaSideEffect();
- dnsdist::Carbon::Endpoint endpoint{ComboAddress(address, 2003),
- (namespace_name && !namespace_name->empty()) ? *namespace_name : "dnsdist",
- ourName ? *ourName : "",
- (instance_name && !instance_name->empty()) ? *instance_name : "main",
- (interval && *interval < std::numeric_limits<unsigned int>::max()) ? static_cast<unsigned int>(*interval) : 30};
- dnsdist::Carbon::addEndpoint(std::move(endpoint));
- });
-#endif /* DISABLE_CARBON */
-
- luaCtx.writeFunction("webserver", [client, configCheck](const std::string& address) {
- setLuaSideEffect();
- ComboAddress local;
- try {
- local = ComboAddress(address);
- }
- catch (const PDNSException& e) {
- throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason);
- }
-
- if (client || configCheck) {
- return;
- }
-
- try {
- int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0);
- SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
- SBind(sock, local);
- SListen(sock, 5);
- auto launch = [sock, local]() {
- thread t(dnsdistWebserverThread, sock, local);
- t.detach();
- };
- if (g_launchWork) {
- g_launchWork->push_back(launch);
- }
- else {
- launch();
- }
- }
- catch (const std::exception& e) {
- g_outputBuffer = "Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what();
- errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what());
- }
- });
-
- typedef LuaAssociativeTable<boost::variant<bool, std::string, LuaAssociativeTable<std::string>>> webserveropts_t;
-
- luaCtx.writeFunction("setWebserverConfig", [](boost::optional<webserveropts_t> vars) {
- setLuaSideEffect();
-
- if (!vars) {
- return;
- }
-
- bool hashPlaintextCredentials = false;
- getOptionalValue<bool>(vars, "hashPlaintextCredentials", hashPlaintextCredentials);
-
- std::string password;
- std::string apiKey;
- std::string acl;
- LuaAssociativeTable<std::string> headers;
- bool statsRequireAuthentication{true};
- bool apiRequiresAuthentication{true};
- bool dashboardRequiresAuthentication{true};
- int maxConcurrentConnections = 0;
-
- if (getOptionalValue<std::string>(vars, "password", password) > 0) {
- auto holder = make_unique<CredentialsHolder>(std::move(password), hashPlaintextCredentials);
- if (!holder->wasHashed() && holder->isHashingAvailable()) {
- infolog("Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead.");
- }
-
- setWebserverPassword(std::move(holder));
- }
-
- if (getOptionalValue<std::string>(vars, "apiKey", apiKey) > 0) {
- auto holder = make_unique<CredentialsHolder>(std::move(apiKey), hashPlaintextCredentials);
- if (!holder->wasHashed() && holder->isHashingAvailable()) {
- infolog("Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead.");
- }
-
- setWebserverAPIKey(std::move(holder));
- }
-
- if (getOptionalValue<std::string>(vars, "acl", acl) > 0) {
- setWebserverACL(acl);
- }
-
- if (getOptionalValue<decltype(headers)>(vars, "customHeaders", headers) > 0) {
- setWebserverCustomHeaders(headers);
- }
-
- if (getOptionalValue<bool>(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) {
- setWebserverStatsRequireAuthentication(statsRequireAuthentication);
- }
-
- if (getOptionalValue<bool>(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) {
- setWebserverAPIRequiresAuthentication(apiRequiresAuthentication);
- }
-
- if (getOptionalValue<bool>(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) {
- setWebserverDashboardRequiresAuthentication(dashboardRequiresAuthentication);
- }
-
- if (getOptionalIntegerValue("setWebserverConfig", vars, "maxConcurrentConnections", maxConcurrentConnections) > 0) {
- setWebserverMaxConcurrentConnections(maxConcurrentConnections);
- }
- });
-
- luaCtx.writeFunction("showWebserverConfig", []() {
- setLuaNoSideEffect();
- return getWebserverConfig();
- });
-
- luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional<uint64_t> workFactor) {
- if (workFactor) {
- return hashPassword(password, *workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
- }
- return hashPassword(password);
- });
-
- luaCtx.writeFunction("controlSocket", [client, configCheck](const std::string& str) {
- setLuaSideEffect();
- ComboAddress local(str, 5199);
-
- if (client || configCheck) {
- g_serverControl = local;
- return;
- }
-
- g_consoleEnabled = true;
-#ifdef HAVE_LIBSODIUM
- if (g_configurationDone && g_consoleKey.empty()) {
- warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
- }
-#endif
-
- try {
- int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0);
- SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
- SBind(sock, local);
- SListen(sock, 5);
- auto launch = [sock, local]() {
- thread t(controlThread, sock, local);
- t.detach();
- };
- if (g_launchWork) {
- g_launchWork->push_back(launch);
- }
- else {
- launch();
- }
- }
- catch (std::exception& e) {
- g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what();
- errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what());
- }
- });
-
- luaCtx.writeFunction("addConsoleACL", [](const std::string& netmask) {
- setLuaSideEffect();
-#ifndef HAVE_LIBSODIUM
- warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
-#endif
-
- g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); });
- });
-
- luaCtx.writeFunction("setConsoleACL", [](LuaTypeOrArrayOf<std::string> inp) {
- setLuaSideEffect();
-
-#ifndef HAVE_LIBSODIUM
- warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
-#endif
-
- NetmaskGroup nmg;
- if (auto str = boost::get<string>(&inp)) {
- nmg.addMask(*str);
- }
- else
- for (const auto& p : boost::get<LuaArray<std::string>>(inp)) {
- nmg.addMask(p.second);
- }
- g_consoleACL.setState(nmg);
- });
-
- luaCtx.writeFunction("showConsoleACL", []() {
- setLuaNoSideEffect();
-
-#ifndef HAVE_LIBSODIUM
- warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
-#endif
-
- vector<string> vec;
- g_consoleACL.getLocal()->toStringVector(&vec);
-
- for (const auto& s : vec) {
- g_outputBuffer += s + "\n";
- }
- });
-
- luaCtx.writeFunction("setConsoleMaximumConcurrentConnections", [](uint64_t max) {
- setLuaSideEffect();
- setConsoleMaximumConcurrentConnections(max);
- });
-
- luaCtx.writeFunction("clearQueryCounters", []() {
- unsigned int size{0};
- {
- auto records = g_qcount.records.write_lock();
- size = records->size();
- records->clear();
- }
-
- boost::format fmt("%d records cleared from query counter buffer\n");
- g_outputBuffer = (fmt % size).str();
- });
-
- luaCtx.writeFunction("getQueryCounters", [](boost::optional<uint64_t> optMax) {
- setLuaNoSideEffect();
- auto records = g_qcount.records.read_lock();
- g_outputBuffer = "query counting is currently: ";
- g_outputBuffer += g_qcount.enabled ? "enabled" : "disabled";
- g_outputBuffer += (boost::format(" (%d records in buffer)\n") % records->size()).str();
-
- boost::format fmt("%-3d %s: %d request(s)\n");
- uint64_t max = optMax ? *optMax : 10U;
- uint64_t index{1};
- for (auto it = records->begin(); it != records->end() && index <= max; ++it, ++index) {
- g_outputBuffer += (fmt % index % it->first % it->second).str();
- }
- });
-
- luaCtx.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled = enabled; });
-
- luaCtx.writeFunction("setQueryCountFilter", [](QueryCountFilter func) {
- g_qcount.filter = func;
- });
-
- luaCtx.writeFunction("makeKey", []() {
- setLuaNoSideEffect();
- g_outputBuffer = "setKey(" + newKey() + ")\n";
- });
-
- luaCtx.writeFunction("setKey", [](const std::string& key) {
- if (!g_configurationDone && !g_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
- return; // but later setKeys() trump the -k value again
- }
-#ifndef HAVE_LIBSODIUM
- warnlog("Calling setKey() while libsodium support has not been enabled is not secure, and will result in cleartext communications");
-#endif
-
- setLuaSideEffect();
- string newkey;
- if (B64Decode(key, newkey) < 0) {
- g_outputBuffer = string("Unable to decode ") + key + " as Base64";
- errlog("%s", g_outputBuffer);
- }
- else
- g_consoleKey = newkey;
- });
-
- luaCtx.writeFunction("clearConsoleHistory", []() {
- clearConsoleHistory();
- });
-
- luaCtx.writeFunction("testCrypto", [](boost::optional<string> optTestMsg) {
- setLuaNoSideEffect();
-#ifdef HAVE_LIBSODIUM
- try {
- string testmsg;
-
- if (optTestMsg) {
- testmsg = *optTestMsg;
- }
- else {
- testmsg = "testStringForCryptoTests";
- }
-
- SodiumNonce sn, sn2;
- sn.init();
- sn2 = sn;
- string encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
- string decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
-
- sn.increment();
- sn2.increment();
-
- encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
- decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
-
- if (testmsg == decrypted)
- g_outputBuffer = "Everything is ok!\n";
- else
- g_outputBuffer = "Crypto failed.. (the decoded value does not match the cleartext one)\n";
- }
- catch (const std::exception& e) {
- g_outputBuffer = "Crypto failed: " + std::string(e.what()) + "\n";
- }
- catch (...) {
- g_outputBuffer = "Crypto failed..\n";
- }
-#else
- g_outputBuffer = "Crypto not available.\n";
-#endif
- });
-
- luaCtx.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout = timeout; });
-
- luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout = timeout; });
-
- luaCtx.writeFunction("setUDPTimeout", [](int timeout) { DownstreamState::s_udpTimeout = timeout; });
-
- luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) {
- if (!checkConfigurationTime("setMaxUDPOutstanding")) {
- return;
- }
-
- checkParameterBound("setMaxUDPOutstanding", max);
- g_maxOutstanding = max;
- });
-
- luaCtx.writeFunction("setMaxTCPClientThreads", [](uint64_t max) {
- if (!checkConfigurationTime("setMaxTCPClientThreads")) {
- return;
- }
- g_maxTCPClientThreads = max;
- });
-
- luaCtx.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) {
- if (!checkConfigurationTime("setMaxTCPQueuedConnections")) {
- return;
- }
- g_maxTCPQueuedConnections = max;
- });
-
- luaCtx.writeFunction("setMaxTCPQueriesPerConnection", [](uint64_t max) {
- if (!checkConfigurationTime("setMaxTCPQueriesPerConnection")) {
- return;
- }
- g_maxTCPQueriesPerConn = max;
- });
-
- luaCtx.writeFunction("setMaxTCPConnectionsPerClient", [](uint64_t max) {
- if (!checkConfigurationTime("setMaxTCPConnectionsPerClient")) {
- return;
- }
- dnsdist::IncomingConcurrentTCPConnectionsManager::setMaxTCPConnectionsPerClient(max);
- });
-
- luaCtx.writeFunction("setMaxTCPConnectionDuration", [](uint64_t max) {
- if (!checkConfigurationTime("setMaxTCPConnectionDuration")) {
- return;
- }
- g_maxTCPConnectionDuration = max;
- });
-
- luaCtx.writeFunction("setMaxCachedTCPConnectionsPerDownstream", [](uint64_t max) {
- setTCPDownstreamMaxIdleConnectionsPerBackend(max);
- });
-
- luaCtx.writeFunction("setMaxIdleDoHConnectionsPerDownstream", [](uint64_t max) {
- setDoHDownstreamMaxIdleConnectionsPerBackend(max);
- });
-
- luaCtx.writeFunction("setOutgoingDoHWorkerThreads", [](uint64_t workers) {
- if (!checkConfigurationTime("setOutgoingDoHWorkerThreads")) {
- return;
- }
- g_outgoingDoHWorkerThreads = workers;
- });
-
- luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketsPerBackend", [](uint64_t max) {
- if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketsPerBackend")) {
- return;
- }
- TLSSessionCache::setMaxTicketsPerBackend(max);
- });
-
- luaCtx.writeFunction("setOutgoingTLSSessionsCacheCleanupDelay", [](time_t delay) {
- if (!checkConfigurationTime("setOutgoingTLSSessionsCacheCleanupDelay")) {
- return;
- }
- TLSSessionCache::setCleanupDelay(delay);
- });
-
- luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketValidity", [](time_t validity) {
- if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketValidity")) {
- return;
- }
- TLSSessionCache::setSessionValidity(validity);
- });
-
- luaCtx.writeFunction("getOutgoingTLSSessionCacheSize", []() {
- setLuaNoSideEffect();
- return g_sessionCache.getSize();
- });
-
- luaCtx.writeFunction("setCacheCleaningDelay", [](uint64_t delay) {
- checkParameterBound("setCacheCleaningDelay", delay, std::numeric_limits<uint32_t>::max());
- g_cacheCleaningDelay = delay;
- });
-
- luaCtx.writeFunction("setCacheCleaningPercentage", [](uint64_t percentage) { if (percentage < 100) g_cacheCleaningPercentage = percentage; else g_cacheCleaningPercentage = 100; });
-
- luaCtx.writeFunction("setECSSourcePrefixV4", [](uint64_t prefix) {
- checkParameterBound("setECSSourcePrefixV4", prefix, std::numeric_limits<uint16_t>::max());
- g_ECSSourcePrefixV4 = prefix;
- });
-
- luaCtx.writeFunction("setECSSourcePrefixV6", [](uint64_t prefix) {
- checkParameterBound("setECSSourcePrefixV6", prefix, std::numeric_limits<uint16_t>::max());
- g_ECSSourcePrefixV6 = prefix;
- });
-
- luaCtx.writeFunction("setECSOverride", [](bool override) { g_ECSOverride = override; });
-
-#ifndef DISABLE_DYNBLOCKS
- luaCtx.writeFunction("showDynBlocks", []() {
- setLuaNoSideEffect();
- auto slow = g_dynblockNMG.getCopy();
- struct timespec now;
- gettime(&now);
- boost::format fmt("%-24s %8d %8d %-10s %-20s %s\n");
- g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "Reason").str();
- for (const auto& e : slow) {
- if (now < e.second.until) {
- uint64_t counter = e.second.blocks;
- if (g_defaultBPFFilter && e.second.bpf) {
- counter += g_defaultBPFFilter->getHits(e.first.getNetwork());
- }
- g_outputBuffer += (fmt % e.first.toString() % (e.second.until.tv_sec - now.tv_sec) % counter % (e.second.warning ? "true" : "false") % DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) % e.second.reason).str();
- }
- }
- auto slow2 = g_dynblockSMT.getCopy();
- slow2.visit([&now, &fmt](const SuffixMatchTree<DynBlock>& node) {
- if (now < node.d_value.until) {
- string dom("empty");
- if (!node.d_value.domain.empty())
- dom = node.d_value.domain.toString();
- g_outputBuffer += (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % (node.d_value.warning ? "true" : "false") % DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) % node.d_value.reason).str();
- }
- });
- });
-
- luaCtx.writeFunction("clearDynBlocks", []() {
- setLuaSideEffect();
- nmts_t nmg;
- g_dynblockNMG.setState(nmg);
- SuffixMatchTree<DynBlock> smt;
- g_dynblockSMT.setState(smt);
- });
-
-#ifndef DISABLE_DEPRECATED_DYNBLOCK
- luaCtx.writeFunction("addDynBlocks",
- [](const std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& m, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
- if (m.empty()) {
- return;
- }
- setLuaSideEffect();
- auto slow = g_dynblockNMG.getCopy();
- struct timespec until, now;
- gettime(&now);
- until = now;
- int actualSeconds = seconds ? *seconds : 10;
- until.tv_sec += actualSeconds;
- for (const auto& capair : m) {
- unsigned int count = 0;
- /* this legacy interface does not support ranges or ports, use DynBlockRulesGroup instead */
- AddressAndPortRange requestor(capair.first, capair.first.isIPv4() ? 32 : 128, 0);
- auto got = slow.lookup(requestor);
- bool expired = false;
- if (got) {
- if (until < got->second.until) {
- // had a longer policy
- continue;
- }
- if (now < got->second.until) {
- // only inherit count on fresh query we are extending
- count = got->second.blocks;
- }
- else {
- expired = true;
- }
- }
- DynBlock db{msg, until, DNSName(), (action ? *action : DNSAction::Action::None)};
- db.blocks = count;
- if (!got || expired) {
- warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg);
- }
- slow.insert(requestor).second = db;
- }
- g_dynblockNMG.setState(slow);
- });
-
- luaCtx.writeFunction("addDynBlockSMT",
- [](const LuaArray<std::string>& names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
- if (names.empty()) {
- return;
- }
- setLuaSideEffect();
- auto slow = g_dynblockSMT.getCopy();
- struct timespec until, now;
- gettime(&now);
- until = now;
- int actualSeconds = seconds ? *seconds : 10;
- until.tv_sec += actualSeconds;
-
- for (const auto& capair : names) {
- unsigned int count = 0;
- DNSName domain(capair.second);
- domain.makeUsLowerCase();
- auto got = slow.lookup(domain);
- bool expired = false;
- if (got) {
- if (until < got->until) // had a longer policy
- continue;
- if (now < got->until) // only inherit count on fresh query we are extending
- count = got->blocks;
- else
- expired = true;
- }
-
- DynBlock db{msg, until, domain, (action ? *action : DNSAction::Action::None)};
- db.blocks = count;
- if (!got || expired)
- warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, actualSeconds, msg);
- slow.add(domain, std::move(db));
- }
- g_dynblockSMT.setState(slow);
- });
-
- luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
- if (!checkConfigurationTime("setDynBlocksAction")) {
- return;
- }
- if (action == DNSAction::Action::Drop || action == DNSAction::Action::NoOp || action == DNSAction::Action::Nxdomain || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate || action == DNSAction::Action::NoRecurse) {
- g_dynBlockAction = action;
- }
- else {
- errlog("Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!");
- g_outputBuffer = "Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!\n";
- }
- });
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
-
- luaCtx.writeFunction("setDynBlocksPurgeInterval", [](uint64_t interval) {
- DynBlockMaintenance::s_expiredDynBlocksPurgeInterval = interval;
- });
-#endif /* DISABLE_DYNBLOCKS */
-
-#ifdef HAVE_DNSCRYPT
- luaCtx.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, LuaTypeOrArrayOf<std::string> certFiles, LuaTypeOrArrayOf<std::string> keyFiles, boost::optional<localbind_t> vars) {
- if (!checkConfigurationTime("addDNSCryptBind")) {
- return;
- }
- bool reusePort = false;
- int tcpFastOpenQueueSize = 0;
- int tcpListenQueueSize = 0;
- uint64_t maxInFlightQueriesPerConn = 0;
- uint64_t tcpMaxConcurrentConnections = 0;
- std::string interface;
- std::set<int> cpus;
- std::vector<DNSCryptContext::CertKeyPaths> certKeys;
-
- parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
- checkAllParametersConsumed("addDNSCryptBind", vars);
-
- if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
- auto certFile = boost::get<std::string>(certFiles);
- auto keyFile = boost::get<std::string>(keyFiles);
- certKeys.push_back({certFile, keyFile});
- }
- else if (certFiles.type() == typeid(LuaArray<std::string>) && keyFiles.type() == typeid(LuaArray<std::string>)) {
- auto certFilesVect = boost::get<LuaArray<std::string>>(certFiles);
- auto keyFilesVect = boost::get<LuaArray<std::string>>(keyFiles);
- if (certFilesVect.size() == keyFilesVect.size()) {
- for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
- certKeys.push_back({certFilesVect.at(idx).second, keyFilesVect.at(idx).second});
- }
- }
- else {
- errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind!");
- g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!";
- return;
- }
- }
- else {
- errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind()!");
- g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!";
- return;
- }
-
- try {
- auto ctx = std::make_shared<DNSCryptContext>(providerName, certKeys);
-
- /* UDP */
- auto cs = std::make_unique<ClientState>(ComboAddress(addr, 443), false, reusePort, tcpFastOpenQueueSize, interface, cpus);
- cs->dnscryptCtx = ctx;
- g_dnsCryptLocals.push_back(ctx);
- g_frontends.push_back(std::move(cs));
-
- /* TCP */
- cs = std::make_unique<ClientState>(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus);
- cs->dnscryptCtx = ctx;
- if (tcpListenQueueSize > 0) {
- cs->tcpListenQueueSize = tcpListenQueueSize;
- }
- if (maxInFlightQueriesPerConn > 0) {
- cs->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
- }
- if (tcpMaxConcurrentConnections > 0) {
- cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
- }
-
- g_frontends.push_back(std::move(cs));
- }
- catch (std::exception& e) {
- errlog(e.what());
- g_outputBuffer = "Error: " + string(e.what()) + "\n";
- }
- });
-
- luaCtx.writeFunction("showDNSCryptBinds", []() {
- setLuaNoSideEffect();
- ostringstream ret;
- boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s");
- ret << (fmt % "#" % "Address" % "Provider Name") << endl;
- size_t idx = 0;
-
- std::unordered_set<std::shared_ptr<DNSCryptContext>> contexts;
- for (const auto& frontend : g_frontends) {
- const std::shared_ptr<DNSCryptContext> ctx = frontend->dnscryptCtx;
- if (!ctx || contexts.count(ctx) != 0) {
- continue;
- }
- contexts.insert(ctx);
- ret << (fmt % idx % frontend->local.toStringWithPort() % ctx->getProviderName()) << endl;
- idx++;
- }
-
- g_outputBuffer = ret.str();
- });
-
- luaCtx.writeFunction("getDNSCryptBind", [](uint64_t idx) {
- setLuaNoSideEffect();
- std::shared_ptr<DNSCryptContext> ret = nullptr;
- if (idx < g_dnsCryptLocals.size()) {
- ret = g_dnsCryptLocals.at(idx);
- }
- return ret;
- });
-
- luaCtx.writeFunction("getDNSCryptBindCount", []() {
- setLuaNoSideEffect();
- return g_dnsCryptLocals.size();
- });
-#endif /* HAVE_DNSCRYPT */
-
- luaCtx.writeFunction("showPools", []() {
- setLuaNoSideEffect();
- try {
- ostringstream ret;
- boost::format fmt("%1$-20.20s %|25t|%2$20s %|25t|%3$20s %|50t|%4%");
- // 1 2 3 4
- ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers") << endl;
-
- const auto localPools = g_pools.getCopy();
- for (const auto& entry : localPools) {
- const string& name = entry.first;
- const std::shared_ptr<ServerPool> pool = entry.second;
- string cache = pool->packetCache != nullptr ? pool->packetCache->toString() : "";
- string policy = g_policy.getLocal()->getName();
- if (pool->policy != nullptr) {
- policy = pool->policy->getName();
- }
- string servers;
-
- const auto poolServers = pool->getServers();
- for (const auto& server : *poolServers) {
- if (!servers.empty()) {
- servers += ", ";
- }
- if (!server.second->getName().empty()) {
- servers += server.second->getName();
- servers += " ";
- }
- servers += server.second->d_config.remote.toStringWithPort();
- }
-
- ret << (fmt % name % cache % policy % servers) << endl;
- }
- g_outputBuffer = ret.str();
- }
- catch (std::exception& e) {
- g_outputBuffer = e.what();
- throw;
- }
- });
-
- luaCtx.writeFunction("getPoolNames", []() {
- setLuaNoSideEffect();
- LuaArray<std::string> ret;
- int count = 1;
- const auto localPools = g_pools.getCopy();
- for (const auto& entry : localPools) {
- const string& name = entry.first;
- ret.push_back(make_pair(count++, name));
- }
- return ret;
- });
-
- luaCtx.writeFunction("getPool", [client](const string& poolName) {
- if (client) {
- return std::make_shared<ServerPool>();
- }
- auto localPools = g_pools.getCopy();
- std::shared_ptr<ServerPool> pool = createPoolIfNotExists(localPools, poolName);
- g_pools.setState(localPools);
- return pool;
- });
-
- luaCtx.writeFunction("setVerbose", [](bool verbose) { g_verbose = verbose; });
- luaCtx.writeFunction("getVerbose", []() { return g_verbose; });
- luaCtx.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks = verbose; });
- luaCtx.writeFunction("setVerboseLogDestination", [](const std::string& dest) {
- if (!checkConfigurationTime("setVerboseLogDestination")) {
- return;
- }
- try {
- auto stream = std::ofstream(dest.c_str());
- g_verboseStream = std::move(stream);
- }
- catch (const std::exception& e) {
- errlog("Error while opening the verbose logging destination file %s: %s", dest, e.what());
- }
- });
-
- luaCtx.writeFunction("setStaleCacheEntriesTTL", [](uint64_t ttl) {
- checkParameterBound("setStaleCacheEntriesTTL", ttl, std::numeric_limits<uint32_t>::max());
- g_staleCacheEntriesTTL = ttl;
- });
-
- luaCtx.writeFunction("showBinds", []() {
- setLuaNoSideEffect();
- try {
- ostringstream ret;
- boost::format fmt("%1$-3d %2$-20.20s %|35t|%3$-20.20s %|57t|%4%");
- // 1 2 3 4
- ret << (fmt % "#" % "Address" % "Protocol" % "Queries") << endl;
-
- size_t counter = 0;
- for (const auto& front : g_frontends) {
- ret << (fmt % counter % front->local.toStringWithPort() % front->getType() % front->queries) << endl;
- counter++;
- }
- g_outputBuffer = ret.str();
- }
- catch (std::exception& e) {
- g_outputBuffer = e.what();
- throw;
- }
- });
-
- luaCtx.writeFunction("getBind", [](uint64_t num) {
- setLuaNoSideEffect();
- ClientState* ret = nullptr;
- if (num < g_frontends.size()) {
- ret = g_frontends[num].get();
- }
- return ret;
- });
-
- luaCtx.writeFunction("getBindCount", []() {
- setLuaNoSideEffect();
- return g_frontends.size();
- });
-
- luaCtx.writeFunction("help", [](boost::optional<std::string> command) {
- setLuaNoSideEffect();
- g_outputBuffer = "";
-#ifndef DISABLE_COMPLETION
- for (const auto& keyword : g_consoleKeywords) {
- if (!command) {
- g_outputBuffer += keyword.toString() + "\n";
- }
- else if (keyword.name == command) {
- g_outputBuffer = keyword.toString() + "\n";
- return;
- }
- }
-#endif /* DISABLE_COMPLETION */
- if (command) {
- g_outputBuffer = "Nothing found for " + *command + "\n";
- }
- });
-
- luaCtx.writeFunction("showVersion", []() {
- setLuaNoSideEffect();
- g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n";
- });
-
-#ifdef HAVE_EBPF
- luaCtx.writeFunction("setDefaultBPFFilter", [](std::shared_ptr<BPFFilter> bpf) {
- if (!checkConfigurationTime("setDefaultBPFFilter")) {
- return;
- }
- g_defaultBPFFilter = bpf;
- });
-
- luaCtx.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
- if (dbpf) {
- g_dynBPFFilters.push_back(dbpf);
- }
- });
-
- luaCtx.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
- if (dbpf) {
- for (auto it = g_dynBPFFilters.begin(); it != g_dynBPFFilters.end(); it++) {
- if (*it == dbpf) {
- g_dynBPFFilters.erase(it);
- break;
- }
- }
- }
- });
-
-#ifndef DISABLE_DYNBLOCKS
-#ifndef DISABLE_DEPRECATED_DYNBLOCK
- luaCtx.writeFunction("addBPFFilterDynBlocks", [](const std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& m, std::shared_ptr<DynBPFFilter> dynbpf, boost::optional<int> seconds, boost::optional<std::string> msg) {
- if (!dynbpf) {
- return;
- }
- setLuaSideEffect();
- struct timespec until, now;
- clock_gettime(CLOCK_MONOTONIC, &now);
- until = now;
- int actualSeconds = seconds ? *seconds : 10;
- until.tv_sec += actualSeconds;
- for (const auto& capair : m) {
- if (dynbpf->block(capair.first, until)) {
- warnlog("Inserting eBPF dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg ? *msg : "");
- }
- }
- });
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
-#endif /* DISABLE_DYNBLOCKS */
-
-#endif /* HAVE_EBPF */
-
- luaCtx.writeFunction<LuaAssociativeTable<uint64_t>()>("getStatisticsCounters", []() {
- setLuaNoSideEffect();
- std::unordered_map<string, uint64_t> res;
- for (const auto& entry : g_stats.entries) {
- if (const auto& val = boost::get<pdns::stat_t*>(&entry.second))
- res[entry.first] = (*val)->load();
- }
- return res;
- });
-
- luaCtx.writeFunction("includeDirectory", [&luaCtx](const std::string& dirname) {
- if (!checkConfigurationTime("includeDirectory")) {
- return;
- }
- if (g_included) {
- errlog("includeDirectory() cannot be used recursively!");
- g_outputBuffer = "includeDirectory() cannot be used recursively!\n";
- return;
- }
-
- struct stat st;
- if (stat(dirname.c_str(), &st)) {
- errlog("The included directory %s does not exist!", dirname.c_str());
- g_outputBuffer = "The included directory " + dirname + " does not exist!";
- return;
- }
-
- if (!S_ISDIR(st.st_mode)) {
- errlog("The included directory %s is not a directory!", dirname.c_str());
- g_outputBuffer = "The included directory " + dirname + " is not a directory!";
- return;
- }
-
- DIR* dirp;
- struct dirent* ent;
- std::vector<std::string> files;
- if (!(dirp = opendir(dirname.c_str()))) {
- errlog("Error opening the included directory %s!", dirname.c_str());
- g_outputBuffer = "Error opening the included directory " + dirname + "!";
- return;
- }
-
- while ((ent = readdir(dirp)) != NULL) {
- if (ent->d_name[0] == '.') {
- continue;
- }
-
- if (boost::ends_with(ent->d_name, ".conf")) {
- std::ostringstream namebuf;
- namebuf << dirname << "/" << ent->d_name;
-
- if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) {
- continue;
- }
-
- files.push_back(namebuf.str());
- }
- }
-
- closedir(dirp);
- std::sort(files.begin(), files.end());
-
- g_included = true;
-
- for (const auto& file : files) {
- std::ifstream ifs(file);
- if (!ifs) {
- warnlog("Unable to read configuration from '%s'", file);
- }
- else {
- vinfolog("Read configuration from '%s'", file);
- }
-
- try {
- luaCtx.executeCode(ifs);
- }
- catch (...) {
- g_included = false;
- throw;
- }
-
- luaCtx.executeCode(ifs);
- }
-
- g_included = false;
- });
-
- luaCtx.writeFunction("setAPIWritable", [](bool writable, boost::optional<std::string> apiConfigDir) {
- setLuaSideEffect();
- g_apiReadWrite = writable;
- if (apiConfigDir) {
- if (!(*apiConfigDir).empty()) {
- g_apiConfigDirectory = *apiConfigDir;
- }
- else {
- errlog("The API configuration directory value cannot be empty!");
- g_outputBuffer = "The API configuration directory value cannot be empty!";
- }
- }
- });
-
- luaCtx.writeFunction("setServFailWhenNoServer", [](bool servfail) {
- setLuaSideEffect();
- g_servFailOnNoPolicy = servfail;
- });
-
- luaCtx.writeFunction("setRoundRobinFailOnNoServer", [](bool fail) {
- setLuaSideEffect();
- g_roundrobinFailOnNoServer = fail;
- });
-
- luaCtx.writeFunction("setConsistentHashingBalancingFactor", [](double factor) {
- setLuaSideEffect();
- if (factor >= 1.0) {
- g_consistentHashBalancingFactor = factor;
- }
- else {
- errlog("Invalid value passed to setConsistentHashingBalancingFactor()!");
- g_outputBuffer = "Invalid value passed to setConsistentHashingBalancingFactor()!\n";
- return;
- }
- });
-
- luaCtx.writeFunction("setWeightedBalancingFactor", [](double factor) {
- setLuaSideEffect();
- if (factor >= 1.0) {
- g_weightedBalancingFactor = factor;
- }
- else {
- errlog("Invalid value passed to setWeightedBalancingFactor()!");
- g_outputBuffer = "Invalid value passed to setWeightedBalancingFactor()!\n";
- return;
- }
- });
-
- luaCtx.writeFunction("setRingBuffersSize", [client](uint64_t capacity, boost::optional<uint64_t> numberOfShards) {
- setLuaSideEffect();
- if (!checkConfigurationTime("setRingBuffersSize")) {
- return;
- }
- if (!client) {
- g_rings.setCapacity(capacity, numberOfShards ? *numberOfShards : 10);
- }
- else {
- g_rings.setCapacity(0, 1);
- }
- });
-
- luaCtx.writeFunction("setRingBuffersLockRetries", [](uint64_t retries) {
- setLuaSideEffect();
- g_rings.setNumberOfLockRetries(retries);
- });
-
- luaCtx.writeFunction("setRingBuffersOptions", [](const LuaAssociativeTable<boost::variant<bool, uint64_t>>& options) {
- setLuaSideEffect();
- if (!checkConfigurationTime("setRingBuffersOptions")) {
- return;
- }
- if (options.count("lockRetries") > 0) {
- auto retries = boost::get<uint64_t>(options.at("lockRetries"));
- g_rings.setNumberOfLockRetries(retries);
- }
- if (options.count("recordQueries") > 0) {
- auto record = boost::get<bool>(options.at("recordQueries"));
- g_rings.setRecordQueries(record);
- }
- if (options.count("recordResponses") > 0) {
- auto record = boost::get<bool>(options.at("recordResponses"));
- g_rings.setRecordResponses(record);
- }
- });
-
- luaCtx.writeFunction("setWHashedPertubation", [](uint64_t perturb) {
- setLuaSideEffect();
- checkParameterBound("setWHashedPertubation", perturb, std::numeric_limits<uint32_t>::max());
- g_hashperturb = perturb;
- });
-
- luaCtx.writeFunction("setTCPInternalPipeBufferSize", [](uint64_t size) { g_tcpInternalPipeBufferSize = size; });
- luaCtx.writeFunction("setTCPFastOpenKey", [](const std::string& keyString) {
- setLuaSideEffect();
- uint32_t key[4] = {};
- auto ret = sscanf(keyString.c_str(), "%" SCNx32 "-%" SCNx32 "-%" SCNx32 "-%" SCNx32, &key[0], &key[1], &key[2], &key[3]);
- if (ret != 4) {
- g_outputBuffer = "Invalid value passed to setTCPFastOpenKey()!\n";
- return;
- }
- extern vector<uint32_t> g_TCPFastOpenKey;
- for (const auto i : key) {
- g_TCPFastOpenKey.push_back(i);
- }
- });
-
-#ifdef HAVE_NET_SNMP
- luaCtx.writeFunction("snmpAgent", [client, configCheck](bool enableTraps, boost::optional<std::string> daemonSocket) {
- if (client || configCheck) {
- return;
- }
- if (!checkConfigurationTime("snmpAgent")) {
- return;
- }
- if (g_snmpEnabled) {
- errlog("snmpAgent() cannot be used twice!");
- g_outputBuffer = "snmpAgent() cannot be used twice!\n";
- return;
- }
-
- g_snmpEnabled = true;
- g_snmpTrapsEnabled = enableTraps;
- g_snmpAgent = new DNSDistSNMPAgent("dnsdist", daemonSocket ? *daemonSocket : std::string());
- });
-
- luaCtx.writeFunction("sendCustomTrap", [](const std::string& str) {
- if (g_snmpAgent && g_snmpTrapsEnabled) {
- g_snmpAgent->sendCustomTrap(str);
- }
- });
-#endif /* HAVE_NET_SNMP */
-
-#ifndef DISABLE_POLICIES_BINDINGS
- luaCtx.writeFunction("setServerPolicy", [](const ServerPolicy& policy) {
- setLuaSideEffect();
- g_policy.setState(policy);
- });
-
- luaCtx.writeFunction("setServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy) {
- setLuaSideEffect();
- g_policy.setState(ServerPolicy{name, policy, true});
- });
-
- luaCtx.writeFunction("setServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy) {
- setLuaSideEffect();
- auto pol = ServerPolicy(name, policy);
- g_policy.setState(std::move(pol));
- });
-
- luaCtx.writeFunction("setServerPolicyLuaFFIPerThread", [](const string& name, const std::string& policyCode) {
- setLuaSideEffect();
- auto pol = ServerPolicy(name, policyCode);
- g_policy.setState(std::move(pol));
- });
-
- luaCtx.writeFunction("showServerPolicy", []() {
- setLuaSideEffect();
- g_outputBuffer = g_policy.getLocal()->getName() + "\n";
- });
-
- luaCtx.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, const string& pool) {
- setLuaSideEffect();
- auto localPools = g_pools.getCopy();
- setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(policy));
- g_pools.setState(localPools);
- });
-
- luaCtx.writeFunction("setPoolServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy, const string& pool) {
- setLuaSideEffect();
- auto localPools = g_pools.getCopy();
- setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, policy, true}));
- g_pools.setState(localPools);
- });
-
- luaCtx.writeFunction("setPoolServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy, const string& pool) {
- setLuaSideEffect();
- auto localPools = g_pools.getCopy();
- setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, policy}));
- g_pools.setState(localPools);
- });
-
- luaCtx.writeFunction("setPoolServerPolicyLuaFFIPerThread", [](const string& name, const std::string& policyCode, const std::string& pool) {
- setLuaSideEffect();
- auto localPools = g_pools.getCopy();
- setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, policyCode}));
- g_pools.setState(localPools);
- });
-
- luaCtx.writeFunction("showPoolServerPolicy", [](const std::string& pool) {
- setLuaSideEffect();
- auto localPools = g_pools.getCopy();
- auto poolObj = getPool(localPools, pool);
- if (poolObj->policy == nullptr) {
- g_outputBuffer = g_policy.getLocal()->getName() + "\n";
- }
- else {
- g_outputBuffer = poolObj->policy->getName() + "\n";
- }
- });
-#endif /* DISABLE_POLICIES_BINDINGS */
-
- luaCtx.writeFunction("setTCPDownstreamCleanupInterval", [](uint64_t interval) {
- setLuaSideEffect();
- checkParameterBound("setTCPDownstreamCleanupInterval", interval);
- setTCPDownstreamCleanupInterval(interval);
- });
-
- luaCtx.writeFunction("setDoHDownstreamCleanupInterval", [](uint64_t interval) {
- setLuaSideEffect();
- checkParameterBound("setDoHDownstreamCleanupInterval", interval);
- setDoHDownstreamCleanupInterval(interval);
- });
-
- luaCtx.writeFunction("setTCPDownstreamMaxIdleTime", [](uint64_t max) {
- setLuaSideEffect();
- checkParameterBound("setTCPDownstreamMaxIdleTime", max);
- setTCPDownstreamMaxIdleTime(max);
- });
-
- luaCtx.writeFunction("setDoHDownstreamMaxIdleTime", [](uint64_t max) {
- setLuaSideEffect();
- checkParameterBound("setDoHDownstreamMaxIdleTime", max);
- setDoHDownstreamMaxIdleTime(max);
- });
-
- luaCtx.writeFunction("setConsoleConnectionsLogging", [](bool enabled) {
- g_logConsoleConnections = enabled;
- });
-
- luaCtx.writeFunction("setConsoleOutputMaxMsgSize", [](uint64_t size) {
- checkParameterBound("setConsoleOutputMaxMsgSize", size, std::numeric_limits<uint32_t>::max());
- g_consoleOutputMsgMaxSize = size;
- });
-
- luaCtx.writeFunction("setProxyProtocolACL", [](LuaTypeOrArrayOf<std::string> inp) {
- if (!checkConfigurationTime("setProxyProtocolACL")) {
- return;
- }
- setLuaSideEffect();
- NetmaskGroup nmg;
- if (auto str = boost::get<string>(&inp)) {
- nmg.addMask(*str);
- }
- else {
- for (const auto& p : boost::get<LuaArray<std::string>>(inp)) {
- nmg.addMask(p.second);
- }
- }
- g_proxyProtocolACL = std::move(nmg);
- });
-
- luaCtx.writeFunction("setProxyProtocolApplyACLToProxiedClients", [](bool apply) {
- if (!checkConfigurationTime("setProxyProtocolApplyACLToProxiedClients")) {
- return;
- }
- setLuaSideEffect();
- g_applyACLToProxiedClients = apply;
- });
-
- luaCtx.writeFunction("setProxyProtocolMaximumPayloadSize", [](uint64_t size) {
- if (!checkConfigurationTime("setProxyProtocolMaximumPayloadSize")) {
- return;
- }
- setLuaSideEffect();
- g_proxyProtocolMaximumSize = std::max(static_cast<uint64_t>(16), size);
- });
-
-#ifndef DISABLE_RECVMMSG
- luaCtx.writeFunction("setUDPMultipleMessagesVectorSize", [](uint64_t vSize) {
- if (!checkConfigurationTime("setUDPMultipleMessagesVectorSize")) {
- return;
- }
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
- setLuaSideEffect();
- g_udpVectorSize = vSize;
-#else
- errlog("recvmmsg() support is not available!");
- g_outputBuffer = "recvmmsg support is not available!\n";
-#endif
- });
-#endif /* DISABLE_RECVMMSG */
-
- luaCtx.writeFunction("setAddEDNSToSelfGeneratedResponses", [](bool add) {
- g_addEDNSToSelfGeneratedResponses = add;
- });
-
- luaCtx.writeFunction("setPayloadSizeOnSelfGeneratedAnswers", [](uint64_t payloadSize) {
- if (payloadSize < 512) {
- warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!");
- g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!";
- payloadSize = 512;
- }
- if (payloadSize > s_udpIncomingBufferSize) {
- warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to %d instead!", s_udpIncomingBufferSize);
- g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to " + std::to_string(s_udpIncomingBufferSize) + " instead";
- payloadSize = s_udpIncomingBufferSize;
- }
- g_PayloadSizeSelfGenAnswers = payloadSize;
- });
-
-#ifndef DISABLE_SECPOLL
- luaCtx.writeFunction("showSecurityStatus", []() {
- setLuaNoSideEffect();
- g_outputBuffer = std::to_string(g_stats.securityStatus) + "\n";
- });
-
- luaCtx.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) {
- if (!checkConfigurationTime("setSecurityPollSuffix")) {
- return;
- }
- g_secPollSuffix = suffix;
- });
-
- luaCtx.writeFunction("setSecurityPollInterval", [](time_t newInterval) {
- if (newInterval <= 0) {
- warnlog("setSecurityPollInterval() should be > 0, skipping");
- g_outputBuffer = "setSecurityPollInterval() should be > 0, skipping";
- }
-
- g_secPollInterval = newInterval;
- });
-#endif /* DISABLE_SECPOLL */
-
- luaCtx.writeFunction("setSyslogFacility", [](boost::variant<int, std::string> facility) {
- if (!checkConfigurationTime("setSyslogFacility")) {
- return;
- }
- setLuaSideEffect();
- if (facility.type() == typeid(std::string)) {
- static std::map<std::string, int> const facilities = {
- {"local0", LOG_LOCAL0},
- {"log_local0", LOG_LOCAL0},
- {"local1", LOG_LOCAL1},
- {"log_local1", LOG_LOCAL1},
- {"local2", LOG_LOCAL2},
- {"log_local2", LOG_LOCAL2},
- {"local3", LOG_LOCAL3},
- {"log_local3", LOG_LOCAL3},
- {"local4", LOG_LOCAL4},
- {"log_local4", LOG_LOCAL4},
- {"local5", LOG_LOCAL5},
- {"log_local5", LOG_LOCAL5},
- {"local6", LOG_LOCAL6},
- {"log_local6", LOG_LOCAL6},
- {"local7", LOG_LOCAL7},
- {"log_local7", LOG_LOCAL7},
- /* most of these likely make very little sense
- for dnsdist, but why not? */
- {"kern", LOG_KERN},
- {"log_kern", LOG_KERN},
- {"user", LOG_USER},
- {"log_user", LOG_USER},
- {"mail", LOG_MAIL},
- {"log_mail", LOG_MAIL},
- {"daemon", LOG_DAEMON},
- {"log_daemon", LOG_DAEMON},
- {"auth", LOG_AUTH},
- {"log_auth", LOG_AUTH},
- {"syslog", LOG_SYSLOG},
- {"log_syslog", LOG_SYSLOG},
- {"lpr", LOG_LPR},
- {"log_lpr", LOG_LPR},
- {"news", LOG_NEWS},
- {"log_news", LOG_NEWS},
- {"uucp", LOG_UUCP},
- {"log_uucp", LOG_UUCP},
- {"cron", LOG_CRON},
- {"log_cron", LOG_CRON},
- {"authpriv", LOG_AUTHPRIV},
- {"log_authpriv", LOG_AUTHPRIV},
- {"ftp", LOG_FTP},
- {"log_ftp", LOG_FTP}};
- auto facilityStr = boost::get<std::string>(facility);
- toLowerInPlace(facilityStr);
- auto it = facilities.find(facilityStr);
- if (it == facilities.end()) {
- g_outputBuffer = "Unknown facility '" + facilityStr + "' passed to setSyslogFacility()!\n";
- return;
- }
- setSyslogFacility(it->second);
- }
- else {
- setSyslogFacility(boost::get<int>(facility));
- }
- });
-
- typedef std::unordered_map<std::string, std::string> tlscertificateopts_t;
- luaCtx.writeFunction("newTLSCertificate", [client](const std::string& cert, boost::optional<tlscertificateopts_t> opts) {
- std::shared_ptr<TLSCertKeyPair> result = nullptr;
- if (client) {
- return result;
- }
-#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
- std::optional<std::string> key, password;
- if (opts) {
- if (opts->count("key")) {
- key = boost::get<const string>((*opts)["key"]);
- }
- if (opts->count("password")) {
- password = boost::get<const string>((*opts)["password"]);
- }
- }
- result = std::make_shared<TLSCertKeyPair>(TLSCertKeyPair{cert, key, password});
-#endif
- return result;
- });
-
- luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::optional<boost::variant<std::string, LuaArray<std::string>>> keyFiles, boost::optional<LuaTypeOrArrayOf<std::string>> urls, boost::optional<localbind_t> vars) {
- if (client) {
- return;
- }
-#ifdef HAVE_DNS_OVER_HTTPS
- if (!checkConfigurationTime("addDOHLocal")) {
- return;
- }
- setLuaSideEffect();
-
- auto frontend = std::make_shared<DOHFrontend>();
- if (certFiles && !certFiles->empty()) {
- if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) {
- return;
- }
-
- frontend->d_local = ComboAddress(addr, 443);
- }
- else {
- frontend->d_local = ComboAddress(addr, 80);
- infolog("No certificate provided for DoH endpoint %s, running in DNS over HTTP mode instead of DNS over HTTPS", frontend->d_local.toStringWithPort());
- }
-
- if (urls) {
- if (urls->type() == typeid(std::string)) {
- frontend->d_urls.push_back(boost::get<std::string>(*urls));
- }
- else if (urls->type() == typeid(LuaArray<std::string>)) {
- auto urlsVect = boost::get<LuaArray<std::string>>(*urls);
- for (const auto& p : urlsVect) {
- frontend->d_urls.push_back(p.second);
- }
- }
- }
- else {
- frontend->d_urls = {"/dns-query"};
- }
-
- bool reusePort = false;
- int tcpFastOpenQueueSize = 0;
- int tcpListenQueueSize = 0;
- uint64_t maxInFlightQueriesPerConn = 0;
- uint64_t tcpMaxConcurrentConnections = 0;
- std::string interface;
- std::set<int> cpus;
- std::vector<std::pair<ComboAddress, int>> additionalAddresses;
-
- if (vars) {
- parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
- getOptionalValue<int>(vars, "idleTimeout", frontend->d_idleTimeout);
- getOptionalValue<std::string>(vars, "serverTokens", frontend->d_serverTokens);
-
- LuaAssociativeTable<std::string> customResponseHeaders;
- if (getOptionalValue<decltype(customResponseHeaders)>(vars, "customResponseHeaders", customResponseHeaders) > 0) {
- for (auto const& headerMap : customResponseHeaders) {
- std::pair<std::string, std::string> headerResponse = std::make_pair(boost::to_lower_copy(headerMap.first), headerMap.second);
- frontend->d_customResponseHeaders.insert(headerResponse);
- }
- }
-
- getOptionalValue<bool>(vars, "sendCacheControlHeaders", frontend->d_sendCacheControlHeaders);
- getOptionalValue<bool>(vars, "keepIncomingHeaders", frontend->d_keepIncomingHeaders);
- getOptionalValue<bool>(vars, "trustForwardedForHeader", frontend->d_trustForwardedForHeader);
- getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
- getOptionalValue<bool>(vars, "exactPathMatching", frontend->d_exactPathMatching);
-
- LuaArray<std::string> addresses;
- if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
- for (const auto& [_, add] : addresses) {
- try {
- ComboAddress address(add);
- additionalAddresses.emplace_back(address, -1);
- }
- catch (const PDNSException& e) {
- errlog("Unable to parse additional address %s for DOH bind: %s", add, e.reason);
- return;
- }
- }
- }
-
- parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars);
-
- bool ignoreTLSConfigurationErrors = false;
- if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
- // we are asked to try to load the certificates so we can return a potential error
- // and properly ignore the frontend before actually launching it
- try {
- std::map<int, std::string> ocspResponses = {};
- auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
- }
- catch (const std::runtime_error& e) {
- errlog("Ignoring DoH frontend: '%s'", e.what());
- return;
- }
- }
-
- checkAllParametersConsumed("addDOHLocal", vars);
- }
- g_dohlocals.push_back(frontend);
- auto cs = std::make_unique<ClientState>(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
- cs->dohFrontend = frontend;
- cs->d_additionalAddresses = std::move(additionalAddresses);
-
- if (tcpListenQueueSize > 0) {
- cs->tcpListenQueueSize = tcpListenQueueSize;
- }
- if (tcpMaxConcurrentConnections > 0) {
- cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
- }
- g_frontends.push_back(std::move(cs));
-#else
- throw std::runtime_error("addDOHLocal() called but DNS over HTTPS support is not present!");
-#endif
- });
-
- luaCtx.writeFunction("showDOHFrontends", []() {
-#ifdef HAVE_DNS_OVER_HTTPS
- setLuaNoSideEffect();
- try {
- ostringstream ret;
- boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d");
- ret << (fmt % "#" % "Address" % "HTTP" % "HTTP/1" % "HTTP/2" % "GET" % "POST" % "Bad" % "Errors" % "Redirects" % "Valid" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
- size_t counter = 0;
- for (const auto& ctx : g_dohlocals) {
- ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_http2Stats.d_nbQueries % ctx->d_getqueries % ctx->d_postqueries % ctx->d_badrequests % ctx->d_errorresponses % ctx->d_redirectresponses % ctx->d_validresponses % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
- counter++;
- }
- g_outputBuffer = ret.str();
- }
- catch (const std::exception& e) {
- g_outputBuffer = e.what();
- throw;
- }
-#else
- g_outputBuffer = "DNS over HTTPS support is not present!\n";
-#endif
- });
-
- luaCtx.writeFunction("showDOHResponseCodes", []() {
-#ifdef HAVE_DNS_OVER_HTTPS
- setLuaNoSideEffect();
- try {
- ostringstream ret;
- boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d");
- g_outputBuffer = "\n- HTTP/1:\n\n";
- ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl;
- size_t counter = 0;
- for (const auto& ctx : g_dohlocals) {
- ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_http1Stats.d_nb200Responses % ctx->d_http1Stats.d_nb400Responses % ctx->d_http1Stats.d_nb403Responses % ctx->d_http1Stats.d_nb500Responses % ctx->d_http1Stats.d_nb502Responses % ctx->d_http1Stats.d_nbOtherResponses) << endl;
- counter++;
- }
- g_outputBuffer += ret.str();
- ret.str("");
-
- g_outputBuffer += "\n- HTTP/2:\n\n";
- ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl;
- counter = 0;
- for (const auto& ctx : g_dohlocals) {
- ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_http2Stats.d_nb200Responses % ctx->d_http2Stats.d_nb400Responses % ctx->d_http2Stats.d_nb403Responses % ctx->d_http2Stats.d_nb500Responses % ctx->d_http2Stats.d_nb502Responses % ctx->d_http2Stats.d_nbOtherResponses) << endl;
- counter++;
- }
- g_outputBuffer += ret.str();
- }
- catch (const std::exception& e) {
- g_outputBuffer = e.what();
- throw;
- }
-#else
- g_outputBuffer = "DNS over HTTPS support is not present!\n";
-#endif
- });
-
- luaCtx.writeFunction("getDOHFrontend", [client](uint64_t index) {
- std::shared_ptr<DOHFrontend> result = nullptr;
- if (client) {
- return result;
- }
-#ifdef HAVE_DNS_OVER_HTTPS
- setLuaNoSideEffect();
- try {
- if (index < g_dohlocals.size()) {
- result = g_dohlocals.at(index);
- }
- else {
- errlog("Error: trying to get DOH frontend with index %zu but we only have %zu frontend(s)\n", index, g_dohlocals.size());
- g_outputBuffer = "Error: trying to get DOH frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_dohlocals.size()) + " frontend(s)\n";
- }
- }
- catch (const std::exception& e) {
- g_outputBuffer = "Error while trying to get DOH frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
- errlog("Error while trying to get DOH frontend with index %zu: %s\n", index, string(e.what()));
- }
-#else
- g_outputBuffer="DNS over HTTPS support is not present!\n";
-#endif
- return result;
- });
-
- luaCtx.writeFunction("getDOHFrontendCount", []() {
- setLuaNoSideEffect();
- return g_dohlocals.size();
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("reloadCertificates", [](std::shared_ptr<DOHFrontend> frontend) {
- if (frontend != nullptr) {
- frontend->reloadCertificates();
- }
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<DOHFrontend> frontend, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles) {
-#ifdef HAVE_DNS_OVER_HTTPS
- if (frontend != nullptr) {
- if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
- frontend->reloadCertificates();
- }
- }
-#endif
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<DOHFrontend> frontend) {
- if (frontend != nullptr) {
- frontend->rotateTicketsKey(time(nullptr));
- }
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<DOHFrontend> frontend, const std::string& file) {
- if (frontend != nullptr) {
- frontend->loadTicketsKeys(file);
- }
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const LuaArray<std::shared_ptr<DOHResponseMapEntry>>&)>("setResponsesMap", [](std::shared_ptr<DOHFrontend> frontend, const LuaArray<std::shared_ptr<DOHResponseMapEntry>>& map) {
- if (frontend != nullptr) {
- auto newMap = std::make_shared<std::vector<std::shared_ptr<DOHResponseMapEntry>>>();
- newMap->reserve(map.size());
-
- for (const auto& entry : map) {
- newMap->push_back(entry.second);
- }
-
- frontend->d_responsesMap = std::move(newMap);
- }
- });
-
- luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, LuaTypeOrArrayOf<std::string> keyFiles, boost::optional<localbind_t> vars) {
- if (client) {
- return;
- }
-#ifdef HAVE_DNS_OVER_TLS
- if (!checkConfigurationTime("addTLSLocal")) {
- return;
- }
- setLuaSideEffect();
-
- shared_ptr<TLSFrontend> frontend = std::make_shared<TLSFrontend>();
- if (!loadTLSCertificateAndKeys("addTLSLocal", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
- return;
- }
-
- bool reusePort = false;
- int tcpFastOpenQueueSize = 0;
- int tcpListenQueueSize = 0;
- uint64_t maxInFlightQueriesPerConn = 0;
- uint64_t tcpMaxConcurrentConns = 0;
- std::string interface;
- std::set<int> cpus;
- std::vector<std::pair<ComboAddress, int>> additionalAddresses;
-
- if (vars) {
- parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConns);
-
- getOptionalValue<std::string>(vars, "provider", frontend->d_provider);
- boost::algorithm::to_lower(frontend->d_provider);
-
- LuaArray<std::string> addresses;
- if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
- for (const auto& [_, add] : addresses) {
- try {
- ComboAddress address(add);
- additionalAddresses.emplace_back(address, -1);
- }
- catch (const PDNSException& e) {
- errlog("Unable to parse additional address %s for DoT bind: %s", add, e.reason);
- return;
- }
- }
- }
-
- parseTLSConfig(frontend->d_tlsConfig, "addTLSLocal", vars);
-
- bool ignoreTLSConfigurationErrors = false;
- if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
- // we are asked to try to load the certificates so we can return a potential error
- // and properly ignore the frontend before actually launching it
- try {
- std::map<int, std::string> ocspResponses = {};
- auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
- }
- catch (const std::runtime_error& e) {
- errlog("Ignoring TLS frontend: '%s'", e.what());
- return;
- }
- }
-
- checkAllParametersConsumed("addTLSLocal", vars);
- }
-
- try {
- frontend->d_addr = ComboAddress(addr, 853);
- if (!frontend->d_provider.empty()) {
- vinfolog("Loading TLS provider '%s'", frontend->d_provider);
- }
- else {
-#ifdef HAVE_LIBSSL
- vinfolog("Loading default TLS provider 'openssl'");
-#else
- vinfolog("Loading default TLS provider 'gnutls'");
-#endif
- }
- // only works pre-startup, so no sync necessary
- auto cs = std::make_unique<ClientState>(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
- cs->tlsFrontend = frontend;
- cs->d_additionalAddresses = std::move(additionalAddresses);
- if (tcpListenQueueSize > 0) {
- cs->tcpListenQueueSize = tcpListenQueueSize;
- }
- if (maxInFlightQueriesPerConn > 0) {
- cs->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
- }
- if (tcpMaxConcurrentConns > 0) {
- cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConns;
- }
-
- g_tlslocals.push_back(cs->tlsFrontend);
- g_frontends.push_back(std::move(cs));
- }
- catch (const std::exception& e) {
- g_outputBuffer = "Error: " + string(e.what()) + "\n";
- }
-#else
- throw std::runtime_error("addTLSLocal() called but DNS over TLS support is not present!");
-#endif
- });
-
- luaCtx.writeFunction("showTLSContexts", []() {
-#ifdef HAVE_DNS_OVER_TLS
- setLuaNoSideEffect();
- try {
- ostringstream ret;
- boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-14d %|40t|%4$-14d %|54t|%5$-21.21s");
- // 1 2 3 4 5
- ret << (fmt % "#" % "Address" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
- size_t counter = 0;
- for (const auto& ctx : g_tlslocals) {
- ret << (fmt % counter % ctx->d_addr.toStringWithPort() % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
- counter++;
- }
- g_outputBuffer = ret.str();
- }
- catch (const std::exception& e) {
- g_outputBuffer = e.what();
- throw;
- }
-#else
- g_outputBuffer = "DNS over TLS support is not present!\n";
-#endif
- });
-
- luaCtx.writeFunction("getTLSContext", [](uint64_t index) {
- std::shared_ptr<TLSCtx> result = nullptr;
-#ifdef HAVE_DNS_OVER_TLS
- setLuaNoSideEffect();
- try {
- if (index < g_tlslocals.size()) {
- result = g_tlslocals.at(index)->getContext();
- }
- else {
- errlog("Error: trying to get TLS context with index %zu but we only have %zu context(s)\n", index, g_tlslocals.size());
- g_outputBuffer = "Error: trying to get TLS context with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " context(s)\n";
- }
- }
- catch (const std::exception& e) {
- g_outputBuffer = "Error while trying to get TLS context with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
- errlog("Error while trying to get TLS context with index %zu: %s\n", index, string(e.what()));
- }
-#else
- g_outputBuffer="DNS over TLS support is not present!\n";
-#endif
- return result;
- });
-
- luaCtx.writeFunction("getTLSFrontend", [](uint64_t index) {
- std::shared_ptr<TLSFrontend> result = nullptr;
-#ifdef HAVE_DNS_OVER_TLS
- setLuaNoSideEffect();
- try {
- if (index < g_tlslocals.size()) {
- result = g_tlslocals.at(index);
- }
- else {
- errlog("Error: trying to get TLS frontend with index %zu but we only have %zu frontends\n", index, g_tlslocals.size());
- g_outputBuffer = "Error: trying to get TLS frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " frontend(s)\n";
- }
- }
- catch (const std::exception& e) {
- g_outputBuffer = "Error while trying to get TLS frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
- errlog("Error while trying to get TLS frontend with index %zu: %s\n", index, string(e.what()));
- }
-#else
- g_outputBuffer="DNS over TLS support is not present!\n";
-#endif
- return result;
- });
-
- luaCtx.writeFunction("getTLSFrontendCount", []() {
- setLuaNoSideEffect();
- return g_tlslocals.size();
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<TLSCtx>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSCtx>& ctx) {
- if (ctx != nullptr) {
- ctx->rotateTicketsKey(time(nullptr));
- }
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<TLSCtx>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSCtx>& ctx, const std::string& file) {
- if (ctx != nullptr) {
- ctx->loadTicketsKeys(file);
- }
- });
-
- luaCtx.registerFunction<std::string (std::shared_ptr<TLSFrontend>::*)() const>("getAddressAndPort", [](const std::shared_ptr<TLSFrontend>& frontend) {
- if (frontend == nullptr) {
- return std::string();
- }
- return frontend->d_addr.toStringWithPort();
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSFrontend>& frontend) {
- if (frontend == nullptr) {
- return;
- }
- auto ctx = frontend->getContext();
- if (ctx) {
- ctx->rotateTicketsKey(time(nullptr));
- }
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSFrontend>& frontend, const std::string& file) {
- if (frontend == nullptr) {
- return;
- }
- auto ctx = frontend->getContext();
- if (ctx) {
- ctx->loadTicketsKeys(file);
- }
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("reloadCertificates", [](std::shared_ptr<TLSFrontend>& frontend) {
- if (frontend == nullptr) {
- return;
- }
- frontend->setupTLS();
- });
-
- luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, LuaTypeOrArrayOf<std::string> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<TLSFrontend>& frontend, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, LuaTypeOrArrayOf<std::string> keyFiles) {
-#ifdef HAVE_DNS_OVER_TLS
- if (loadTLSCertificateAndKeys("TLSFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
- frontend->setupTLS();
- }
-#endif
- });
-
- luaCtx.writeFunction("reloadAllCertificates", []() {
- for (auto& frontend : g_frontends) {
- if (!frontend) {
- continue;
- }
- try {
-#ifdef HAVE_DNSCRYPT
- if (frontend->dnscryptCtx) {
- frontend->dnscryptCtx->reloadCertificates();
- }
-#endif /* HAVE_DNSCRYPT */
-#ifdef HAVE_DNS_OVER_TLS
- if (frontend->tlsFrontend) {
- frontend->tlsFrontend->setupTLS();
- }
-#endif /* HAVE_DNS_OVER_TLS */
-#ifdef HAVE_DNS_OVER_HTTPS
- if (frontend->dohFrontend) {
- frontend->dohFrontend->reloadCertificates();
- }
-#endif /* HAVE_DNS_OVER_HTTPS */
- }
- catch (const std::exception& e) {
- errlog("Error reloading certificates for frontend %s: %s", frontend->local.toStringWithPort(), e.what());
- }
- }
- });
-
- luaCtx.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse = allow; });
- luaCtx.writeFunction("setDropEmptyQueries", [](bool drop) { extern bool g_dropEmptyQueries; g_dropEmptyQueries = drop; });
-
-#if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN) && !defined(DISABLE_OCSP_STAPLING)
- luaCtx.writeFunction("generateOCSPResponse", [client](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
- if (client) {
- return;
- }
-
- libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
- });
-#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN && !DISABLE_OCSP_STAPLING */
-
- luaCtx.writeFunction("addCapabilitiesToRetain", [](LuaTypeOrArrayOf<std::string> caps) {
- if (!checkConfigurationTime("addCapabilitiesToRetain")) {
- return;
- }
- setLuaSideEffect();
- if (caps.type() == typeid(std::string)) {
- g_capabilitiesToRetain.insert(boost::get<std::string>(caps));
- }
- else if (caps.type() == typeid(LuaArray<std::string>)) {
- for (const auto& cap : boost::get<LuaArray<std::string>>(caps)) {
- g_capabilitiesToRetain.insert(cap.second);
- }
- }
- });
-
- luaCtx.writeFunction("setUDPSocketBufferSizes", [client](uint64_t recv, uint64_t snd) {
- if (client) {
- return;
- }
- if (!checkConfigurationTime("setUDPSocketBufferSizes")) {
- return;
- }
- checkParameterBound("setUDPSocketBufferSizes", recv, std::numeric_limits<uint32_t>::max());
- checkParameterBound("setUDPSocketBufferSizes", snd, std::numeric_limits<uint32_t>::max());
- setLuaSideEffect();
-
- g_socketUDPSendBuffer = snd;
- g_socketUDPRecvBuffer = recv;
- });
-
- luaCtx.writeFunction("setRandomizedOutgoingSockets", [](bool randomized) {
- DownstreamState::s_randomizeSockets = randomized;
- });
-
- luaCtx.writeFunction("setRandomizedIdsOverUDP", [](bool randomized) {
- DownstreamState::s_randomizeIDs = randomized;
- });
-
-#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
- luaCtx.writeFunction("loadTLSEngine", [client](const std::string& engineName, boost::optional<std::string> defaultString) {
- if (client) {
- return;
- }
-
- auto [success, error] = libssl_load_engine(engineName, defaultString ? std::optional<std::string>(*defaultString) : std::nullopt);
- if (!success) {
- g_outputBuffer = "Error while trying to load TLS engine '" + engineName + "': " + error + "\n";
- errlog("Error while trying to load TLS engine '%s': %s", engineName, error);
- }
- });
-#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */
-
-#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
- luaCtx.writeFunction("loadTLSProvider", [client](const std::string& providerName) {
- if (client) {
- return;
- }
-
- auto [success, error] = libssl_load_provider(providerName);
- if (!success) {
- g_outputBuffer = "Error while trying to load TLS provider '" + providerName + "': " + error + "\n";
- errlog("Error while trying to load TLS provider '%s': %s", providerName, error);
- }
- });
-#endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */
-
- luaCtx.writeFunction("newThread", [client, configCheck](const std::string& code) {
- if (client || configCheck) {
- return;
- }
- std::thread newThread(LuaThread, code);
-
- newThread.detach();
- });
-
- luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional<std::string> customName) {
- if (!checkConfigurationTime("declareMetric")) {
- return false;
- }
- if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) {
- g_outputBuffer = "Unable to declare metric '" + name + "': invalid name\n";
- errlog("Unable to declare metric '%s': invalid name", name);
- return false;
- }
- if (type == "counter") {
- auto itp = g_stats.customCounters.emplace(name, 0);
- if (itp.second) {
- g_stats.entries.emplace_back(name, &g_stats.customCounters[name]);
- addMetricDefinition(name, "counter", description, customName ? *customName : "");
- }
- }
- else if (type == "gauge") {
- auto itp = g_stats.customGauges.emplace(name, 0.);
- if (itp.second) {
- g_stats.entries.emplace_back(name, &g_stats.customGauges[name]);
- addMetricDefinition(name, "gauge", description, customName ? *customName : "");
- }
- }
- else {
- g_outputBuffer = "declareMetric unknown type '" + type + "'\n";
- errlog("Unable to declareMetric '%s': no such type '%s'", name, type);
- return false;
- }
- return true;
- });
- luaCtx.writeFunction("incMetric", [](const std::string& name) {
- auto metric = g_stats.customCounters.find(name);
- if (metric != g_stats.customCounters.end()) {
- return ++(metric->second);
- }
- g_outputBuffer = "incMetric no such metric '" + name + "'\n";
- errlog("Unable to incMetric: no such name '%s'", name);
- return (uint64_t)0;
- });
- luaCtx.writeFunction("decMetric", [](const std::string& name) {
- auto metric = g_stats.customCounters.find(name);
- if (metric != g_stats.customCounters.end()) {
- return --(metric->second);
- }
- g_outputBuffer = "decMetric no such metric '" + name + "'\n";
- errlog("Unable to decMetric: no such name '%s'", name);
- return (uint64_t)0;
- });
- luaCtx.writeFunction("setMetric", [](const std::string& name, const double& value) {
- auto metric = g_stats.customGauges.find(name);
- if (metric != g_stats.customGauges.end()) {
- metric->second = value;
- return value;
- }
- g_outputBuffer = "setMetric no such metric '" + name + "'\n";
- errlog("Unable to setMetric: no such name '%s'", name);
- return 0.;
- });
- luaCtx.writeFunction("getMetric", [](const std::string& name) {
- auto counter = g_stats.customCounters.find(name);
- if (counter != g_stats.customCounters.end()) {
- return (double)counter->second.load();
- }
- else {
- auto gauge = g_stats.customGauges.find(name);
- if (gauge != g_stats.customGauges.end()) {
- return gauge->second.load();
- }
- }
- g_outputBuffer = "getMetric no such metric '" + name + "'\n";
- errlog("Unable to getMetric: no such name '%s'", name);
- return 0.;
- });
-}
-
-vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config)
-{
- // this needs to exist only during the parsing of the configuration
- // and cannot be captured by lambdas
- g_launchWork = std::vector<std::function<void(void)>>();
-
- setupLuaActions(luaCtx);
- setupLuaConfig(luaCtx, client, configCheck);
- setupLuaBindings(luaCtx, client);
- setupLuaBindingsDNSCrypt(luaCtx, client);
- setupLuaBindingsDNSParser(luaCtx);
- setupLuaBindingsDNSQuestion(luaCtx);
- setupLuaBindingsKVS(luaCtx, client);
- setupLuaBindingsNetwork(luaCtx, client);
- setupLuaBindingsPacketCache(luaCtx, client);
- setupLuaBindingsProtoBuf(luaCtx, client, configCheck);
- setupLuaBindingsRings(luaCtx, client);
- setupLuaInspection(luaCtx);
- setupLuaRules(luaCtx);
- setupLuaVars(luaCtx);
- setupLuaWeb(luaCtx);
-
-#ifdef LUAJIT_VERSION
- luaCtx.executeCode(getLuaFFIWrappers());
-#endif
-
- std::ifstream ifs(config);
- if (!ifs) {
- if (configCheck) {
- throw std::runtime_error("Unable to read configuration file from " + config);
- }
- else {
- warnlog("Unable to read configuration from '%s'", config);
- }
- }
- else {
- vinfolog("Read configuration from '%s'", config);
- }
-
- luaCtx.executeCode(ifs);
-
- auto ret = *g_launchWork;
- g_launchWork = boost::none;
- return ret;
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-#include "dolog.hh"
-#include "dnsdist.hh"
-#include "dnsparser.hh"
-#include <random>
-
-struct ResponseConfig
-{
- boost::optional<bool> setAA{boost::none};
- boost::optional<bool> setAD{boost::none};
- boost::optional<bool> setRA{boost::none};
- uint32_t ttl{60};
-};
-void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
-
-class SpoofAction : public DNSAction
-{
-public:
- SpoofAction(const vector<ComboAddress>& addrs): d_addrs(addrs)
- {
- for (const auto& addr : d_addrs) {
- if (addr.isIPv4()) {
- d_types.insert(QType::A);
- }
- else if (addr.isIPv6()) {
- d_types.insert(QType::AAAA);
- }
- }
-
- if (!d_addrs.empty()) {
- d_types.insert(QType::ANY);
- }
- }
-
- SpoofAction(const DNSName& cname): d_cname(cname)
- {
- }
-
- SpoofAction(const char* rawresponse, size_t len): d_raw(rawresponse, rawresponse + len)
- {
- }
-
- SpoofAction(const vector<std::string>& raws): d_rawResponses(raws)
- {
- }
-
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override;
-
- string toString() const override
- {
- string ret = "spoof in ";
- if (!d_cname.empty()) {
- ret += d_cname.toString() + " ";
- }
- if (d_rawResponses.size() > 0) {
- ret += "raw bytes ";
- }
- else {
- for(const auto& a : d_addrs)
- ret += a.toString()+" ";
- }
- return ret;
- }
-
-
- ResponseConfig d_responseConfig;
-private:
- static thread_local std::default_random_engine t_randomEngine;
- std::vector<ComboAddress> d_addrs;
- std::unordered_set<uint16_t> d_types;
- std::vector<std::string> d_rawResponses;
- PacketBuffer d_raw;
- DNSName d_cname;
-};
-
-class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
- LimitTTLResponseAction() {}
-
- LimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max(), const std::unordered_set<QType>& types = {}) : d_types(types), d_min(min), d_max(max)
- {
- }
-
- DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
- {
- auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
- if (!d_types.empty() && qclass == QClass::IN && d_types.count(qtype) == 0) {
- return ttl;
- }
-
- if (d_min > 0) {
- if (ttl < d_min) {
- ttl = d_min;
- }
- }
- if (ttl > d_max) {
- ttl = d_max;
- }
- return ttl;
- };
- editDNSPacketTTL(reinterpret_cast<char *>(dr->getMutableData().data()), dr->getData().size(), visitor);
- return DNSResponseAction::Action::None;
- }
-
- std::string toString() const override
- {
- std::string result = "limit ttl (" + std::to_string(d_min) + " <= ttl <= " + std::to_string(d_max);
- if (!d_types.empty()) {
- bool first = true;
- result += ", types in [";
- for (const auto& type : d_types) {
- if (first) {
- first = false;
- }
- else {
- result += " ";
- }
- result += type.toString();
- }
- result += "]";
- }
- result += + ")";
- return result;
- }
-
-private:
- std::unordered_set<QType> d_types;
- uint32_t d_min{0};
- uint32_t d_max{std::numeric_limits<uint32_t>::max()};
-};
-
-template <class T> using LuaArray = std::vector<std::pair<int, T>>;
-template <class T> using LuaAssociativeTable = std::unordered_map<std::string, T>;
-template <class T> using LuaTypeOrArrayOf = boost::variant<T, LuaArray<T>>;
-
-using luadnsrule_t = boost::variant<string, LuaArray<std::string>, std::shared_ptr<DNSRule>, DNSName, LuaArray<DNSName>>;
-using luaruleparams_t = LuaAssociativeTable<std::string>;
-using nmts_t = NetmaskTree<DynBlock, AddressAndPortRange>;
-
-std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var);
-void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder);
-void checkParameterBound(const std::string& parameter, uint64_t value, size_t max = std::numeric_limits<uint16_t>::max());
-
-vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config);
-void setupLuaActions(LuaContext& luaCtx);
-void setupLuaBindings(LuaContext& luaCtx, bool client);
-void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client);
-void setupLuaBindingsDNSParser(LuaContext& luaCtx);
-void setupLuaBindingsDNSQuestion(LuaContext& luaCtx);
-void setupLuaBindingsKVS(LuaContext& luaCtx, bool client);
-void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client);
-void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client);
-void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck);
-void setupLuaBindingsRings(LuaContext& luaCtx, bool client);
-void setupLuaRules(LuaContext& luaCtx);
-void setupLuaInspection(LuaContext& luaCtx);
-void setupLuaVars(LuaContext& luaCtx);
-void setupLuaWeb(LuaContext& luaCtx);
-void setupLuaLoadBalancingContext(LuaContext& luaCtx);
-
-/**
- * getOptionalValue(vars, key, value)
- *
- * Attempts to extract value for key in vars.
- * Erases the key from vars.
- *
- * returns: -1 if type wasn't compatible, 0 if not found or number of element(s) found
- */
-template<class G, class T, class V>
-static inline int getOptionalValue(boost::optional<V>& vars, const std::string& key, T& value, bool warnOnWrongType = true) {
- /* nothing found, nothing to return */
- if (!vars) {
- return 0;
- }
-
- if (vars->count(key)) {
- try {
- value = boost::get<G>((*vars)[key]);
- } catch (const boost::bad_get& e) {
- /* key is there but isn't compatible */
- if (warnOnWrongType) {
- warnlog("Invalid type for key '%s' - ignored", key);
- vars->erase(key);
- }
- return -1;
- }
- }
- return vars->erase(key);
-}
-
-template<class T, class V>
-static inline int getOptionalIntegerValue(const std::string& func, boost::optional<V>& vars, const std::string& key, T& value) {
- std::string valueStr;
- auto ret = getOptionalValue<std::string>(vars, key, valueStr, true);
- if (ret == 1) {
- try {
- value = std::stoi(valueStr);
- }
- catch (const std::exception& e) {
- warnlog("Parameter '%s' of '%s' must be integer, not '%s' - ignoring", func, key, valueStr);
- return -1;
- }
- }
- return ret;
-}
-
-template<class V>
-static inline void checkAllParametersConsumed(const std::string& func, const boost::optional<V>& vars) {
- /* no vars */
- if (!vars) {
- return;
- }
- for (const auto& [key, value] : *vars) {
- warnlog("%s: Unknown key '%s' given - ignored", func, key);
- }
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include "config.h"
-
-#ifndef DISABLE_PROTOBUF
-#include "base64.hh"
-#include "dnsdist.hh"
-#include "dnsdist-protobuf.hh"
-#include "protozero.hh"
-
-DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSQuestion& dq): d_dq(dq), d_type(pdns::ProtoZero::Message::MessageType::DNSQueryType)
-{
-}
-
-DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSResponse& dr, bool includeCNAME): d_dq(dr), d_dr(&dr), d_type(pdns::ProtoZero::Message::MessageType::DNSResponseType), d_includeCNAME(includeCNAME)
-{
-}
-
-void DNSDistProtoBufMessage::setServerIdentity(const std::string& serverId)
-{
- d_serverIdentity = serverId;
-}
-
-void DNSDistProtoBufMessage::setRequestor(const ComboAddress& requestor)
-{
- d_requestor = requestor;
-}
-
-void DNSDistProtoBufMessage::setResponder(const ComboAddress& responder)
-{
- d_responder = responder;
-}
-
-void DNSDistProtoBufMessage::setRequestorPort(uint16_t port)
-{
- if (d_requestor) {
- d_requestor->setPort(port);
- }
-}
-
-void DNSDistProtoBufMessage::setResponderPort(uint16_t port)
-{
- if (d_responder) {
- d_responder->setPort(port);
- }
-}
-
-void DNSDistProtoBufMessage::setResponseCode(uint8_t rcode)
-{
- d_rcode = rcode;
-}
-
-void DNSDistProtoBufMessage::setType(pdns::ProtoZero::Message::MessageType type)
-{
- d_type = type;
-}
-
-void DNSDistProtoBufMessage::setBytes(size_t bytes)
-{
- d_bytes = bytes;
-}
-
-void DNSDistProtoBufMessage::setTime(time_t sec, uint32_t usec)
-{
- d_time = std::pair(sec, usec);
-}
-
-void DNSDistProtoBufMessage::setQueryTime(time_t sec, uint32_t usec)
-{
- d_queryTime = std::pair(sec, usec);
-}
-
-void DNSDistProtoBufMessage::setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass)
-{
- d_question = DNSDistProtoBufMessage::PBQuestion(name, qtype, qclass);
-}
-
-void DNSDistProtoBufMessage::setEDNSSubnet(const Netmask& nm)
-{
- d_ednsSubnet = nm;
-}
-
-void DNSDistProtoBufMessage::addTag(const std::string& strValue)
-{
- d_additionalTags.push_back(strValue);
-}
-
-void DNSDistProtoBufMessage::addMeta(const std::string& key, std::vector<std::string>&& values)
-{
- auto& entry = d_metaTags[key];
- for (auto& value : values) {
- entry.insert(std::move(value));
- }
-}
-
-void DNSDistProtoBufMessage::addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)
-{
- d_additionalRRs.push_back({std::move(qname), strBlob, uTTL, uType, uClass});
-}
-
-void DNSDistProtoBufMessage::serialize(std::string& data) const
-{
- if ((data.capacity() - data.size()) < 128) {
- data.reserve(data.size() + 128);
- }
- pdns::ProtoZero::Message m{data};
-
- m.setType(d_type);
-
- if (d_time) {
- m.setTime(d_time->first, d_time->second);
- }
- else {
- struct timespec ts;
- gettime(&ts, true);
- m.setTime(ts.tv_sec, ts.tv_nsec / 1000);
- }
-
- const auto distProto = d_dq.getProtocol();
- pdns::ProtoZero::Message::TransportProtocol protocol = pdns::ProtoZero::Message::TransportProtocol::UDP;
-
- if (distProto == dnsdist::Protocol::DoTCP) {
- protocol = pdns::ProtoZero::Message::TransportProtocol::TCP;
- }
- else if (distProto == dnsdist::Protocol::DoT) {
- protocol = pdns::ProtoZero::Message::TransportProtocol::DoT;
- }
- else if (distProto == dnsdist::Protocol::DoH) {
- protocol = pdns::ProtoZero::Message::TransportProtocol::DoH;
- }
- else if (distProto == dnsdist::Protocol::DNSCryptUDP) {
- protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptUDP;
- }
- else if (distProto == dnsdist::Protocol::DNSCryptTCP) {
- protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptTCP;
- }
-
- m.setRequest(d_dq.ids.d_protoBufData && d_dq.ids.d_protoBufData->uniqueId ? *d_dq.ids.d_protoBufData->uniqueId : getUniqueID(), d_requestor ? *d_requestor : d_dq.ids.origRemote, d_responder ? *d_responder : d_dq.ids.origDest, d_question ? d_question->d_name : d_dq.ids.qname, d_question ? d_question->d_type : d_dq.ids.qtype, d_question ? d_question->d_class : d_dq.ids.qclass, d_dq.getHeader()->id, protocol, d_bytes ? *d_bytes : d_dq.getData().size());
-
- if (d_serverIdentity) {
- m.setServerIdentity(*d_serverIdentity);
- }
- else if (d_ServerIdentityRef != nullptr) {
- m.setServerIdentity(*d_ServerIdentityRef);
- }
-
- if (d_ednsSubnet) {
- m.setEDNSSubnet(*d_ednsSubnet, 128);
- }
-
- m.startResponse();
- if (d_queryTime) {
- // coverity[store_truncates_time_t]
- m.setQueryTime(d_queryTime->first, d_queryTime->second);
- }
- else {
- m.setQueryTime(d_dq.getQueryRealTime().tv_sec, d_dq.getQueryRealTime().tv_nsec / 1000);
- }
-
- if (d_dr != nullptr) {
- m.setResponseCode(d_rcode ? *d_rcode : d_dr->getHeader()->rcode);
- m.addRRsFromPacket(reinterpret_cast<const char*>(d_dr->getData().data()), d_dr->getData().size(), d_includeCNAME);
- }
- else {
- if (d_rcode) {
- m.setResponseCode(*d_rcode);
- }
- }
-
- for (const auto& rr : d_additionalRRs) {
- m.addRR(rr.d_name, rr.d_type, rr.d_class, rr.d_ttl, rr.d_data);
- }
-
- for (const auto& tag : d_additionalTags) {
- m.addPolicyTag(tag);
- }
-
- m.commitResponse();
-
- if (d_dq.ids.d_protoBufData) {
- const auto& pbData = d_dq.ids.d_protoBufData;
- if (!pbData->d_deviceName.empty()) {
- m.setDeviceName(pbData->d_deviceName);
- }
- if (!pbData->d_deviceID.empty()) {
- m.setDeviceId(pbData->d_deviceID);
- }
- if (!pbData->d_requestorID.empty()) {
- m.setRequestorId(pbData->d_requestorID);
- }
- }
-
- for (const auto& [key, values] : d_metaTags) {
- if (!values.empty()) {
- m.setMeta(key, values, {});
- }
- else {
- /* the MetaValue field is _required_ to exist, even if we have no value */
- m.setMeta(key, {std::string()}, {});
- }
- }
-}
-
-ProtoBufMetaKey::ProtoBufMetaKey(const std::string& key)
-{
- auto& idx = s_types.get<NameTag>();
- auto it = idx.find(key);
- if (it != idx.end()) {
- d_type = it->d_type;
- return;
- }
- else {
- auto [prefix, variable] = splitField(key, ':');
- if (!variable.empty()) {
- it = idx.find(prefix);
- if (it != idx.end() && it->d_prefix) {
- d_type = it->d_type;
- if (it->d_numeric) {
- try {
- d_numericSubKey = std::stoi(variable);
- }
- catch (const std::exception& e) {
- throw std::runtime_error("Unable to parse numeric ProtoBuf key '" + key + "'");
- }
- }
- else {
- if (!it->d_caseSensitive) {
- boost::algorithm::to_lower(variable);
- }
- d_subKey = variable;
- }
- return;
- }
- }
- }
- throw std::runtime_error("Invalid ProtoBuf key '" + key + "'");
-}
-
-std::vector<std::string> ProtoBufMetaKey::getValues(const DNSQuestion& dq) const
-{
- auto& idx = s_types.get<TypeTag>();
- auto it = idx.find(d_type);
- if (it == idx.end()) {
- throw std::runtime_error("Trying to get the values of an unsupported type: " + std::to_string(static_cast<uint8_t>(d_type)));
- }
- return (it->d_func)(dq, d_subKey, d_numericSubKey);
-}
-
-const std::string& ProtoBufMetaKey::getName() const
-{
- auto& idx = s_types.get<TypeTag>();
- auto it = idx.find(d_type);
- if (it == idx.end()) {
- throw std::runtime_error("Trying to get the name of an unsupported type: " + std::to_string(static_cast<uint8_t>(d_type)));
- }
- return it->d_name;
-}
-
-const ProtoBufMetaKey::TypeContainer ProtoBufMetaKey::s_types = {
- ProtoBufMetaKey::KeyTypeDescription{ "sni", Type::SNI, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> { return {dq.sni}; }, false },
- ProtoBufMetaKey::KeyTypeDescription{ "pool", Type::Pool, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> { return {dq.ids.poolName}; }, false },
- ProtoBufMetaKey::KeyTypeDescription{ "b64-content", Type::B64Content, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> { const auto& data = dq.getData(); return {Base64Encode(std::string(data.begin(), data.end()))}; }, false },
-#ifdef HAVE_DNS_OVER_HTTPS
- ProtoBufMetaKey::KeyTypeDescription{ "doh-header", Type::DoHHeader, [](const DNSQuestion& dq , const std::string& name, uint8_t) -> std::vector<std::string> {
- if (!dq.ids.du) {
- return {};
- }
- auto headers = dq.ids.du->getHTTPHeaders();
- auto it = headers.find(name);
- if (it != headers.end()) {
- return {it->second};
- }
- return {};
- }, true, false },
- ProtoBufMetaKey::KeyTypeDescription{ "doh-host", Type::DoHHost, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
- if (dq.ids.du) {
- return {dq.ids.du->getHTTPHost()};
- }
- return {};
- }, true, false },
- ProtoBufMetaKey::KeyTypeDescription{ "doh-path", Type::DoHPath, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
- if (dq.ids.du) {
- return {dq.ids.du->getHTTPPath()};
- }
- return {};
- }, false },
- ProtoBufMetaKey::KeyTypeDescription{ "doh-query-string", Type::DoHQueryString, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
- if (dq.ids.du) {
- return {dq.ids.du->getHTTPQueryString()};
- }
- return {};
- }, false },
- ProtoBufMetaKey::KeyTypeDescription{ "doh-scheme", Type::DoHScheme, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
- if (dq.ids.du) {
- return {dq.ids.du->getHTTPScheme()};
- }
- return {};
- }, false, false },
-#endif // HAVE_DNS_OVER_HTTPS
- ProtoBufMetaKey::KeyTypeDescription{ "proxy-protocol-value", Type::ProxyProtocolValue, [](const DNSQuestion& dq, const std::string&, uint8_t numericSubKey) -> std::vector<std::string> {
- if (!dq.proxyProtocolValues) {
- return {};
- }
- for (const auto& value : *dq.proxyProtocolValues) {
- if (value.type == numericSubKey) {
- return {value.content};
- }
- }
- return {};
- }, true, false, true },
- ProtoBufMetaKey::KeyTypeDescription{ "proxy-protocol-values", Type::ProxyProtocolValues, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
- std::vector<std::string> result;
- if (!dq.proxyProtocolValues) {
- return result;
- }
- for (const auto& value : *dq.proxyProtocolValues) {
- result.push_back(std::to_string(value.type) + ":" + value.content);
- }
- return result;
- } },
- ProtoBufMetaKey::KeyTypeDescription{ "tag", Type::Tag, [](const DNSQuestion& dq, const std::string& subKey, uint8_t) -> std::vector<std::string> {
- if (!dq.ids.qTag) {
- return {};
- }
- for (const auto& [key, value] : *dq.ids.qTag) {
- if (key == subKey) {
- return {value};
- }
- }
- return {};
- }, true, true },
- ProtoBufMetaKey::KeyTypeDescription{ "tags", Type::Tags, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
- std::vector<std::string> result;
- if (!dq.ids.qTag) {
- return result;
- }
- for (const auto& [key, value] : *dq.ids.qTag) {
- if (value.empty()) {
- /* avoids a spurious ':' when the value is empty */
- result.push_back(key);
- }
- else {
- result.push_back(key + ":" + value);
- }
- }
- return result;
- } },
-};
-
-#endif /* DISABLE_PROTOBUF */
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-#include "dnsdist.hh"
-#include "dnsname.hh"
-
-#ifndef DISABLE_PROTOBUF
-#include "protozero.hh"
-
-class DNSDistProtoBufMessage
-{
-public:
- DNSDistProtoBufMessage(const DNSQuestion& dq);
- DNSDistProtoBufMessage(const DNSResponse& dr, bool includeCNAME);
-
- void setServerIdentity(const std::string& serverId);
- void setRequestor(const ComboAddress& requestor);
- void setResponder(const ComboAddress& responder);
- void setRequestorPort(uint16_t port);
- void setResponderPort(uint16_t port);
- void setResponseCode(uint8_t rcode);
- void setType(pdns::ProtoZero::Message::MessageType type);
- void setBytes(size_t bytes);
- void setTime(time_t sec, uint32_t usec);
- void setQueryTime(time_t sec, uint32_t usec);
- void setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass);
- void setEDNSSubnet(const Netmask& nm);
-
- void addTag(const std::string& strValue);
- void addMeta(const std::string& key, std::vector<std::string>&& values);
- void addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& data);
-
- void serialize(std::string& data) const;
-
- std::string toDebugString() const;
-
-private:
- struct PBRecord
- {
- DNSName d_name;
- std::string d_data;
- uint32_t d_ttl;
- uint16_t d_type;
- uint16_t d_class;
- };
- struct PBQuestion
- {
- PBQuestion(const DNSName& name, uint16_t type, uint16_t class_): d_name(name), d_type(type), d_class(class_)
- {
- }
-
- DNSName d_name;
- uint16_t d_type;
- uint16_t d_class;
- };
-
- std::vector<PBRecord> d_additionalRRs;
- std::vector<std::string> d_additionalTags;
- std::unordered_map<std::string, std::unordered_set<std::string>> d_metaTags;
-
- const DNSQuestion& d_dq;
- const DNSResponse* d_dr{nullptr};
- const std::string* d_ServerIdentityRef{nullptr};
-
- boost::optional<PBQuestion> d_question{boost::none};
- boost::optional<std::string> d_serverIdentity{boost::none};
- boost::optional<ComboAddress> d_requestor{boost::none};
- boost::optional<ComboAddress> d_responder{boost::none};
- boost::optional<Netmask> d_ednsSubnet{boost::none};
- boost::optional<std::pair<time_t, uint32_t>> d_time{boost::none};
- boost::optional<std::pair<time_t, uint32_t>> d_queryTime{boost::none};
- boost::optional<size_t> d_bytes{boost::none};
- boost::optional<uint8_t> d_rcode{boost::none};
-
- pdns::ProtoZero::Message::MessageType d_type{pdns::ProtoZero::Message::MessageType::DNSQueryType};
- bool d_includeCNAME{false};
-};
-
-class ProtoBufMetaKey
-{
- enum class Type : uint8_t { SNI, Pool, B64Content, DoHHeader, DoHHost, DoHPath, DoHQueryString, DoHScheme, ProxyProtocolValue, ProxyProtocolValues, Tag, Tags };
-
- struct KeyTypeDescription
- {
- const std::string d_name;
- const Type d_type;
- const std::function<std::vector<std::string>(const DNSQuestion&, const std::string&, uint8_t)> d_func;
- bool d_prefix{false};
- bool d_caseSensitive{true};
- bool d_numeric{false};
- };
-
- struct NameTag {};
- struct TypeTag {};
-
- typedef boost::multi_index_container<
- KeyTypeDescription,
- indexed_by <
- hashed_unique<tag<NameTag>, member<KeyTypeDescription, const std::string, &KeyTypeDescription::d_name>>,
- hashed_unique<tag<TypeTag>, member<KeyTypeDescription, const Type, &KeyTypeDescription::d_type>>
- >
- > TypeContainer;
-
- static const TypeContainer s_types;
-
-public:
- ProtoBufMetaKey(const std::string& key);
-
- const std::string& getName() const;
- std::vector<std::string> getValues(const DNSQuestion& dq) const;
-private:
- std::string d_subKey;
- uint8_t d_numericSubKey{0};
- Type d_type;
-};
-
-#endif /* DISABLE_PROTOBUF */
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <algorithm>
-#include <stdexcept>
-
-#include "dnsdist-protocols.hh"
-
-namespace dnsdist
-{
-const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_names = {
- "DoUDP",
- "DoTCP",
- "DNSCryptUDP",
- "DNSCryptTCP",
- "DoT",
- "DoH"};
-
-const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_prettyNames = {
- "Do53 UDP",
- "Do53 TCP",
- "DNSCrypt UDP",
- "DNSCrypt TCP",
- "DNS over TLS",
- "DNS over HTTPS"};
-
-Protocol::Protocol(const std::string& s)
-{
- const auto& it = std::find(s_names.begin(), s_names.end(), s);
- if (it == s_names.end()) {
- throw std::runtime_error("Unknown protocol name: '" + s + "'");
- }
-
- auto index = std::distance(s_names.begin(), it);
- d_protocol = static_cast<Protocol::typeenum>(index);
-}
-
-bool Protocol::operator==(Protocol::typeenum type) const
-{
- return d_protocol == type;
-}
-
-bool Protocol::operator!=(Protocol::typeenum type) const
-{
- return d_protocol != type;
-}
-
-const std::string& Protocol::toString() const
-{
- return s_names.at(static_cast<uint8_t>(d_protocol));
-}
-
-const std::string& Protocol::toPrettyString() const
-{
- return s_prettyNames.at(static_cast<uint8_t>(d_protocol));
-}
-
-bool Protocol::isUDP() const
-{
- return d_protocol == DoUDP || d_protocol == DNSCryptUDP;
-}
-
-uint8_t Protocol::toNumber() const
-{
- return static_cast<uint8_t>(d_protocol);
-}
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <fstream>
-
-#include "dnsdist-rings.hh"
-
-void Rings::setCapacity(size_t newCapacity, size_t numberOfShards)
-{
- if (d_initialized) {
- throw std::runtime_error("Rings::setCapacity() should not be called once the rings have been initialized");
- }
- d_capacity = newCapacity;
- d_numberOfShards = numberOfShards;
-}
-
-void Rings::init()
-{
- if (d_initialized.exchange(true)) {
- throw std::runtime_error("Rings::init() should only be called once");
- }
-
- if (d_numberOfShards <= 1) {
- d_nbLockTries = 0;
- }
-
- d_shards.resize(d_numberOfShards);
-
- /* resize all the rings */
- for (auto& shard : d_shards) {
- shard = std::make_unique<Shard>();
- if (shouldRecordQueries()) {
- shard->queryRing.lock()->set_capacity(d_capacity / d_numberOfShards);
- }
- if (shouldRecordResponses()) {
- shard->respRing.lock()->set_capacity(d_capacity / d_numberOfShards);
- }
- }
-
- /* we just recreated the shards so they are now empty */
- d_nbQueryEntries = 0;
- d_nbResponseEntries = 0;
-}
-
-void Rings::setNumberOfLockRetries(size_t retries)
-{
- if (d_numberOfShards <= 1) {
- d_nbLockTries = 0;
- } else {
- d_nbLockTries = retries;
- }
-}
-
-void Rings::setRecordQueries(bool record)
-{
- d_recordQueries = record;
-}
-
-void Rings::setRecordResponses(bool record)
-{
- d_recordResponses = record;
-}
-
-size_t Rings::numDistinctRequestors()
-{
- std::set<ComboAddress, ComboAddress::addressOnlyLessThan> s;
- for (const auto& shard : d_shards) {
- auto rl = shard->queryRing.lock();
- for (const auto& q : *rl) {
- s.insert(q.requestor);
- }
- }
- return s.size();
-}
-
-std::unordered_map<int, vector<boost::variant<string,double>>> Rings::getTopBandwidth(unsigned int numentries)
-{
- map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts;
- uint64_t total=0;
- for (const auto& shard : d_shards) {
- {
- auto rl = shard->queryRing.lock();
- for(const auto& q : *rl) {
- counts[q.requestor] += q.size;
- total+=q.size;
- }
- }
- {
- auto rl = shard->respRing.lock();
- for(const auto& r : *rl) {
- counts[r.requestor] += r.size;
- total+=r.size;
- }
- }
- }
-
- typedef vector<pair<unsigned int, ComboAddress>> ret_t;
- ret_t rcounts;
- rcounts.reserve(counts.size());
- for(const auto& p : counts)
- rcounts.push_back({p.second, p.first});
- numentries = rcounts.size() < numentries ? rcounts.size() : numentries;
- partial_sort(rcounts.begin(), rcounts.begin()+numentries, rcounts.end(), [](const ret_t::value_type&a, const ret_t::value_type&b)
- {
- return(b.first < a.first);
- });
- std::unordered_map<int, vector<boost::variant<string,double>>> ret;
- uint64_t rest = 0;
- int count = 1;
- for(const auto& rc : rcounts) {
- if (count == static_cast<int>(numentries + 1)) {
- rest+=rc.first;
- }
- else {
- ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}});
- }
- }
-
- if (total > 0) {
- ret.insert({count, {"Rest", rest, 100.0*rest/total}});
- }
- else {
- ret.insert({count, {"Rest", rest, 100.0 }});
- }
-
- return ret;
-}
-
-size_t Rings::loadFromFile(const std::string& filepath, const struct timespec& now)
-{
- ifstream ifs(filepath);
- if (!ifs) {
- throw std::runtime_error("unable to open the file at " + filepath);
- }
-
- size_t inserted = 0;
- string line;
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
-
- while (std::getline(ifs, line)) {
- boost::trim_right_if(line, boost::is_any_of(" \r\n\x1a"));
- boost::trim_left(line);
- bool isResponse = false;
- vector<string> parts;
- stringtok(parts, line, " \t,");
-
- if (parts.size() == 8) {
- }
- else if (parts.size() >= 11 && parts.size() <= 13) {
- isResponse = true;
- }
- else {
- cerr<<"skipping line with "<<parts.size()<<"parts: "<<line<<endl;
- continue;
- }
-
- size_t idx = 0;
- vector<string> timeStr;
- stringtok(timeStr, parts.at(idx++), ".");
- if (timeStr.size() != 2) {
- cerr<<"skipping invalid time "<<parts.at(0)<<endl;
- continue;
- }
-
- struct timespec when;
- try {
- when.tv_sec = now.tv_sec + std::stoi(timeStr.at(0));
- when.tv_nsec = now.tv_nsec + std::stoi(timeStr.at(1)) * 100 * 1000 * 1000;
- }
- catch (const std::exception& e) {
- cerr<<"error parsing time "<<parts.at(idx-1)<<" from line "<<line<<endl;
- continue;
- }
-
- ComboAddress from(parts.at(idx++));
- ComboAddress to;
- dnsdist::Protocol protocol(parts.at(idx++));
- if (isResponse) {
- to = ComboAddress(parts.at(idx++));
- }
- /* skip ID */
- idx++;
- DNSName qname(parts.at(idx++));
- QType qtype(QType::chartocode(parts.at(idx++).c_str()));
-
- if (isResponse) {
- insertResponse(when, from, qname, qtype.getCode(), 0, 0, dh, to, protocol);
- }
- else {
- insertQuery(when, from, qname, qtype.getCode(), 0, dh, protocol);
- }
- ++inserted;
- }
-
- return inserted;
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-
-#include <time.h>
-#include <unordered_map>
-
-#include <boost/variant.hpp>
-
-#include "circular_buffer.hh"
-#include "dnsname.hh"
-#include "iputils.hh"
-#include "lock.hh"
-#include "stat_t.hh"
-#include "dnsdist-protocols.hh"
-#include "dnsdist-mac-address.hh"
-
-struct Rings {
- struct Query
- {
- ComboAddress requestor;
- DNSName name;
- struct timespec when;
- struct dnsheader dh;
- uint16_t size;
- uint16_t qtype;
- // incoming protocol
- dnsdist::Protocol protocol;
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
- dnsdist::MacAddress macaddress;
- bool hasmac{false};
-#endif
- };
- struct Response
- {
- ComboAddress requestor;
- ComboAddress ds; // who handled it
- DNSName name;
- struct timespec when;
- struct dnsheader dh;
- unsigned int usec;
- unsigned int size;
- uint16_t qtype;
- // outgoing protocol
- dnsdist::Protocol protocol;
- };
-
- struct Shard
- {
- LockGuarded<boost::circular_buffer<Query>> queryRing;
- LockGuarded<boost::circular_buffer<Response>> respRing;
- };
-
- Rings(size_t capacity=10000, size_t numberOfShards=10, size_t nbLockTries=5, bool keepLockingStats=false): d_blockingQueryInserts(0), d_blockingResponseInserts(0), d_deferredQueryInserts(0), d_deferredResponseInserts(0), d_nbQueryEntries(0), d_nbResponseEntries(0), d_currentShardId(0), d_capacity(capacity), d_numberOfShards(numberOfShards), d_nbLockTries(nbLockTries), d_keepLockingStats(keepLockingStats)
- {
- }
-
- std::unordered_map<int, vector<boost::variant<string,double> > > getTopBandwidth(unsigned int numentries);
- size_t numDistinctRequestors();
- /* this function should not be called after init() has been called */
- void setCapacity(size_t newCapacity, size_t numberOfShards);
-
- /* This function should only be called at configuration time before any query or response has been inserted */
- void init();
-
- void setNumberOfLockRetries(size_t retries);
- void setRecordQueries(bool);
- void setRecordResponses(bool);
-
- size_t getNumberOfShards() const
- {
- return d_numberOfShards;
- }
-
- size_t getNumberOfQueryEntries() const
- {
- return d_nbQueryEntries;
- }
-
- size_t getNumberOfResponseEntries() const
- {
- return d_nbResponseEntries;
- }
-
- void insertQuery(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
- {
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
- dnsdist::MacAddress macaddress;
- bool hasmac{false};
- if (dnsdist::MacAddressesCache::get(requestor, macaddress.data(), macaddress.size()) == 0) {
- hasmac = true;
- }
-#endif
- for (size_t idx = 0; idx < d_nbLockTries; idx++) {
- auto& shard = getOneShard();
- auto lock = shard->queryRing.try_lock();
- if (lock.owns_lock()) {
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
- insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
-#else
- insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
-#endif
- return;
- }
- if (d_keepLockingStats) {
- ++d_deferredQueryInserts;
- }
- }
-
- /* out of luck, let's just wait */
- if (d_keepLockingStats) {
- ++d_blockingResponseInserts;
- }
- auto& shard = getOneShard();
- auto lock = shard->queryRing.lock();
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
- insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
-#else
- insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
-#endif
- }
-
- void insertResponse(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, unsigned int size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
- {
- for (size_t idx = 0; idx < d_nbLockTries; idx++) {
- auto& shard = getOneShard();
- auto lock = shard->respRing.try_lock();
- if (lock.owns_lock()) {
- insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol);
- return;
- }
- if (d_keepLockingStats) {
- ++d_deferredResponseInserts;
- }
- }
-
- /* out of luck, let's just wait */
- if (d_keepLockingStats) {
- ++d_blockingResponseInserts;
- }
- auto& shard = getOneShard();
- auto lock = shard->respRing.lock();
- insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol);
- }
-
- void clear()
- {
- for (auto& shard : d_shards) {
- shard->queryRing.lock()->clear();
- shard->respRing.lock()->clear();
- }
-
- d_nbQueryEntries.store(0);
- d_nbResponseEntries.store(0);
- d_currentShardId.store(0);
- d_blockingQueryInserts.store(0);
- d_blockingResponseInserts.store(0);
- d_deferredQueryInserts.store(0);
- d_deferredResponseInserts.store(0);
- }
-
- /* this should be called in the unit tests, and never at runtime */
- void reset()
- {
- clear();
- d_initialized = false;
- }
-
- /* load the content of the ring buffer from a file in the format emitted by grepq(),
- only useful for debugging purposes */
- size_t loadFromFile(const std::string& filepath, const struct timespec& now);
-
- bool shouldRecordQueries() const
- {
- return d_recordQueries;
- }
-
- bool shouldRecordResponses() const
- {
- return d_recordResponses;
- }
-
- std::vector<std::unique_ptr<Shard> > d_shards;
- pdns::stat_t d_blockingQueryInserts;
- pdns::stat_t d_blockingResponseInserts;
- pdns::stat_t d_deferredQueryInserts;
- pdns::stat_t d_deferredResponseInserts;
-
-private:
- size_t getShardId()
- {
- return (d_currentShardId++ % d_numberOfShards);
- }
-
- std::unique_ptr<Shard>& getOneShard()
- {
- return d_shards[getShardId()];
- }
-
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
- void insertQueryLocked(boost::circular_buffer<Query>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol, const dnsdist::MacAddress& macaddress, const bool hasmac)
-#else
- void insertQueryLocked(boost::circular_buffer<Query>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
-#endif
- {
- if (!ring.full()) {
- d_nbQueryEntries++;
- }
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
- Rings::Query query{requestor, name, when, dh, size, qtype, protocol, dnsdist::MacAddress{""}, hasmac};
- if (hasmac) {
- memcpy(query.macaddress.data(), macaddress.data(), macaddress.size());
- }
- ring.push_back(std::move(query));
-#else
- ring.push_back({requestor, name, when, dh, size, qtype, protocol});
-#endif
- }
-
- void insertResponseLocked(boost::circular_buffer<Response>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, unsigned int size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
- {
- if (!ring.full()) {
- d_nbResponseEntries++;
- }
- ring.push_back({requestor, backend, name, when, dh, usec, size, qtype, protocol});
- }
-
- std::atomic<size_t> d_nbQueryEntries;
- std::atomic<size_t> d_nbResponseEntries;
- std::atomic<size_t> d_currentShardId;
- std::atomic<bool> d_initialized{false};
-
- size_t d_capacity;
- size_t d_numberOfShards;
- size_t d_nbLockTries = 5;
- bool d_keepLockingStats{false};
- bool d_recordQueries{true};
- bool d_recordResponses{true};
-};
-
-extern Rings g_rings;
+++ /dev/null
-
-#include "dnsdist-snmp.hh"
-#include "dolog.hh"
-
-bool g_snmpEnabled{false};
-bool g_snmpTrapsEnabled{false};
-DNSDistSNMPAgent* g_snmpAgent{nullptr};
-
-#ifdef HAVE_NET_SNMP
-
-#define DNSDIST_OID 1, 3, 6, 1, 4, 1, 43315, 3
-#define DNSDIST_STATS_OID DNSDIST_OID, 1
-#define DNSDIST_STATS_TABLE_OID DNSDIST_OID, 2
-#define DNSDIST_TRAPS_OID DNSDIST_OID, 10, 0
-#define DNSDIST_TRAP_OBJECTS_OID DNSDIST_OID, 11
-
-static const oid queriesOID[] = { DNSDIST_STATS_OID, 1 };
-static const oid responsesOID[] = { DNSDIST_STATS_OID, 2 };
-static const oid servfailResponsesOID[] = { DNSDIST_STATS_OID, 3 };
-static const oid aclDropsOID[] = { DNSDIST_STATS_OID, 4 };
-// 5 was BlockFilter, removed in 1.2.0
-static const oid ruleDropOID[] = { DNSDIST_STATS_OID, 6 };
-static const oid ruleNXDomainOID[] = { DNSDIST_STATS_OID, 7 };
-static const oid ruleRefusedOID[] = { DNSDIST_STATS_OID, 8 };
-static const oid selfAnsweredOID[] = { DNSDIST_STATS_OID, 9 };
-static const oid downstreamTimeoutsOID[] = { DNSDIST_STATS_OID, 10 };
-static const oid downstreamSendErrorsOID[] = { DNSDIST_STATS_OID, 11 };
-static const oid truncFailOID[] = { DNSDIST_STATS_OID, 12 };
-static const oid noPolicyOID[] = { DNSDIST_STATS_OID, 13 };
-static const oid latency0_1OID[] = { DNSDIST_STATS_OID, 14 };
-static const oid latency1_10OID[] = { DNSDIST_STATS_OID, 15 };
-static const oid latency10_50OID[] = { DNSDIST_STATS_OID, 16 };
-static const oid latency50_100OID[] = { DNSDIST_STATS_OID, 17 };
-static const oid latency100_1000OID[] = { DNSDIST_STATS_OID, 18 };
-static const oid latencySlowOID[] = { DNSDIST_STATS_OID, 19 };
-static const oid latencyAvg100OID[] = { DNSDIST_STATS_OID, 20 };
-static const oid latencyAvg1000OID[] = { DNSDIST_STATS_OID, 21 };
-static const oid latencyAvg10000OID[] = { DNSDIST_STATS_OID, 22 };
-static const oid latencyAvg1000000OID[] = { DNSDIST_STATS_OID, 23 };
-static const oid uptimeOID[] = { DNSDIST_STATS_OID, 24 };
-static const oid realMemoryUsageOID[] = { DNSDIST_STATS_OID, 25 };
-static const oid nonCompliantQueriesOID[] = { DNSDIST_STATS_OID, 26 };
-static const oid nonCompliantResponsesOID[] = { DNSDIST_STATS_OID, 27 };
-static const oid rdQueriesOID[] = { DNSDIST_STATS_OID, 28 };
-static const oid emptyQueriesOID[] = { DNSDIST_STATS_OID, 29 };
-static const oid cacheHitsOID[] = { DNSDIST_STATS_OID, 30 };
-static const oid cacheMissesOID[] = { DNSDIST_STATS_OID, 31 };
-static const oid cpuUserMSecOID[] = { DNSDIST_STATS_OID, 32 };
-static const oid cpuSysMSecOID[] = { DNSDIST_STATS_OID, 33 };
-static const oid fdUsageOID[] = { DNSDIST_STATS_OID, 34 };
-static const oid dynBlockedOID[] = { DNSDIST_STATS_OID, 35 };
-static const oid dynBlockedNMGSizeOID[] = { DNSDIST_STATS_OID, 36 };
-static const oid ruleServFailOID[] = { DNSDIST_STATS_OID, 37 };
-static const oid securityStatusOID[] = { DNSDIST_STATS_OID, 38 };
-static const oid specialMemoryUsageOID[] = { DNSDIST_STATS_OID, 39 };
-static const oid ruleTruncatedOID[] = { DNSDIST_STATS_OID, 40 };
-
-static std::unordered_map<oid, DNSDistStats::entry_t> s_statsMap;
-
-/* We are never called for a GETNEXT if it's registered as a
- "instance", as it's "magically" handled for us. */
-/* a instance handler also only hands us one request at a time, so
- we don't need to loop over a list of requests; we'll only get one. */
-
-static int handleCounter64Stats(netsnmp_mib_handler* handler,
- netsnmp_handler_registration* reginfo,
- netsnmp_agent_request_info* reqinfo,
- netsnmp_request_info* requests)
-{
- if (reqinfo->mode != MODE_GET) {
- return SNMP_ERR_GENERR;
- }
-
- if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
- return SNMP_ERR_GENERR;
- }
-
- const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
- if (it == s_statsMap.end()) {
- return SNMP_ERR_GENERR;
- }
-
- if (const auto& val = boost::get<pdns::stat_t*>(&it->second)) {
- return DNSDistSNMPAgent::setCounter64Value(requests, (*val)->load());
- }
-
- return SNMP_ERR_GENERR;
-}
-
-static void registerCounter64Stat(const char* name, const oid statOID[], size_t statOIDLength, pdns::stat_t* ptr)
-{
- if (statOIDLength != OID_LENGTH(queriesOID)) {
- errlog("Invalid OID for SNMP Counter64 statistic %s", name);
- return;
- }
-
- if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
- errlog("OID for SNMP Counter64 statistic %s has already been registered", name);
- return;
- }
-
- s_statsMap[statOID[statOIDLength - 1]] = ptr;
- netsnmp_register_scalar(netsnmp_create_handler_registration(name,
- handleCounter64Stats,
- statOID,
- statOIDLength,
- HANDLER_CAN_RONLY));
-}
-
-static int handleFloatStats(netsnmp_mib_handler* handler,
- netsnmp_handler_registration* reginfo,
- netsnmp_agent_request_info* reqinfo,
- netsnmp_request_info* requests)
-{
- if (reqinfo->mode != MODE_GET) {
- return SNMP_ERR_GENERR;
- }
-
- if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
- return SNMP_ERR_GENERR;
- }
-
- const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
- if (it == s_statsMap.end()) {
- return SNMP_ERR_GENERR;
- }
-
- if (const auto& val = boost::get<double*>(&it->second)) {
- std::string str(std::to_string(**val));
- snmp_set_var_typed_value(requests->requestvb,
- ASN_OCTET_STR,
- str.c_str(),
- str.size());
- return SNMP_ERR_NOERROR;
- }
-
- return SNMP_ERR_GENERR;
-}
-
-static void registerFloatStat(const char* name, const oid statOID[], size_t statOIDLength, double* ptr)
-{
- if (statOIDLength != OID_LENGTH(queriesOID)) {
- errlog("Invalid OID for SNMP Float statistic %s", name);
- return;
- }
-
- if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
- errlog("OID for SNMP Float statistic %s has already been registered", name);
- return;
- }
-
- s_statsMap[statOID[statOIDLength - 1]] = ptr;
- netsnmp_register_scalar(netsnmp_create_handler_registration(name,
- handleFloatStats,
- statOID,
- statOIDLength,
- HANDLER_CAN_RONLY));
-}
-
-static int handleGauge64Stats(netsnmp_mib_handler* handler,
- netsnmp_handler_registration* reginfo,
- netsnmp_agent_request_info* reqinfo,
- netsnmp_request_info* requests)
-{
- if (reqinfo->mode != MODE_GET) {
- return SNMP_ERR_GENERR;
- }
-
- if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
- return SNMP_ERR_GENERR;
- }
-
- const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
- if (it == s_statsMap.end()) {
- return SNMP_ERR_GENERR;
- }
-
- std::string str;
- uint64_t value = (*boost::get<DNSDistStats::statfunction_t>(&it->second))(str);
- return DNSDistSNMPAgent::setCounter64Value(requests, value);
-}
-
-static void registerGauge64Stat(const char* name, const oid statOID[], size_t statOIDLength, DNSDistStats::statfunction_t ptr)
-{
- if (statOIDLength != OID_LENGTH(queriesOID)) {
- errlog("Invalid OID for SNMP Gauge64 statistic %s", name);
- return;
- }
-
- if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
- errlog("OID for SNMP Gauge64 statistic %s has already been registered", name);
- return;
- }
-
- s_statsMap[statOID[statOIDLength - 1]] = ptr;
- netsnmp_register_scalar(netsnmp_create_handler_registration(name,
- handleGauge64Stats,
- statOID,
- statOIDLength,
- HANDLER_CAN_RONLY));
-}
-
-/* column number definitions for table backendStatTable */
-#define COLUMN_BACKENDID 1
-#define COLUMN_BACKENDNAME 2
-#define COLUMN_BACKENDLATENCY 3
-#define COLUMN_BACKENDWEIGHT 4
-#define COLUMN_BACKENDOUTSTANDING 5
-#define COLUMN_BACKENDQPSLIMIT 6
-#define COLUMN_BACKENDREUSED 7
-#define COLUMN_BACKENDSTATE 8
-#define COLUMN_BACKENDADDRESS 9
-#define COLUMN_BACKENDPOOLS 10
-#define COLUMN_BACKENDQPS 11
-#define COLUMN_BACKENDQUERIES 12
-#define COLUMN_BACKENDORDER 13
-
-static const oid backendStatTableOID[] = { DNSDIST_STATS_TABLE_OID };
-static const oid backendNameOID[] = { DNSDIST_STATS_TABLE_OID, 1, 2 };
-static const oid backendStateOID[] = { DNSDIST_STATS_TABLE_OID, 1, 8};
-static const oid backendAddressOID[] = { DNSDIST_STATS_TABLE_OID, 1, 9};
-
-static const oid socketFamilyOID[] = { DNSDIST_TRAP_OBJECTS_OID, 1, 0 };
-static const oid socketProtocolOID[] = { DNSDIST_TRAP_OBJECTS_OID, 2, 0 };
-static const oid fromAddressOID[] = { DNSDIST_TRAP_OBJECTS_OID, 3, 0 };
-static const oid toAddressOID[] = { DNSDIST_TRAP_OBJECTS_OID, 4, 0 };
-static const oid queryTypeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 5, 0 };
-static const oid querySizeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 6, 0 };
-static const oid queryIDOID[] = { DNSDIST_TRAP_OBJECTS_OID, 7, 0 };
-static const oid qNameOID[] = { DNSDIST_TRAP_OBJECTS_OID, 8, 0 };
-static const oid qClassOID[] = { DNSDIST_TRAP_OBJECTS_OID, 9, 0 };
-static const oid qTypeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 10, 0 };
-static const oid trapReasonOID[] = { DNSDIST_TRAP_OBJECTS_OID, 11, 0 };
-
-static const oid backendStatusChangeTrapOID[] = { DNSDIST_TRAPS_OID, 1 };
-static const oid actionTrapOID[] = { DNSDIST_TRAPS_OID, 2 };
-static const oid customTrapOID[] = { DNSDIST_TRAPS_OID, 3 };
-
-static servers_t s_servers;
-static size_t s_currentServerIdx = 0;
-
-static netsnmp_variable_list* backendStatTable_get_next_data_point(void** loop_context,
- void** my_data_context,
- netsnmp_variable_list* put_index_data,
- netsnmp_iterator_info* mydata)
-{
- if (s_currentServerIdx >= s_servers.size()) {
- return NULL;
- }
-
- *my_data_context = (void*) (s_servers[s_currentServerIdx]).get();
- snmp_set_var_typed_integer(put_index_data, ASN_UNSIGNED, s_currentServerIdx);
- s_currentServerIdx++;
-
- return put_index_data;
-}
-
-static netsnmp_variable_list* backendStatTable_get_first_data_point(void** loop_context,
- void** data_context,
- netsnmp_variable_list* put_index_data,
- netsnmp_iterator_info* data)
-{
- s_currentServerIdx = 0;
-
- /* get a copy of the shared_ptrs so they are not
- destroyed while we process the request */
- auto dstates = g_dstates.getLocal();
- s_servers.clear();
- s_servers.reserve(dstates->size());
- for (const auto& server : *dstates) {
- s_servers.push_back(server);
- }
-
- return backendStatTable_get_next_data_point(loop_context,
- data_context,
- put_index_data,
- data);
-}
-
-static int backendStatTable_handler(netsnmp_mib_handler* handler,
- netsnmp_handler_registration* reginfo,
- netsnmp_agent_request_info* reqinfo,
- netsnmp_request_info* requests)
-{
- netsnmp_request_info* request;
-
- switch (reqinfo->mode) {
- case MODE_GET:
- for (request = requests; request; request = request->next) {
- netsnmp_table_request_info* table_info = netsnmp_extract_table_info(request);
- const DownstreamState* server = (const DownstreamState*) netsnmp_extract_iterator_context(request);
-
- if (!server) {
- continue;
- }
-
- switch (table_info->colnum) {
- case COLUMN_BACKENDNAME:
- snmp_set_var_typed_value(request->requestvb,
- ASN_OCTET_STR,
- server->getName().c_str(),
- server->getName().size());
- break;
- case COLUMN_BACKENDLATENCY:
- DNSDistSNMPAgent::setCounter64Value(request,
- server->getRelevantLatencyUsec() / 1000.0);
- break;
- case COLUMN_BACKENDWEIGHT:
- DNSDistSNMPAgent::setCounter64Value(request,
- server->d_config.d_weight);
- break;
- case COLUMN_BACKENDOUTSTANDING:
- DNSDistSNMPAgent::setCounter64Value(request,
- server->outstanding.load());
- break;
- case COLUMN_BACKENDQPSLIMIT:
- DNSDistSNMPAgent::setCounter64Value(request,
- server->qps.getRate());
- break;
- case COLUMN_BACKENDREUSED:
- DNSDistSNMPAgent::setCounter64Value(request, server->reuseds.load());
- break;
- case COLUMN_BACKENDSTATE:
- {
- std::string state(server->getStatus());
- snmp_set_var_typed_value(request->requestvb,
- ASN_OCTET_STR,
- state.c_str(),
- state.size());
- break;
- }
- case COLUMN_BACKENDADDRESS:
- {
- std::string addr(server->d_config.remote.toStringWithPort());
- snmp_set_var_typed_value(request->requestvb,
- ASN_OCTET_STR,
- addr.c_str(),
- addr.size());
- break;
- }
- case COLUMN_BACKENDPOOLS:
- {
- std::string pools;
- for (const auto& p : server->d_config.pools) {
- if (!pools.empty()) {
- pools+=" ";
- }
- pools += p;
- }
- snmp_set_var_typed_value(request->requestvb,
- ASN_OCTET_STR,
- pools.c_str(),
- pools.size());
- break;
- }
- case COLUMN_BACKENDQPS:
- DNSDistSNMPAgent::setCounter64Value(request, server->queryLoad.load());
- break;
- case COLUMN_BACKENDQUERIES:
- DNSDistSNMPAgent::setCounter64Value(request, server->queries.load());
- break;
- case COLUMN_BACKENDORDER:
- DNSDistSNMPAgent::setCounter64Value(request, server->d_config.order);
- break;
- default:
- netsnmp_set_request_error(reqinfo,
- request,
- SNMP_NOSUCHOBJECT);
- break;
- }
- }
- break;
- }
- return SNMP_ERR_NOERROR;
-}
-#endif /* HAVE_NET_SNMP */
-
-bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const DownstreamState& dss)
-{
-#ifdef HAVE_NET_SNMP
- const string backendAddress = dss.d_config.remote.toStringWithPort();
- const string backendStatus = dss.getStatus();
- netsnmp_variable_list* varList = nullptr;
-
- snmp_varlist_add_variable(&varList,
- snmpTrapOID,
- snmpTrapOIDLen,
- ASN_OBJECT_ID,
- backendStatusChangeTrapOID,
- OID_LENGTH(backendStatusChangeTrapOID) * sizeof(oid));
-
-
- snmp_varlist_add_variable(&varList,
- backendNameOID,
- OID_LENGTH(backendNameOID),
- ASN_OCTET_STR,
- dss.getName().c_str(),
- dss.getName().size());
-
- snmp_varlist_add_variable(&varList,
- backendAddressOID,
- OID_LENGTH(backendAddressOID),
- ASN_OCTET_STR,
- backendAddress.c_str(),
- backendAddress.size());
-
- snmp_varlist_add_variable(&varList,
- backendStateOID,
- OID_LENGTH(backendStateOID),
- ASN_OCTET_STR,
- backendStatus.c_str(),
- backendStatus.size());
-
- return sendTrap(d_trapPipe[1], varList);
-#else
- return true;
-#endif /* HAVE_NET_SNMP */
-}
-
-bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason)
-{
-#ifdef HAVE_NET_SNMP
- netsnmp_variable_list* varList = nullptr;
-
- snmp_varlist_add_variable(&varList,
- snmpTrapOID,
- snmpTrapOIDLen,
- ASN_OBJECT_ID,
- customTrapOID,
- OID_LENGTH(customTrapOID) * sizeof(oid));
-
- snmp_varlist_add_variable(&varList,
- trapReasonOID,
- OID_LENGTH(trapReasonOID),
- ASN_OCTET_STR,
- reason.c_str(),
- reason.size());
-
- return sendTrap(d_trapPipe[1], varList);
-#else
- return true;
-#endif /* HAVE_NET_SNMP */
-}
-
-bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dq, const std::string& reason)
-{
-#ifdef HAVE_NET_SNMP
- std::string local = dq.ids.origDest.toString();
- std::string remote = dq.ids.origRemote.toString();
- std::string qname = dq.ids.qname.toStringNoDot();
- const uint32_t socketFamily = dq.ids.origRemote.isIPv4() ? 1 : 2;
- const uint32_t socketProtocol = dq.overTCP() ? 2 : 1;
- const uint32_t queryType = dq.getHeader()->qr ? 2 : 1;
- const uint32_t querySize = (uint32_t) dq.getData().size();
- const uint32_t queryID = (uint32_t) ntohs(dq.getHeader()->id);
- const uint32_t qType = (uint32_t) dq.ids.qtype;
- const uint32_t qClass = (uint32_t) dq.ids.qclass;
-
- netsnmp_variable_list* varList = nullptr;
-
- snmp_varlist_add_variable(&varList,
- snmpTrapOID,
- snmpTrapOIDLen,
- ASN_OBJECT_ID,
- actionTrapOID,
- OID_LENGTH(actionTrapOID) * sizeof(oid));
-
- snmp_varlist_add_variable(&varList,
- socketFamilyOID,
- OID_LENGTH(socketFamilyOID),
- ASN_INTEGER,
- reinterpret_cast<const u_char*>(&socketFamily),
- sizeof(socketFamily));
-
- snmp_varlist_add_variable(&varList,
- socketProtocolOID,
- OID_LENGTH(socketProtocolOID),
- ASN_INTEGER,
- reinterpret_cast<const u_char*>(&socketProtocol),
- sizeof(socketProtocol));
-
- snmp_varlist_add_variable(&varList,
- fromAddressOID,
- OID_LENGTH(fromAddressOID),
- ASN_OCTET_STR,
- remote.c_str(),
- remote.size());
-
- snmp_varlist_add_variable(&varList,
- toAddressOID,
- OID_LENGTH(toAddressOID),
- ASN_OCTET_STR,
- local.c_str(),
- local.size());
-
- snmp_varlist_add_variable(&varList,
- queryTypeOID,
- OID_LENGTH(queryTypeOID),
- ASN_INTEGER,
- reinterpret_cast<const u_char*>(&queryType),
- sizeof(queryType));
-
- snmp_varlist_add_variable(&varList,
- querySizeOID,
- OID_LENGTH(querySizeOID),
- ASN_UNSIGNED,
- reinterpret_cast<const u_char*>(&querySize),
- sizeof(querySize));
-
- snmp_varlist_add_variable(&varList,
- queryIDOID,
- OID_LENGTH(queryIDOID),
- ASN_UNSIGNED,
- reinterpret_cast<const u_char*>(&queryID),
- sizeof(queryID));
-
- snmp_varlist_add_variable(&varList,
- qNameOID,
- OID_LENGTH(qNameOID),
- ASN_OCTET_STR,
- qname.c_str(),
- qname.size());
-
- snmp_varlist_add_variable(&varList,
- qClassOID,
- OID_LENGTH(qClassOID),
- ASN_UNSIGNED,
- reinterpret_cast<const u_char*>(&qClass),
- sizeof(qClass));
-
- snmp_varlist_add_variable(&varList,
- qTypeOID,
- OID_LENGTH(qTypeOID),
- ASN_UNSIGNED,
- reinterpret_cast<const u_char*>(&qType),
- sizeof(qType));
-
- snmp_varlist_add_variable(&varList,
- trapReasonOID,
- OID_LENGTH(trapReasonOID),
- ASN_OCTET_STR,
- reason.c_str(),
- reason.size());
-
- return sendTrap(d_trapPipe[1], varList);
-#else
- return true;
-#endif /* HAVE_NET_SNMP */
-}
-
-DNSDistSNMPAgent::DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket): SNMPAgent(name, daemonSocket)
-{
-#ifdef HAVE_NET_SNMP
-
- registerCounter64Stat("queries", queriesOID, OID_LENGTH(queriesOID), &g_stats.queries);
- registerCounter64Stat("responses", responsesOID, OID_LENGTH(responsesOID), &g_stats.responses);
- registerCounter64Stat("servfailResponses", servfailResponsesOID, OID_LENGTH(servfailResponsesOID), &g_stats.servfailResponses);
- registerCounter64Stat("aclDrops", aclDropsOID, OID_LENGTH(aclDropsOID), &g_stats.aclDrops);
- registerCounter64Stat("ruleDrop", ruleDropOID, OID_LENGTH(ruleDropOID), &g_stats.ruleDrop);
- registerCounter64Stat("ruleNXDomain", ruleNXDomainOID, OID_LENGTH(ruleNXDomainOID), &g_stats.ruleNXDomain);
- registerCounter64Stat("ruleRefused", ruleRefusedOID, OID_LENGTH(ruleRefusedOID), &g_stats.ruleRefused);
- registerCounter64Stat("ruleServFail", ruleServFailOID, OID_LENGTH(ruleServFailOID), &g_stats.ruleServFail);
- registerCounter64Stat("ruleTruncated", ruleTruncatedOID, OID_LENGTH(ruleTruncatedOID), &g_stats.ruleTruncated);
- registerCounter64Stat("selfAnswered", selfAnsweredOID, OID_LENGTH(selfAnsweredOID), &g_stats.selfAnswered);
- registerCounter64Stat("downstreamTimeouts", downstreamTimeoutsOID, OID_LENGTH(downstreamTimeoutsOID), &g_stats.downstreamTimeouts);
- registerCounter64Stat("downstreamSendErrors", downstreamSendErrorsOID, OID_LENGTH(downstreamSendErrorsOID), &g_stats.downstreamSendErrors);
- registerCounter64Stat("truncFail", truncFailOID, OID_LENGTH(truncFailOID), &g_stats.truncFail);
- registerCounter64Stat("noPolicy", noPolicyOID, OID_LENGTH(noPolicyOID), &g_stats.noPolicy);
- registerCounter64Stat("latency0_1", latency0_1OID, OID_LENGTH(latency0_1OID), &g_stats.latency0_1);
- registerCounter64Stat("latency1_10", latency1_10OID, OID_LENGTH(latency1_10OID), &g_stats.latency1_10);
- registerCounter64Stat("latency10_50", latency10_50OID, OID_LENGTH(latency10_50OID), &g_stats.latency10_50);
- registerCounter64Stat("latency50_100", latency50_100OID, OID_LENGTH(latency50_100OID), &g_stats.latency50_100);
- registerCounter64Stat("latency100_1000", latency100_1000OID, OID_LENGTH(latency100_1000OID), &g_stats.latency100_1000);
- registerCounter64Stat("latencySlow", latencySlowOID, OID_LENGTH(latencySlowOID), &g_stats.latencySlow);
- registerCounter64Stat("nonCompliantQueries", nonCompliantQueriesOID, OID_LENGTH(nonCompliantQueriesOID), &g_stats.nonCompliantQueries);
- registerCounter64Stat("nonCompliantResponses", nonCompliantResponsesOID, OID_LENGTH(nonCompliantResponsesOID), &g_stats.nonCompliantResponses);
- registerCounter64Stat("rdQueries", rdQueriesOID, OID_LENGTH(rdQueriesOID), &g_stats.rdQueries);
- registerCounter64Stat("emptyQueries", emptyQueriesOID, OID_LENGTH(emptyQueriesOID), &g_stats.emptyQueries);
- registerCounter64Stat("cacheHits", cacheHitsOID, OID_LENGTH(cacheHitsOID), &g_stats.cacheHits);
- registerCounter64Stat("cacheMisses", cacheMissesOID, OID_LENGTH(cacheMissesOID), &g_stats.cacheMisses);
- registerCounter64Stat("dynBlocked", dynBlockedOID, OID_LENGTH(dynBlockedOID), &g_stats.dynBlocked);
- registerFloatStat("latencyAvg100", latencyAvg100OID, OID_LENGTH(latencyAvg100OID), &g_stats.latencyAvg100);
- registerFloatStat("latencyAvg1000", latencyAvg1000OID, OID_LENGTH(latencyAvg1000OID), &g_stats.latencyAvg1000);
- registerFloatStat("latencyAvg10000", latencyAvg10000OID, OID_LENGTH(latencyAvg10000OID), &g_stats.latencyAvg10000);
- registerFloatStat("latencyAvg1000000", latencyAvg1000000OID, OID_LENGTH(latencyAvg1000000OID), &g_stats.latencyAvg1000000);
- registerGauge64Stat("uptime", uptimeOID, OID_LENGTH(uptimeOID), &uptimeOfProcess);
- registerGauge64Stat("specialMemoryUsage", specialMemoryUsageOID, OID_LENGTH(specialMemoryUsageOID), &getSpecialMemoryUsage);
- registerGauge64Stat("cpuUserMSec", cpuUserMSecOID, OID_LENGTH(cpuUserMSecOID), &getCPUTimeUser);
- registerGauge64Stat("cpuSysMSec", cpuSysMSecOID, OID_LENGTH(cpuSysMSecOID), &getCPUTimeSystem);
- registerGauge64Stat("fdUsage", fdUsageOID, OID_LENGTH(fdUsageOID), &getOpenFileDescriptors);
- registerGauge64Stat("dynBlockedNMGSize", dynBlockedNMGSizeOID, OID_LENGTH(dynBlockedNMGSizeOID), [](const std::string&) { return g_dynblockNMG.getLocal()->size(); });
- registerGauge64Stat("securityStatus", securityStatusOID, OID_LENGTH(securityStatusOID), [](const std::string&) { return g_stats.securityStatus.load(); });
- registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID), &getRealMemoryUsage);
-
-
- netsnmp_table_registration_info* table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
- netsnmp_table_helper_add_indexes(table_info,
- ASN_GAUGE, /* index: backendId */
- 0);
- table_info->min_column = COLUMN_BACKENDNAME;
- table_info->max_column = COLUMN_BACKENDORDER;
- netsnmp_iterator_info* iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
- iinfo->get_first_data_point = backendStatTable_get_first_data_point;
- iinfo->get_next_data_point = backendStatTable_get_next_data_point;
- iinfo->table_reginfo = table_info;
-
- netsnmp_register_table_iterator(netsnmp_create_handler_registration("backendStatTable",
- backendStatTable_handler,
- backendStatTableOID,
- OID_LENGTH(backendStatTableOID),
- HANDLER_CAN_RONLY),
- iinfo);
-
-#endif /* HAVE_NET_SNMP */
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <thread>
-#include <netinet/tcp.h>
-#include <queue>
-
-#include "dnsdist.hh"
-#include "dnsdist-concurrent-connections.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-proxy-protocol.hh"
-#include "dnsdist-rings.hh"
-#include "dnsdist-tcp.hh"
-#include "dnsdist-tcp-downstream.hh"
-#include "dnsdist-downstream-connection.hh"
-#include "dnsdist-tcp-upstream.hh"
-#include "dnsdist-xpf.hh"
-#include "dnsparser.hh"
-#include "dolog.hh"
-#include "gettime.hh"
-#include "lock.hh"
-#include "sstuff.hh"
-#include "tcpiohandler.hh"
-#include "tcpiohandler-mplexer.hh"
-#include "threadname.hh"
-
-/* TCP: the grand design.
- We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops.
- An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially
- we will not go there.
-
- In a sense there is a strong symmetry between UDP and TCP, once a connection to a downstream has been setup.
- This symmetry is broken because of head-of-line blocking within TCP though, necessitating additional connections
- to guarantee performance.
-
- So the idea is to have a 'pool' of available downstream connections, and forward messages to/from them and never queue.
- So whenever an answer comes in, we know where it needs to go.
-
- Let's start naively.
-*/
-
-size_t g_maxTCPQueriesPerConn{0};
-size_t g_maxTCPConnectionDuration{0};
-
-#ifdef __linux__
-// On Linux this gives us 128k pending queries (default is 8192 queries),
-// which should be enough to deal with huge spikes
-size_t g_tcpInternalPipeBufferSize{1024*1024};
-uint64_t g_maxTCPQueuedConnections{10000};
-#else
-size_t g_tcpInternalPipeBufferSize{0};
-uint64_t g_maxTCPQueuedConnections{1000};
-#endif
-
-int g_tcpRecvTimeout{2};
-int g_tcpSendTimeout{2};
-std::atomic<uint64_t> g_tcpStatesDumpRequested{0};
-
-LockGuarded<std::map<ComboAddress, size_t, ComboAddress::addressOnlyLessThan>> dnsdist::IncomingConcurrentTCPConnectionsManager::s_tcpClientsConcurrentConnectionsCount;
-size_t dnsdist::IncomingConcurrentTCPConnectionsManager::s_maxTCPConnectionsPerClient = 0;
-
-IncomingTCPConnectionState::~IncomingTCPConnectionState()
-{
- dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(d_ci.remote);
-
- if (d_ci.cs != nullptr) {
- struct timeval now;
- gettimeofday(&now, nullptr);
-
- auto diff = now - d_connectionStartTime;
- d_ci.cs->updateTCPMetrics(d_queriesCount, diff.tv_sec * 1000.0 + diff.tv_usec / 1000.0);
- }
-
- // would have been done when the object is destroyed anyway,
- // but that way we make sure it's done before the ConnectionInfo is destroyed,
- // closing the descriptor, instead of relying on the declaration order of the objects in the class
- d_handler.close();
-}
-
-size_t IncomingTCPConnectionState::clearAllDownstreamConnections()
-{
- return t_downstreamTCPConnectionsManager.clear();
-}
-
-std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getDownstreamConnection(std::shared_ptr<DownstreamState>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now)
-{
- std::shared_ptr<TCPConnectionToBackend> downstream{nullptr};
-
- downstream = getOwnedDownstreamConnection(ds, tlvs);
-
- if (!downstream) {
- /* we don't have a connection to this backend owned yet, let's get one (it might not be a fresh one, though) */
- downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(d_threadData.mplexer, ds, now, std::string());
- if (ds->d_config.useProxyProtocol) {
- registerOwnedDownstreamConnection(downstream);
- }
- }
-
- return downstream;
-}
-
-static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int crossProtocolResponsesListenPipeFD, int crossProtocolResponsesWritePipeFD, std::vector<ClientState*> tcpAcceptStates);
-
-TCPClientCollection::TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpAcceptStates): d_tcpclientthreads(maxThreads), d_maxthreads(maxThreads)
-{
- for (size_t idx = 0; idx < maxThreads; idx++) {
- addTCPClientThread(tcpAcceptStates);
- }
-}
-
-void TCPClientCollection::addTCPClientThread(std::vector<ClientState*>& tcpAcceptStates)
-{
- auto preparePipe = [](int fds[2], const std::string& type) -> bool {
- if (pipe(fds) < 0) {
- errlog("Error creating the TCP thread %s pipe: %s", type, stringerror());
- return false;
- }
-
- if (!setNonBlocking(fds[0])) {
- int err = errno;
- close(fds[0]);
- close(fds[1]);
- errlog("Error setting the TCP thread %s pipe non-blocking: %s", type, stringerror(err));
- return false;
- }
-
- if (!setNonBlocking(fds[1])) {
- int err = errno;
- close(fds[0]);
- close(fds[1]);
- errlog("Error setting the TCP thread %s pipe non-blocking: %s", type, stringerror(err));
- return false;
- }
-
- if (g_tcpInternalPipeBufferSize > 0 && getPipeBufferSize(fds[0]) < g_tcpInternalPipeBufferSize) {
- setPipeBufferSize(fds[0], g_tcpInternalPipeBufferSize);
- }
-
- return true;
- };
-
- int pipefds[2] = { -1, -1};
- if (!preparePipe(pipefds, "communication")) {
- return;
- }
-
- int crossProtocolQueriesFDs[2] = { -1, -1};
- if (!preparePipe(crossProtocolQueriesFDs, "cross-protocol queries")) {
- return;
- }
-
- int crossProtocolResponsesFDs[2] = { -1, -1};
- if (!preparePipe(crossProtocolResponsesFDs, "cross-protocol responses")) {
- return;
- }
-
- vinfolog("Adding TCP Client thread");
-
- {
- if (d_numthreads >= d_tcpclientthreads.size()) {
- vinfolog("Adding a new TCP client thread would exceed the vector size (%d/%d), skipping. Consider increasing the maximum amount of TCP client threads with setMaxTCPClientThreads() in the configuration.", d_numthreads.load(), d_tcpclientthreads.size());
- close(crossProtocolQueriesFDs[0]);
- close(crossProtocolQueriesFDs[1]);
- close(crossProtocolResponsesFDs[0]);
- close(crossProtocolResponsesFDs[1]);
- close(pipefds[0]);
- close(pipefds[1]);
- return;
- }
-
- /* from now on this side of the pipe will be managed by that object,
- no need to worry about it */
- TCPWorkerThread worker(pipefds[1], crossProtocolQueriesFDs[1], crossProtocolResponsesFDs[1]);
- try {
- std::thread t1(tcpClientThread, pipefds[0], crossProtocolQueriesFDs[0], crossProtocolResponsesFDs[0], crossProtocolResponsesFDs[1], tcpAcceptStates);
- t1.detach();
- }
- catch (const std::runtime_error& e) {
- /* the thread creation failed, don't leak */
- errlog("Error creating a TCP thread: %s", e.what());
- close(pipefds[0]);
- close(crossProtocolQueriesFDs[0]);
- close(crossProtocolResponsesFDs[0]);
- return;
- }
-
- d_tcpclientthreads.at(d_numthreads) = std::move(worker);
- ++d_numthreads;
- }
-}
-
-std::unique_ptr<TCPClientCollection> g_tcpclientthreads;
-
-static IOState sendQueuedResponses(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
-{
- IOState result = IOState::Done;
-
- while (state->active() && !state->d_queuedResponses.empty()) {
- DEBUGLOG("queue size is "<<state->d_queuedResponses.size()<<", sending the next one");
- TCPResponse resp = std::move(state->d_queuedResponses.front());
- state->d_queuedResponses.pop_front();
- state->d_state = IncomingTCPConnectionState::State::idle;
- result = state->sendResponse(state, now, std::move(resp));
- if (result != IOState::Done) {
- return result;
- }
- }
-
- state->d_state = IncomingTCPConnectionState::State::idle;
- return IOState::Done;
-}
-
-static void handleResponseSent(std::shared_ptr<IncomingTCPConnectionState>& state, TCPResponse& currentResponse)
-{
- if (currentResponse.d_idstate.qtype == QType::AXFR || currentResponse.d_idstate.qtype == QType::IXFR) {
- return;
- }
-
- --state->d_currentQueriesCount;
-
- const auto& ds = currentResponse.d_connection ? currentResponse.d_connection->getDS() : currentResponse.d_ds;
- if (currentResponse.d_idstate.selfGenerated == false && ds) {
- const auto& ids = currentResponse.d_idstate;
- double udiff = ids.queryRealTime.udiff();
- vinfolog("Got answer from %s, relayed to %s (%s, %d bytes), took %f us", ds->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), (state->d_handler.isTLS() ? "DoT" : "TCP"), currentResponse.d_buffer.size(), udiff);
-
- auto backendProtocol = ds->getProtocol();
- if (backendProtocol == dnsdist::Protocol::DoUDP) {
- backendProtocol = dnsdist::Protocol::DoTCP;
- }
- ::handleResponseSent(ids, udiff, state->d_ci.remote, ds->d_config.remote, static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, backendProtocol, true);
- } else {
- const auto& ids = currentResponse.d_idstate;
- ::handleResponseSent(ids, 0., state->d_ci.remote, ComboAddress(), static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, ids.protocol, false);
- }
-
- currentResponse.d_buffer.clear();
- currentResponse.d_connection.reset();
-}
-
-static void prependSizeToTCPQuery(PacketBuffer& buffer, size_t proxyProtocolPayloadSize)
-{
- if (buffer.size() <= proxyProtocolPayloadSize) {
- throw std::runtime_error("The payload size is smaller or equal to the buffer size");
- }
-
- uint16_t queryLen = proxyProtocolPayloadSize > 0 ? (buffer.size() - proxyProtocolPayloadSize) : buffer.size();
- const uint8_t sizeBytes[] = { static_cast<uint8_t>(queryLen / 256), static_cast<uint8_t>(queryLen % 256) };
- /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes
- that could occur if we had to deal with the size during the processing,
- especially alignment issues */
- buffer.insert(buffer.begin() + proxyProtocolPayloadSize, sizeBytes, sizeBytes + 2);
-}
-
-bool IncomingTCPConnectionState::canAcceptNewQueries(const struct timeval& now)
-{
- if (d_hadErrors) {
- DEBUGLOG("not accepting new queries because we encountered some error during the processing already");
- return false;
- }
-
- if (d_currentQueriesCount >= d_ci.cs->d_maxInFlightQueriesPerConn) {
- DEBUGLOG("not accepting new queries because we already have "<<d_currentQueriesCount<<" out of "<<d_ci.cs->d_maxInFlightQueriesPerConn);
- return false;
- }
-
- if (g_maxTCPQueriesPerConn && d_queriesCount > g_maxTCPQueriesPerConn) {
- vinfolog("not accepting new queries from %s because it reached the maximum number of queries per conn (%d / %d)", d_ci.remote.toStringWithPort(), d_queriesCount, g_maxTCPQueriesPerConn);
- return false;
- }
-
- if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
- vinfolog("not accepting new queries from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
- return false;
- }
-
- return true;
-}
-
-void IncomingTCPConnectionState::resetForNewQuery()
-{
- d_buffer.resize(sizeof(uint16_t));
- d_currentPos = 0;
- d_querySize = 0;
- d_state = State::waitingForQuery;
-}
-
-std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs)
-{
- auto it = d_ownedConnectionsToBackend.find(ds);
- if (it == d_ownedConnectionsToBackend.end()) {
- DEBUGLOG("no owned connection found for "<<ds->getName());
- return nullptr;
- }
-
- for (auto& conn : it->second) {
- if (conn->canBeReused(true) && conn->matchesTLVs(tlvs)) {
- DEBUGLOG("Got one owned connection accepting more for "<<ds->getName());
- conn->setReused();
- return conn;
- }
- DEBUGLOG("not accepting more for "<<ds->getName());
- }
-
- return nullptr;
-}
-
-void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn)
-{
- d_ownedConnectionsToBackend[conn->getDS()].push_front(conn);
-}
-
-/* called when the buffer has been set and the rules have been processed, and only from handleIO (sometimes indirectly via handleQuery) */
-IOState IncomingTCPConnectionState::sendResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response)
-{
- state->d_state = IncomingTCPConnectionState::State::sendingResponse;
-
- uint16_t responseSize = static_cast<uint16_t>(response.d_buffer.size());
- const uint8_t sizeBytes[] = { static_cast<uint8_t>(responseSize / 256), static_cast<uint8_t>(responseSize % 256) };
- /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes
- that could occur if we had to deal with the size during the processing,
- especially alignment issues */
- response.d_buffer.insert(response.d_buffer.begin(), sizeBytes, sizeBytes + 2);
- state->d_currentPos = 0;
- state->d_currentResponse = std::move(response);
-
- try {
- auto iostate = state->d_handler.tryWrite(state->d_currentResponse.d_buffer, state->d_currentPos, state->d_currentResponse.d_buffer.size());
- if (iostate == IOState::Done) {
- DEBUGLOG("response sent from "<<__PRETTY_FUNCTION__);
- handleResponseSent(state, state->d_currentResponse);
- return iostate;
- } else {
- state->d_lastIOBlocked = true;
- DEBUGLOG("partial write");
- return iostate;
- }
- }
- catch (const std::exception& e) {
- vinfolog("Closing TCP client connection with %s: %s", state->d_ci.remote.toStringWithPort(), e.what());
- DEBUGLOG("Closing TCP client connection: "<<e.what());
- ++state->d_ci.cs->tcpDiedSendingResponse;
-
- state->terminateClientConnection();
-
- return IOState::Done;
- }
-}
-
-void IncomingTCPConnectionState::terminateClientConnection()
-{
- DEBUGLOG("terminating client connection");
- d_queuedResponses.clear();
- /* we have already released idle connections that could be reused,
- we don't care about the ones still waiting for responses */
- for (auto& backend : d_ownedConnectionsToBackend) {
- for (auto& conn : backend.second) {
- conn->release();
- }
- }
- d_ownedConnectionsToBackend.clear();
-
- /* meaning we will no longer be 'active' when the backend
- response or timeout comes in */
- d_ioState.reset();
-
- /* if we do have remaining async descriptors associated with this TLS
- connection, we need to defer the destruction of the TLS object until
- the engine has reported back, otherwise we have a use-after-free.. */
- auto afds = d_handler.getAsyncFDs();
- if (afds.empty()) {
- d_handler.close();
- }
- else {
- /* we might already be waiting, but we might also not because sometimes we have already been
- notified via the descriptor, not received Async again, but the async job still exists.. */
- auto state = shared_from_this();
- for (const auto fd : afds) {
- try {
- state->d_threadData.mplexer->addReadFD(fd, handleAsyncReady, state);
- }
- catch (...) {
- }
- }
-
- }
-}
-
-void IncomingTCPConnectionState::queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response)
-{
- // queue response
- state->d_queuedResponses.push_back(std::move(response));
- DEBUGLOG("queueing response, state is "<<(int)state->d_state<<", queue size is now "<<state->d_queuedResponses.size());
-
- // when the response comes from a backend, there is a real possibility that we are currently
- // idle, and thus not trying to send the response right away would make our ref count go to 0.
- // Even if we are waiting for a query, we will not wake up before the new query arrives or a
- // timeout occurs
- if (state->d_state == IncomingTCPConnectionState::State::idle ||
- state->d_state == IncomingTCPConnectionState::State::waitingForQuery) {
- auto iostate = sendQueuedResponses(state, now);
-
- if (iostate == IOState::Done && state->active()) {
- if (state->canAcceptNewQueries(now)) {
- state->resetForNewQuery();
- state->d_state = IncomingTCPConnectionState::State::waitingForQuery;
- iostate = IOState::NeedRead;
- }
- else {
- state->d_state = IncomingTCPConnectionState::State::idle;
- }
- }
-
- // for the same reason we need to update the state right away, nobody will do that for us
- if (state->active()) {
- updateIO(state, iostate, now);
- }
- }
-}
-
-void IncomingTCPConnectionState::handleAsyncReady(int fd, FDMultiplexer::funcparam_t& param)
-{
- auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
-
- /* If we are here, the async jobs for this SSL* are finished
- so we should be able to remove all FDs */
- auto afds = state->d_handler.getAsyncFDs();
- for (const auto afd : afds) {
- try {
- state->d_threadData.mplexer->removeReadFD(afd);
- }
- catch (...) {
- }
- }
-
- if (state->active()) {
- /* and now we restart our own I/O state machine */
- struct timeval now;
- gettimeofday(&now, nullptr);
- handleIO(state, now);
- }
- else {
- /* we were only waiting for the engine to come back,
- to prevent a use-after-free */
- state->d_handler.close();
- }
-}
-
-void IncomingTCPConnectionState::updateIO(std::shared_ptr<IncomingTCPConnectionState>& state, IOState newState, const struct timeval& now)
-{
- if (newState == IOState::Async) {
- auto fds = state->d_handler.getAsyncFDs();
- for (const auto fd : fds) {
- state->d_threadData.mplexer->addReadFD(fd, handleAsyncReady, state);
- }
- state->d_ioState->update(IOState::Done, handleIOCallback, state);
- }
- else {
- state->d_ioState->update(newState, handleIOCallback, state, newState == IOState::NeedWrite ? state->getClientWriteTTD(now) : state->getClientReadTTD(now));
- }
-}
-
-/* called from the backend code when a new response has been received */
-void IncomingTCPConnectionState::handleResponse(const struct timeval& now, TCPResponse&& response)
-{
- if (std::this_thread::get_id() != d_creatorThreadID) {
- handleCrossProtocolResponse(now, std::move(response));
- return;
- }
-
- std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
-
- if (!response.isAsync() && response.d_connection && response.d_connection->getDS() && response.d_connection->getDS()->d_config.useProxyProtocol) {
- // if we have added a TCP Proxy Protocol payload to a connection, don't release it to the general pool as no one else will be able to use it anyway
- if (!response.d_connection->willBeReusable(true)) {
- // if it can't be reused even by us, well
- const auto connIt = state->d_ownedConnectionsToBackend.find(response.d_connection->getDS());
- if (connIt != state->d_ownedConnectionsToBackend.end()) {
- auto& list = connIt->second;
-
- for (auto it = list.begin(); it != list.end(); ++it) {
- if (*it == response.d_connection) {
- try {
- response.d_connection->release();
- }
- catch (const std::exception& e) {
- vinfolog("Error releasing connection: %s", e.what());
- }
- list.erase(it);
- break;
- }
- }
- }
- }
- }
-
- if (response.d_buffer.size() < sizeof(dnsheader)) {
- state->terminateClientConnection();
- return;
- }
-
- if (!response.isAsync()) {
- try {
- auto& ids = response.d_idstate;
- unsigned int qnameWireLength;
- if (!response.d_connection || !responseContentMatches(response.d_buffer, ids.qname, ids.qtype, ids.qclass, response.d_connection->getDS(), qnameWireLength)) {
- state->terminateClientConnection();
- return;
- }
-
- if (response.d_connection->getDS()) {
- ++response.d_connection->getDS()->responses;
- }
-
- DNSResponse dr(ids, response.d_buffer, response.d_connection->getDS());
- dr.d_incomingTCPState = state;
-
- memcpy(&response.d_cleartextDH, dr.getHeader(), sizeof(response.d_cleartextDH));
-
- if (!processResponse(response.d_buffer, *state->d_threadData.localRespRuleActions, *state->d_threadData.localCacheInsertedRespRuleActions, dr, false)) {
- state->terminateClientConnection();
- return;
- }
-
- if (dr.isAsynchronous()) {
- /* we are done for now */
- return;
- }
- }
- catch (const std::exception& e) {
- vinfolog("Unexpected exception while handling response from backend: %s", e.what());
- state->terminateClientConnection();
- return;
- }
- }
-
- ++g_stats.responses;
- ++state->d_ci.cs->responses;
-
- queueResponse(state, now, std::move(response));
-}
-
-struct TCPCrossProtocolResponse
-{
- TCPCrossProtocolResponse(TCPResponse&& response, std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now): d_response(std::move(response)), d_state(state), d_now(now)
- {
- }
-
- TCPResponse d_response;
- std::shared_ptr<IncomingTCPConnectionState> d_state;
- struct timeval d_now;
-};
-
-class TCPCrossProtocolQuery : public CrossProtocolQuery
-{
-public:
- TCPCrossProtocolQuery(PacketBuffer&& buffer, InternalQueryState&& ids, std::shared_ptr<DownstreamState> ds, std::shared_ptr<IncomingTCPConnectionState> sender): CrossProtocolQuery(InternalQuery(std::move(buffer), std::move(ids)), ds), d_sender(std::move(sender))
- {
- proxyProtocolPayloadSize = 0;
- }
-
- ~TCPCrossProtocolQuery()
- {
- }
-
- std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
- {
- return d_sender;
- }
-
- DNSQuestion getDQ() override
- {
- auto& ids = query.d_idstate;
- DNSQuestion dq(ids, query.d_buffer);
- dq.d_incomingTCPState = d_sender;
- return dq;
- }
-
- DNSResponse getDR() override
- {
- auto& ids = query.d_idstate;
- DNSResponse dr(ids, query.d_buffer, downstream);
- dr.d_incomingTCPState = d_sender;
- return dr;
- }
-
-private:
- std::shared_ptr<IncomingTCPConnectionState> d_sender;
-};
-
-std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dq)
-{
- auto state = dq.getIncomingTCPState();
- if (!state) {
- throw std::runtime_error("Trying to create a TCP cross protocol query without a valid TCP state");
- }
-
- dq.ids.origID = dq.getHeader()->id;
- return std::make_unique<TCPCrossProtocolQuery>(std::move(dq.getMutableData()), std::move(dq.ids), nullptr, std::move(state));
-}
-
-void IncomingTCPConnectionState::handleCrossProtocolResponse(const struct timeval& now, TCPResponse&& response)
-{
- if (d_threadData.crossProtocolResponsesPipe == -1) {
- throw std::runtime_error("Invalid pipe descriptor in TCP Cross Protocol Query Sender");
- }
-
- std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
- auto ptr = new TCPCrossProtocolResponse(std::move(response), state, now);
- static_assert(sizeof(ptr) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
- ssize_t sent = write(d_threadData.crossProtocolResponsesPipe, &ptr, sizeof(ptr));
- if (sent != sizeof(ptr)) {
- if (errno == EAGAIN || errno == EWOULDBLOCK) {
- ++g_stats.tcpCrossProtocolResponsePipeFull;
- vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because the pipe is full");
- }
- else {
- vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because we couldn't write to the pipe: %s", stringerror());
- }
- delete ptr;
- }
-}
-
-static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
-{
- if (state->d_querySize < sizeof(dnsheader)) {
- ++g_stats.nonCompliantQueries;
- ++state->d_ci.cs->nonCompliantQueries;
- state->terminateClientConnection();
- return;
- }
-
- ++state->d_queriesCount;
- ++state->d_ci.cs->queries;
- ++g_stats.queries;
-
- if (state->d_handler.isTLS()) {
- auto tlsVersion = state->d_handler.getTLSVersion();
- switch (tlsVersion) {
- case LibsslTLSVersion::TLS10:
- ++state->d_ci.cs->tls10queries;
- break;
- case LibsslTLSVersion::TLS11:
- ++state->d_ci.cs->tls11queries;
- break;
- case LibsslTLSVersion::TLS12:
- ++state->d_ci.cs->tls12queries;
- break;
- case LibsslTLSVersion::TLS13:
- ++state->d_ci.cs->tls13queries;
- break;
- default:
- ++state->d_ci.cs->tlsUnknownqueries;
- }
- }
-
- InternalQueryState ids;
- ids.origDest = state->d_proxiedDestination;
- ids.origRemote = state->d_proxiedRemote;
- ids.cs = state->d_ci.cs;
- ids.queryRealTime.start();
-
- auto dnsCryptResponse = checkDNSCryptQuery(*state->d_ci.cs, state->d_buffer, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, true);
- if (dnsCryptResponse) {
- TCPResponse response;
- state->d_state = IncomingTCPConnectionState::State::idle;
- ++state->d_currentQueriesCount;
- state->queueResponse(state, now, std::move(response));
- return;
- }
-
- {
- /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
- auto* dh = reinterpret_cast<dnsheader*>(state->d_buffer.data());
- if (!checkQueryHeaders(dh, *state->d_ci.cs)) {
- state->terminateClientConnection();
- return;
- }
-
- if (dh->qdcount == 0) {
- TCPResponse response;
- dh->rcode = RCode::NotImp;
- dh->qr = true;
- response.d_idstate.selfGenerated = true;
- response.d_buffer = std::move(state->d_buffer);
- state->d_state = IncomingTCPConnectionState::State::idle;
- ++state->d_currentQueriesCount;
- state->queueResponse(state, now, std::move(response));
- return;
- }
- }
-
- ids.qname = DNSName(reinterpret_cast<const char*>(state->d_buffer.data()), state->d_buffer.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- ids.protocol = dnsdist::Protocol::DoTCP;
- if (ids.dnsCryptQuery) {
- ids.protocol = dnsdist::Protocol::DNSCryptTCP;
- }
- else if (state->d_handler.isTLS()) {
- ids.protocol = dnsdist::Protocol::DoT;
- }
-
- DNSQuestion dq(ids, state->d_buffer);
- const uint16_t* flags = getFlagsFromDNSHeader(dq.getHeader());
- ids.origFlags = *flags;
- dq.d_incomingTCPState = state;
- dq.sni = state->d_handler.getServerNameIndication();
-
- if (state->d_proxyProtocolValues) {
- /* we need to copy them, because the next queries received on that connection will
- need to get the _unaltered_ values */
- dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(*state->d_proxyProtocolValues);
- }
-
- if (dq.ids.qtype == QType::AXFR || dq.ids.qtype == QType::IXFR) {
- dq.ids.skipCache = true;
- }
-
- std::shared_ptr<DownstreamState> ds;
- auto result = processQuery(dq, state->d_threadData.holders, ds);
-
- if (result == ProcessQueryResult::Drop) {
- state->terminateClientConnection();
- return;
- }
- else if (result == ProcessQueryResult::Asynchronous) {
- /* we are done for now */
- ++state->d_currentQueriesCount;
- return;
- }
-
- // the buffer might have been invalidated by now
- const dnsheader* dh = dq.getHeader();
- if (result == ProcessQueryResult::SendAnswer) {
- TCPResponse response;
- memcpy(&response.d_cleartextDH, dh, sizeof(response.d_cleartextDH));
- response.d_idstate = std::move(ids);
- response.d_idstate.origID = dh->id;
- response.d_idstate.selfGenerated = true;
- response.d_idstate.cs = state->d_ci.cs;
- response.d_buffer = std::move(state->d_buffer);
-
- state->d_state = IncomingTCPConnectionState::State::idle;
- ++state->d_currentQueriesCount;
- state->queueResponse(state, now, std::move(response));
- return;
- }
-
- if (result != ProcessQueryResult::PassToBackend || ds == nullptr) {
- state->terminateClientConnection();
- return;
- }
-
- dq.ids.origID = dh->id;
-
- ++state->d_currentQueriesCount;
-
- std::string proxyProtocolPayload;
- if (ds->isDoH()) {
- vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", ids.qname.toLogString(), QType(ids.qtype).toString(), state->d_proxiedRemote.toStringWithPort(), (state->d_handler.isTLS() ? "DoT" : "TCP"), state->d_buffer.size(), ds->getNameWithAddr());
-
- /* we need to do this _before_ creating the cross protocol query because
- after that the buffer will have been moved */
- if (ds->d_config.useProxyProtocol) {
- proxyProtocolPayload = getProxyProtocolPayload(dq);
- }
-
- auto cpq = std::make_unique<TCPCrossProtocolQuery>(std::move(state->d_buffer), std::move(ids), ds, state);
- cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
-
- ds->passCrossProtocolQuery(std::move(cpq));
- return;
- }
-
- prependSizeToTCPQuery(state->d_buffer, 0);
-
- auto downstreamConnection = state->getDownstreamConnection(ds, dq.proxyProtocolValues, now);
-
- if (ds->d_config.useProxyProtocol) {
- /* if we ever sent a TLV over a connection, we can never go back */
- if (!state->d_proxyProtocolPayloadHasTLV) {
- state->d_proxyProtocolPayloadHasTLV = dq.proxyProtocolValues && !dq.proxyProtocolValues->empty();
- }
-
- proxyProtocolPayload = getProxyProtocolPayload(dq);
- }
-
- if (dq.proxyProtocolValues) {
- downstreamConnection->setProxyProtocolValuesSent(std::move(dq.proxyProtocolValues));
- }
-
- TCPQuery query(std::move(state->d_buffer), std::move(ids));
- query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
-
- vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", query.d_idstate.qname.toLogString(), QType(query.d_idstate.qtype).toString(), state->d_proxiedRemote.toStringWithPort(), (state->d_handler.isTLS() ? "DoT" : "TCP"), query.d_buffer.size(), ds->getNameWithAddr());
- std::shared_ptr<TCPQuerySender> incoming = state;
- downstreamConnection->queueQuery(incoming, std::move(query));
-}
-
-void IncomingTCPConnectionState::handleIOCallback(int fd, FDMultiplexer::funcparam_t& param)
-{
- auto conn = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
- if (fd != conn->d_handler.getDescriptor()) {
- throw std::runtime_error("Unexpected socket descriptor " + std::to_string(fd) + " received in " + std::string(__PRETTY_FUNCTION__) + ", expected " + std::to_string(conn->d_handler.getDescriptor()));
- }
-
- struct timeval now;
- gettimeofday(&now, nullptr);
- handleIO(conn, now);
-}
-
-void IncomingTCPConnectionState::handleIO(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
-{
- // why do we loop? Because the TLS layer does buffering, and thus can have data ready to read
- // even though the underlying socket is not ready, so we need to actually ask for the data first
- IOState iostate = IOState::Done;
- do {
- iostate = IOState::Done;
- IOStateGuard ioGuard(state->d_ioState);
-
- if (state->maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
- vinfolog("Terminating TCP connection from %s because it reached the maximum TCP connection duration", state->d_ci.remote.toStringWithPort());
- // will be handled by the ioGuard
- //handleNewIOState(state, IOState::Done, fd, handleIOCallback);
- return;
- }
-
- state->d_lastIOBlocked = false;
-
- try {
- if (state->d_state == IncomingTCPConnectionState::State::doingHandshake) {
- DEBUGLOG("doing handshake");
- iostate = state->d_handler.tryHandshake();
- if (iostate == IOState::Done) {
- DEBUGLOG("handshake done");
- if (state->d_handler.isTLS()) {
- if (!state->d_handler.hasTLSSessionBeenResumed()) {
- ++state->d_ci.cs->tlsNewSessions;
- }
- else {
- ++state->d_ci.cs->tlsResumptions;
- }
- if (state->d_handler.getResumedFromInactiveTicketKey()) {
- ++state->d_ci.cs->tlsInactiveTicketKey;
- }
- if (state->d_handler.getUnknownTicketKey()) {
- ++state->d_ci.cs->tlsUnknownTicketKey;
- }
- }
-
- state->d_handshakeDoneTime = now;
- if (expectProxyProtocolFrom(state->d_ci.remote)) {
- state->d_state = IncomingTCPConnectionState::State::readingProxyProtocolHeader;
- state->d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
- state->d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
- }
- else {
- state->d_state = IncomingTCPConnectionState::State::readingQuerySize;
- }
- }
- else {
- state->d_lastIOBlocked = true;
- }
- }
-
- if (!state->d_lastIOBlocked && state->d_state == IncomingTCPConnectionState::State::readingProxyProtocolHeader) {
- do {
- DEBUGLOG("reading proxy protocol header");
- iostate = state->d_handler.tryRead(state->d_buffer, state->d_currentPos, state->d_proxyProtocolNeed);
- if (iostate == IOState::Done) {
- state->d_buffer.resize(state->d_currentPos);
- ssize_t remaining = isProxyHeaderComplete(state->d_buffer);
- if (remaining == 0) {
- vinfolog("Unable to consume proxy protocol header in packet from TCP client %s", state->d_ci.remote.toStringWithPort());
- ++g_stats.proxyProtocolInvalid;
- break;
- }
- else if (remaining < 0) {
- state->d_proxyProtocolNeed += -remaining;
- state->d_buffer.resize(state->d_currentPos + state->d_proxyProtocolNeed);
- /* we need to keep reading, since we might have buffered data */
- iostate = IOState::NeedRead;
- }
- else {
- /* proxy header received */
- std::vector<ProxyProtocolValue> proxyProtocolValues;
- if (!handleProxyProtocol(state->d_ci.remote, true, *state->d_threadData.holders.acl, state->d_buffer, state->d_proxiedRemote, state->d_proxiedDestination, proxyProtocolValues)) {
- vinfolog("Error handling the Proxy Protocol received from TCP client %s", state->d_ci.remote.toStringWithPort());
- break;
- }
-
- if (!proxyProtocolValues.empty()) {
- state->d_proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
- }
-
- state->d_state = IncomingTCPConnectionState::State::readingQuerySize;
- state->d_buffer.resize(sizeof(uint16_t));
- state->d_currentPos = 0;
- state->d_proxyProtocolNeed = 0;
- break;
- }
- }
- else {
- state->d_lastIOBlocked = true;
- }
- }
- while (state->active() && !state->d_lastIOBlocked);
- }
-
- if (!state->d_lastIOBlocked && (state->d_state == IncomingTCPConnectionState::State::waitingForQuery ||
- state->d_state == IncomingTCPConnectionState::State::readingQuerySize)) {
- DEBUGLOG("reading query size");
- iostate = state->d_handler.tryRead(state->d_buffer, state->d_currentPos, sizeof(uint16_t));
- if (state->d_currentPos > 0) {
- /* if we got at least one byte, we can't go around sending responses */
- state->d_state = IncomingTCPConnectionState::State::readingQuerySize;
- }
-
- if (iostate == IOState::Done) {
- DEBUGLOG("query size received");
- state->d_state = IncomingTCPConnectionState::State::readingQuery;
- state->d_querySizeReadTime = now;
- if (state->d_queriesCount == 0) {
- state->d_firstQuerySizeReadTime = now;
- }
- state->d_querySize = state->d_buffer.at(0) * 256 + state->d_buffer.at(1);
- if (state->d_querySize < sizeof(dnsheader)) {
- /* go away */
- state->terminateClientConnection();
- return;
- }
-
- /* allocate a bit more memory to be able to spoof the content, get an answer from the cache
- or to add ECS without allocating a new buffer */
- state->d_buffer.resize(std::max(state->d_querySize + static_cast<size_t>(512), s_maxPacketCacheEntrySize));
- state->d_currentPos = 0;
- }
- else {
- state->d_lastIOBlocked = true;
- }
- }
-
- if (!state->d_lastIOBlocked && state->d_state == IncomingTCPConnectionState::State::readingQuery) {
- DEBUGLOG("reading query");
- iostate = state->d_handler.tryRead(state->d_buffer, state->d_currentPos, state->d_querySize);
- if (iostate == IOState::Done) {
- DEBUGLOG("query received");
- state->d_buffer.resize(state->d_querySize);
-
- state->d_state = IncomingTCPConnectionState::State::idle;
- handleQuery(state, now);
- /* the state might have been updated in the meantime, we don't want to override it
- in that case */
- if (state->active() && state->d_state != IncomingTCPConnectionState::State::idle) {
- if (state->d_ioState->isWaitingForRead()) {
- iostate = IOState::NeedRead;
- }
- else if (state->d_ioState->isWaitingForWrite()) {
- iostate = IOState::NeedWrite;
- }
- else {
- iostate = IOState::Done;
- }
- }
- }
- else {
- state->d_lastIOBlocked = true;
- }
- }
-
- if (!state->d_lastIOBlocked && state->d_state == IncomingTCPConnectionState::State::sendingResponse) {
- DEBUGLOG("sending response");
- iostate = state->d_handler.tryWrite(state->d_currentResponse.d_buffer, state->d_currentPos, state->d_currentResponse.d_buffer.size());
- if (iostate == IOState::Done) {
- DEBUGLOG("response sent from "<<__PRETTY_FUNCTION__);
- handleResponseSent(state, state->d_currentResponse);
- state->d_state = IncomingTCPConnectionState::State::idle;
- }
- else {
- state->d_lastIOBlocked = true;
- }
- }
-
- if (state->active() &&
- !state->d_lastIOBlocked &&
- iostate == IOState::Done &&
- (state->d_state == IncomingTCPConnectionState::State::idle ||
- state->d_state == IncomingTCPConnectionState::State::waitingForQuery))
- {
- // try sending queued responses
- DEBUGLOG("send responses, if any");
- iostate = sendQueuedResponses(state, now);
-
- if (!state->d_lastIOBlocked && state->active() && iostate == IOState::Done) {
- // if the query has been passed to a backend, or dropped, and the responses have been sent,
- // we can start reading again
- if (state->canAcceptNewQueries(now)) {
- state->resetForNewQuery();
- iostate = IOState::NeedRead;
- }
- else {
- state->d_state = IncomingTCPConnectionState::State::idle;
- iostate = IOState::Done;
- }
- }
- }
-
- if (state->d_state != IncomingTCPConnectionState::State::idle &&
- state->d_state != IncomingTCPConnectionState::State::doingHandshake &&
- state->d_state != IncomingTCPConnectionState::State::readingProxyProtocolHeader &&
- state->d_state != IncomingTCPConnectionState::State::waitingForQuery &&
- state->d_state != IncomingTCPConnectionState::State::readingQuerySize &&
- state->d_state != IncomingTCPConnectionState::State::readingQuery &&
- state->d_state != IncomingTCPConnectionState::State::sendingResponse) {
- vinfolog("Unexpected state %d in handleIOCallback", static_cast<int>(state->d_state));
- }
- }
- catch (const std::exception& e) {
- /* most likely an EOF because the other end closed the connection,
- but it might also be a real IO error or something else.
- Let's just drop the connection
- */
- if (state->d_state == IncomingTCPConnectionState::State::idle ||
- state->d_state == IncomingTCPConnectionState::State::waitingForQuery) {
- /* no need to increase any counters in that case, the client is simply done with us */
- }
- else if (state->d_state == IncomingTCPConnectionState::State::doingHandshake ||
- state->d_state != IncomingTCPConnectionState::State::readingProxyProtocolHeader ||
- state->d_state == IncomingTCPConnectionState::State::waitingForQuery ||
- state->d_state == IncomingTCPConnectionState::State::readingQuerySize ||
- state->d_state == IncomingTCPConnectionState::State::readingQuery) {
- ++state->d_ci.cs->tcpDiedReadingQuery;
- }
- else if (state->d_state == IncomingTCPConnectionState::State::sendingResponse) {
- /* unlikely to happen here, the exception should be handled in sendResponse() */
- ++state->d_ci.cs->tcpDiedSendingResponse;
- }
-
- if (state->d_ioState->isWaitingForWrite() || state->d_queriesCount == 0) {
- DEBUGLOG("Got an exception while handling TCP query: "<<e.what());
- vinfolog("Got an exception while handling (%s) TCP query from %s: %s", (state->d_ioState->isWaitingForRead() ? "reading" : "writing"), state->d_ci.remote.toStringWithPort(), e.what());
- }
- else {
- vinfolog("Closing TCP client connection with %s: %s", state->d_ci.remote.toStringWithPort(), e.what());
- DEBUGLOG("Closing TCP client connection: "<<e.what());
- }
- /* remove this FD from the IO multiplexer */
- state->terminateClientConnection();
- }
-
- if (!state->active()) {
- DEBUGLOG("state is no longer active");
- return;
- }
-
- if (iostate == IOState::Done) {
- state->d_ioState->update(iostate, handleIOCallback, state);
- }
- else {
- updateIO(state, iostate, now);
- }
- ioGuard.release();
- }
- while ((iostate == IOState::NeedRead || iostate == IOState::NeedWrite) && !state->d_lastIOBlocked);
-}
-
-void IncomingTCPConnectionState::notifyIOError(InternalQueryState&& query, const struct timeval& now)
-{
- if (std::this_thread::get_id() != d_creatorThreadID) {
- /* empty buffer will signal an IO error */
- TCPResponse response(PacketBuffer(), std::move(query), nullptr, nullptr);
- handleCrossProtocolResponse(now, std::move(response));
- return;
- }
-
- std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
- --state->d_currentQueriesCount;
- state->d_hadErrors = true;
-
- if (state->d_state == State::sendingResponse) {
- /* if we have responses to send, let's do that first */
- }
- else if (!state->d_queuedResponses.empty()) {
- /* stop reading and send what we have */
- try {
- auto iostate = sendQueuedResponses(state, now);
-
- if (state->active() && iostate != IOState::Done) {
- // we need to update the state right away, nobody will do that for us
- updateIO(state, iostate, now);
- }
- }
- catch (const std::exception& e) {
- vinfolog("Exception in notifyIOError: %s", e.what());
- }
- }
- else {
- // the backend code already tried to reconnect if it was possible
- state->terminateClientConnection();
- }
-}
-
-void IncomingTCPConnectionState::handleXFRResponse(const struct timeval& now, TCPResponse&& response)
-{
- if (std::this_thread::get_id() != d_creatorThreadID) {
- handleCrossProtocolResponse(now, std::move(response));
- return;
- }
-
- std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
- queueResponse(state, now, std::move(response));
-}
-
-void IncomingTCPConnectionState::handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bool write)
-{
- vinfolog("Timeout while %s TCP client %s", (write ? "writing to" : "reading from"), state->d_ci.remote.toStringWithPort());
- DEBUGLOG("client timeout");
- DEBUGLOG("Processed "<<state->d_queriesCount<<" queries, current count is "<<state->d_currentQueriesCount<<", "<<state->d_ownedConnectionsToBackend.size()<<" owned connections, "<<state->d_queuedResponses.size()<<" response queued");
-
- if (write || state->d_currentQueriesCount == 0) {
- ++state->d_ci.cs->tcpClientTimeouts;
- state->d_ioState.reset();
- }
- else {
- DEBUGLOG("Going idle");
- /* we still have some queries in flight, let's just stop reading for now */
- state->d_state = IncomingTCPConnectionState::State::idle;
- state->d_ioState->update(IOState::Done, handleIOCallback, state);
- }
-}
-
-static void handleIncomingTCPQuery(int pipefd, FDMultiplexer::funcparam_t& param)
-{
- auto threadData = boost::any_cast<TCPClientThreadData*>(param);
-
- ConnectionInfo* citmp{nullptr};
-
- ssize_t got = read(pipefd, &citmp, sizeof(citmp));
- if (got == 0) {
- throw std::runtime_error("EOF while reading from the TCP acceptor pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
- }
- else if (got == -1) {
- if (errno == EAGAIN || errno == EINTR) {
- return;
- }
- throw std::runtime_error("Error while reading from the TCP acceptor pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode:" + stringerror());
- }
- else if (got != sizeof(citmp)) {
- throw std::runtime_error("Partial read while reading from the TCP acceptor pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
- }
-
- try {
- g_tcpclientthreads->decrementQueuedCount();
-
- struct timeval now;
- gettimeofday(&now, nullptr);
- auto state = std::make_shared<IncomingTCPConnectionState>(std::move(*citmp), *threadData, now);
- delete citmp;
- citmp = nullptr;
-
- IncomingTCPConnectionState::handleIO(state, now);
- }
- catch (...) {
- delete citmp;
- citmp = nullptr;
- throw;
- }
-}
-
-static void handleCrossProtocolQuery(int pipefd, FDMultiplexer::funcparam_t& param)
-{
- auto threadData = boost::any_cast<TCPClientThreadData*>(param);
- CrossProtocolQuery* tmp{nullptr};
-
- ssize_t got = read(pipefd, &tmp, sizeof(tmp));
- if (got == 0) {
- throw std::runtime_error("EOF while reading from the TCP cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
- }
- else if (got == -1) {
- if (errno == EAGAIN || errno == EINTR) {
- return;
- }
- throw std::runtime_error("Error while reading from the TCP cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode:" + stringerror());
- }
- else if (got != sizeof(tmp)) {
- throw std::runtime_error("Partial read while reading from the TCP cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
- }
-
- try {
- struct timeval now;
- gettimeofday(&now, nullptr);
-
- std::shared_ptr<TCPQuerySender> tqs = tmp->getTCPQuerySender();
- auto query = std::move(tmp->query);
- auto downstreamServer = std::move(tmp->downstream);
- auto proxyProtocolPayloadSize = tmp->proxyProtocolPayloadSize;
- delete tmp;
- tmp = nullptr;
-
- try {
- auto downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::string());
-
- prependSizeToTCPQuery(query.d_buffer, proxyProtocolPayloadSize);
- query.d_proxyProtocolPayloadAddedSize = proxyProtocolPayloadSize;
-
- vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", query.d_idstate.qname.toLogString(), QType(query.d_idstate.qtype).toString(), query.d_idstate.origRemote.toStringWithPort(), query.d_idstate.protocol.toString(), query.d_buffer.size(), downstreamServer->getNameWithAddr());
-
- downstream->queueQuery(tqs, std::move(query));
- }
- catch (...) {
- tqs->notifyIOError(std::move(query.d_idstate), now);
- }
- }
- catch (...) {
- delete tmp;
- tmp = nullptr;
- }
-}
-
-static void handleCrossProtocolResponse(int pipefd, FDMultiplexer::funcparam_t& param)
-{
- TCPCrossProtocolResponse* tmp{nullptr};
-
- ssize_t got = read(pipefd, &tmp, sizeof(tmp));
- if (got == 0) {
- throw std::runtime_error("EOF while reading from the TCP cross-protocol response pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
- }
- else if (got == -1) {
- if (errno == EAGAIN || errno == EINTR) {
- return;
- }
- throw std::runtime_error("Error while reading from the TCP cross-protocol response pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode:" + stringerror());
- }
- else if (got != sizeof(tmp)) {
- throw std::runtime_error("Partial read while reading from the TCP cross-protocol response pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
- }
-
- auto response = std::move(*tmp);
- delete tmp;
- tmp = nullptr;
-
- try {
- if (response.d_response.d_buffer.empty()) {
- response.d_state->notifyIOError(std::move(response.d_response.d_idstate), response.d_now);
- }
- else if (response.d_response.d_idstate.qtype == QType::AXFR || response.d_response.d_idstate.qtype == QType::IXFR) {
- response.d_state->handleXFRResponse(response.d_now, std::move(response.d_response));
- }
- else {
- response.d_state->handleResponse(response.d_now, std::move(response.d_response));
- }
- }
- catch (...) {
- /* no point bubbling up from there */
- }
-}
-
-struct TCPAcceptorParam
-{
- ClientState& cs;
- ComboAddress local;
- LocalStateHolder<NetmaskGroup>& acl;
- int socket{-1};
-};
-
-static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData);
-
-static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int crossProtocolResponsesListenPipeFD, int crossProtocolResponsesWritePipeFD, std::vector<ClientState*> tcpAcceptStates)
-{
- /* we get launched with a pipe on which we receive file descriptors from clients that we own
- from that point on */
-
- setThreadName("dnsdist/tcpClie");
-
- try {
- TCPClientThreadData data;
- /* this is the writing end! */
- data.crossProtocolResponsesPipe = crossProtocolResponsesWritePipeFD;
- data.mplexer->addReadFD(pipefd, handleIncomingTCPQuery, &data);
- data.mplexer->addReadFD(crossProtocolQueriesPipeFD, handleCrossProtocolQuery, &data);
- data.mplexer->addReadFD(crossProtocolResponsesListenPipeFD, handleCrossProtocolResponse, &data);
-
- /* only used in single acceptor mode for now */
- auto acl = g_ACL.getLocal();
- std::vector<TCPAcceptorParam> acceptParams;
- acceptParams.reserve(tcpAcceptStates.size());
-
- for (auto& state : tcpAcceptStates) {
- acceptParams.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD});
- for (const auto& [addr, socket] : state->d_additionalAddresses) {
- acceptParams.emplace_back(TCPAcceptorParam{*state, addr, acl, socket});
- }
- }
-
- auto acceptCallback = [&data](int socket, FDMultiplexer::funcparam_t& funcparam) {
- auto acceptorParam = boost::any_cast<const TCPAcceptorParam*>(funcparam);
- acceptNewConnection(*acceptorParam, &data);
- };
-
- for (size_t idx = 0; idx < acceptParams.size(); idx++) {
- const auto& param = acceptParams.at(idx);
- setNonBlocking(param.socket);
- data.mplexer->addReadFD(param.socket, acceptCallback, ¶m);
- }
-
- struct timeval now;
- gettimeofday(&now, nullptr);
- time_t lastTimeoutScan = now.tv_sec;
-
- for (;;) {
- data.mplexer->run(&now);
-
- try {
- t_downstreamTCPConnectionsManager.cleanupClosedConnections(now);
-
- if (now.tv_sec > lastTimeoutScan) {
- lastTimeoutScan = now.tv_sec;
- auto expiredReadConns = data.mplexer->getTimeouts(now, false);
- for (const auto& cbData : expiredReadConns) {
- if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
- auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
- if (cbData.first == state->d_handler.getDescriptor()) {
- vinfolog("Timeout (read) from remote TCP client %s", state->d_ci.remote.toStringWithPort());
- state->handleTimeout(state, false);
- }
- }
- else if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
- auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
- vinfolog("Timeout (read) from remote backend %s", conn->getBackendName());
- conn->handleTimeout(now, false);
- }
- }
-
- auto expiredWriteConns = data.mplexer->getTimeouts(now, true);
- for (const auto& cbData : expiredWriteConns) {
- if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
- auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
- if (cbData.first == state->d_handler.getDescriptor()) {
- vinfolog("Timeout (write) from remote TCP client %s", state->d_ci.remote.toStringWithPort());
- state->handleTimeout(state, true);
- }
- }
- else if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
- auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
- vinfolog("Timeout (write) from remote backend %s", conn->getBackendName());
- conn->handleTimeout(now, true);
- }
- }
-
- if (g_tcpStatesDumpRequested > 0) {
- /* just to keep things clean in the output, debug only */
- static std::mutex s_lock;
- std::lock_guard<decltype(s_lock)> lck(s_lock);
- if (g_tcpStatesDumpRequested > 0) {
- /* no race here, we took the lock so it can only be increased in the meantime */
- --g_tcpStatesDumpRequested;
- errlog("Dumping the TCP states, as requested:");
- data.mplexer->runForAllWatchedFDs([](bool isRead, int fd, const FDMultiplexer::funcparam_t& param, struct timeval ttd)
- {
- struct timeval lnow;
- gettimeofday(&lnow, nullptr);
- if (ttd.tv_sec > 0) {
- errlog("- Descriptor %d is in %s state, TTD in %d", fd, (isRead ? "read" : "write"), (ttd.tv_sec-lnow.tv_sec));
- }
- else {
- errlog("- Descriptor %d is in %s state, no TTD set", fd, (isRead ? "read" : "write"));
- }
-
- if (param.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
- auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
- errlog(" - %s", state->toString());
- }
- else if (param.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
- auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(param);
- errlog(" - %s", conn->toString());
- }
- else if (param.type() == typeid(TCPClientThreadData*)) {
- errlog(" - Worker thread pipe");
- }
- });
- errlog("The TCP/DoT client cache has %d active and %d idle outgoing connections cached", t_downstreamTCPConnectionsManager.getActiveCount(), t_downstreamTCPConnectionsManager.getIdleCount());
- }
- }
- }
- }
- catch (const std::exception& e) {
- errlog("Error in TCP worker thread: %s", e.what());
- }
- }
- }
- catch (const std::exception& e) {
- errlog("Fatal error in TCP worker thread: %s", e.what());
- }
-}
-
-static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData)
-{
- auto& cs = param.cs;
- auto& acl = param.acl;
- int socket = param.socket;
- bool tcpClientCountIncremented = false;
- ComboAddress remote;
- remote.sin4.sin_family = param.local.sin4.sin_family;
-
- tcpClientCountIncremented = false;
- try {
- socklen_t remlen = remote.getSocklen();
- ConnectionInfo ci(&cs);
-#ifdef HAVE_ACCEPT4
- ci.fd = accept4(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen, SOCK_NONBLOCK);
-#else
- ci.fd = accept(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen);
-#endif
- // will be decremented when the ConnectionInfo object is destroyed, no matter the reason
- auto concurrentConnections = ++cs.tcpCurrentConnections;
-
- if (ci.fd < 0) {
- throw std::runtime_error((boost::format("accepting new connection on socket: %s") % stringerror()).str());
- }
-
- if (!acl->match(remote)) {
- ++g_stats.aclDrops;
- vinfolog("Dropped TCP connection from %s because of ACL", remote.toStringWithPort());
- return;
- }
-
- if (cs.d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > cs.d_tcpConcurrentConnectionsLimit) {
- vinfolog("Dropped TCP connection from %s because of concurrent connections limit", remote.toStringWithPort());
- return;
- }
-
- if (concurrentConnections > cs.tcpMaxConcurrentConnections.load()) {
- cs.tcpMaxConcurrentConnections.store(concurrentConnections);
- }
-
-#ifndef HAVE_ACCEPT4
- if (!setNonBlocking(ci.fd)) {
- return;
- }
-#endif
-
- setTCPNoDelay(ci.fd); // disable NAGLE
-
- if (g_maxTCPQueuedConnections > 0 && g_tcpclientthreads->getQueuedCount() >= g_maxTCPQueuedConnections) {
- vinfolog("Dropping TCP connection from %s because we have too many queued already", remote.toStringWithPort());
- return;
- }
-
- if (!dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(remote)) {
- vinfolog("Dropping TCP connection from %s because we have too many from this client already", remote.toStringWithPort());
- return;
- }
- tcpClientCountIncremented = true;
-
- vinfolog("Got TCP connection from %s", remote.toStringWithPort());
-
- ci.remote = remote;
- if (threadData == nullptr) {
- if (!g_tcpclientthreads->passConnectionToThread(std::make_unique<ConnectionInfo>(std::move(ci)))) {
- if (tcpClientCountIncremented) {
- dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
- }
- }
- }
- else {
- struct timeval now;
- gettimeofday(&now, nullptr);
- auto state = std::make_shared<IncomingTCPConnectionState>(std::move(ci), *threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
- }
- }
- catch (const std::exception& e) {
- errlog("While reading a TCP question: %s", e.what());
- if (tcpClientCountIncremented) {
- dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
- }
- }
- catch (...){}
-}
-
-/* spawn as many of these as required, they call Accept on a socket on which they will accept queries, and
- they will hand off to worker threads & spawn more of them if required
-*/
-#ifndef USE_SINGLE_ACCEPTOR_THREAD
-void tcpAcceptorThread(std::vector<ClientState*> states)
-{
- setThreadName("dnsdist/tcpAcce");
-
- auto acl = g_ACL.getLocal();
- std::vector<TCPAcceptorParam> params;
- params.reserve(states.size());
-
- for (auto& state : states) {
- params.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD});
- for (const auto& [addr, socket] : state->d_additionalAddresses) {
- params.emplace_back(TCPAcceptorParam{*state, addr, acl, socket});
- }
- }
-
- if (params.size() == 1) {
- while (true) {
- acceptNewConnection(params.at(0), nullptr);
- }
- }
- else {
- auto acceptCallback = [](int socket, FDMultiplexer::funcparam_t& funcparam) {
- auto acceptorParam = boost::any_cast<const TCPAcceptorParam*>(funcparam);
- acceptNewConnection(*acceptorParam, nullptr);
- };
-
- auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
- for (size_t idx = 0; idx < params.size(); idx++) {
- const auto& param = params.at(idx);
- mplexer->addReadFD(param.socket, acceptCallback, ¶m);
- }
-
- struct timeval tv;
- while (true) {
- mplexer->run(&tv, -1);
- }
- }
-}
-#endif
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <boost/format.hpp>
-#include <sstream>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <thread>
-
-#include "ext/json11/json11.hpp"
-#include <yahttp/yahttp.hpp>
-
-#include "base64.hh"
-#include "connection-management.hh"
-#include "dnsdist.hh"
-#include "dnsdist-dynblocks.hh"
-#include "dnsdist-healthchecks.hh"
-#include "dnsdist-prometheus.hh"
-#include "dnsdist-web.hh"
-#include "dolog.hh"
-#include "gettime.hh"
-#include "threadname.hh"
-#include "sstuff.hh"
-
-struct WebserverConfig
-{
- WebserverConfig()
- {
- acl.toMasks("127.0.0.1, ::1");
- }
-
- NetmaskGroup acl;
- std::unique_ptr<CredentialsHolder> password;
- std::unique_ptr<CredentialsHolder> apiKey;
- boost::optional<std::unordered_map<std::string, std::string> > customHeaders;
- bool apiRequiresAuthentication{true};
- bool dashboardRequiresAuthentication{true};
- bool statsRequireAuthentication{true};
-};
-
-bool g_apiReadWrite{false};
-LockGuarded<WebserverConfig> g_webserverConfig;
-std::string g_apiConfigDirectory;
-
-static ConcurrentConnectionManager s_connManager(100);
-
-std::string getWebserverConfig()
-{
- ostringstream out;
-
- {
- auto config = g_webserverConfig.lock();
- out << "Current web server configuration:" << endl;
- out << "ACL: " << config->acl.toString() << endl;
- out << "Custom headers: ";
- if (config->customHeaders) {
- out << endl;
- for (const auto& header : *config->customHeaders) {
- out << " - " << header.first << ": " << header.second << endl;
- }
- }
- else {
- out << "None" << endl;
- }
- out << "API requires authentication: " << (config->apiRequiresAuthentication ? "yes" : "no") << endl;
- out << "Dashboard requires authentication: " << (config->dashboardRequiresAuthentication ? "yes" : "no") << endl;
- out << "Statistics require authentication: " << (config->statsRequireAuthentication ? "yes" : "no") << endl;
- out << "Password: " << (config->password ? "set" : "unset") << endl;
- out << "API key: " << (config->apiKey ? "set" : "unset") << endl;
- }
- out << "API writable: " << (g_apiReadWrite ? "yes" : "no") << endl;
- out << "API configuration directory: " << g_apiConfigDirectory << endl;
- out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl;
-
- return out.str();
-}
-
-class WebClientConnection
-{
-public:
- WebClientConnection(const ComboAddress& client, int fd): d_client(client), d_socket(fd)
- {
- if (!s_connManager.registerConnection()) {
- throw std::runtime_error("Too many concurrent web client connections");
- }
- }
- WebClientConnection(WebClientConnection&& rhs): d_client(rhs.d_client), d_socket(std::move(rhs.d_socket))
- {
- }
-
- WebClientConnection(const WebClientConnection&) = delete;
- WebClientConnection& operator=(const WebClientConnection&) = delete;
-
- ~WebClientConnection()
- {
- if (d_socket.getHandle() != -1) {
- s_connManager.releaseConnection();
- }
- }
-
- const Socket& getSocket() const
- {
- return d_socket;
- }
-
- const ComboAddress& getClient() const
- {
- return d_client;
- }
-
-private:
- ComboAddress d_client;
- Socket d_socket;
-};
-
-#ifndef DISABLE_PROMETHEUS
-static MetricDefinitionStorage s_metricDefinitions;
-
-std::map<std::string, MetricDefinition> MetricDefinitionStorage::metrics{
- { "responses", MetricDefinition(PrometheusMetricType::counter, "Number of responses received from backends") },
- { "servfail-responses", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received from backends") },
- { "queries", MetricDefinition(PrometheusMetricType::counter, "Number of received queries")},
- { "frontend-nxdomain", MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers sent to clients")},
- { "frontend-servfail", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers sent to clients")},
- { "frontend-noerror", MetricDefinition(PrometheusMetricType::counter, "Number of NoError answers sent to clients")},
- { "acl-drops", MetricDefinition(PrometheusMetricType::counter, "Number of packets dropped because of the ACL")},
- { "rule-drop", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a rule")},
- { "rule-nxdomain", MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers returned because of a rule")},
- { "rule-refused", MetricDefinition(PrometheusMetricType::counter, "Number of Refused answers returned because of a rule")},
- { "rule-servfail", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received because of a rule")},
- { "rule-truncated", MetricDefinition(PrometheusMetricType::counter, "Number of truncated answers returned because of a rule")},
- { "self-answered", MetricDefinition(PrometheusMetricType::counter, "Number of self-answered responses")},
- { "downstream-timeouts", MetricDefinition(PrometheusMetricType::counter, "Number of queries not answered in time by a backend")},
- { "downstream-send-errors", MetricDefinition(PrometheusMetricType::counter, "Number of errors when sending a query to a backend")},
- { "trunc-failures", MetricDefinition(PrometheusMetricType::counter, "Number of errors encountered while truncating an answer")},
- { "no-policy", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because no server was available")},
- { "latency0-1", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in less than 1ms")},
- { "latency1-10", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 1-10 ms")},
- { "latency10-50", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 10-50 ms")},
- { "latency50-100", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 50-100 ms")},
- { "latency100-1000", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 100-1000 ms")},
- { "latency-slow", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in more than 1 second")},
- { "latency-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 100 packets")},
- { "latency-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 1000 packets")},
- { "latency-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 10000 packets")},
- { "latency-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 1000000 packets")},
- { "latency-tcp-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over TCP")},
- { "latency-tcp-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over TCP")},
- { "latency-tcp-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over TCP")},
- { "latency-tcp-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over TCP")},
- { "latency-dot-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoT")},
- { "latency-dot-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoT")},
- { "latency-dot-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoT")},
- { "latency-dot-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoT")},
- { "latency-doh-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoH")},
- { "latency-doh-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoH")},
- { "latency-doh-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoH")},
- { "latency-doh-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoH")},
- { "uptime", MetricDefinition(PrometheusMetricType::gauge, "Uptime of the dnsdist process in seconds")},
- { "real-memory-usage", MetricDefinition(PrometheusMetricType::gauge, "Current memory usage in bytes")},
- { "noncompliant-queries", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped as non-compliant")},
- { "noncompliant-responses", MetricDefinition(PrometheusMetricType::counter, "Number of answers from a backend dropped as non-compliant")},
- { "rdqueries", MetricDefinition(PrometheusMetricType::counter, "Number of received queries with the recursion desired bit set")},
- { "empty-queries", MetricDefinition(PrometheusMetricType::counter, "Number of empty queries received from clients")},
- { "cache-hits", MetricDefinition(PrometheusMetricType::counter, "Number of times an answer was retrieved from cache")},
- { "cache-misses", MetricDefinition(PrometheusMetricType::counter, "Number of times an answer not found in the cache")},
- { "cpu-iowait", MetricDefinition(PrometheusMetricType::counter, "Time waiting for I/O to complete by the whole system, in units of USER_HZ")},
- { "cpu-user-msec", MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the user state")},
- { "cpu-steal", MetricDefinition(PrometheusMetricType::counter, "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ")},
- { "cpu-sys-msec", MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the system state")},
- { "fd-usage", MetricDefinition(PrometheusMetricType::gauge, "Number of currently used file descriptors")},
- { "dyn-blocked", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a dynamic block")},
- { "dyn-block-nmg-size", MetricDefinition(PrometheusMetricType::gauge, "Number of dynamic blocks entries") },
- { "security-status", MetricDefinition(PrometheusMetricType::gauge, "Security status of this software. 0=unknown, 1=OK, 2=upgrade recommended, 3=upgrade mandatory") },
- { "doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of DoH queries dropped because the internal pipe used to distribute queries was full") },
- { "doh-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of DoH responses dropped because the internal pipe used to distribute responses was full") },
- { "outgoing-doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of outgoing DoH queries dropped because the internal pipe used to distribute queries was full") },
- { "tcp-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP queries dropped because the internal pipe used to distribute queries was full") },
- { "tcp-cross-protocol-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol queries dropped because the internal pipe used to distribute queries was full") },
- { "tcp-cross-protocol-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol responses dropped because the internal pipe used to distribute queries was full") },
- { "udp-in-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InErrors") },
- { "udp-noport-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp NoPorts") },
- { "udp-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp RcvbufErrors") },
- { "udp-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp SndbufErrors") },
- { "udp-in-csum-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InCsumErrors") },
- { "udp6-in-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InErrors") },
- { "udp6-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6RcvbufErrors") },
- { "udp6-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6SndbufErrors") },
- { "udp6-noport-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6NoPorts") },
- { "udp6-in-csum-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InCsumErrors") },
- { "tcp-listen-overflows", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/netstat ListenOverflows") },
- { "proxy-protocol-invalid", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of an invalid Proxy Protocol header") },
-};
-#endif /* DISABLE_PROMETHEUS */
-
-bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customName) {
-#ifndef DISABLE_PROMETHEUS
- return MetricDefinitionStorage::addMetricDefinition(name, type, description, customName);
-#else
- return true;
-#endif /* DISABLE_PROMETHEUS */
-}
-
-#ifndef DISABLE_WEB_CONFIG
-static bool apiWriteConfigFile(const string& filebasename, const string& content)
-{
- if (!g_apiReadWrite) {
- errlog("Not writing content to %s since the API is read-only", filebasename);
- return false;
- }
-
- if (g_apiConfigDirectory.empty()) {
- vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename);
- return false;
- }
-
- string filename = g_apiConfigDirectory + "/" + filebasename + ".conf";
- ofstream ofconf(filename.c_str());
- if (!ofconf) {
- errlog("Could not open configuration fragment file '%s' for writing: %s", filename, stringerror());
- return false;
- }
- ofconf << "-- Generated by the REST API, DO NOT EDIT" << endl;
- ofconf << content << endl;
- ofconf.close();
- return true;
-}
-
-static void apiSaveACL(const NetmaskGroup& nmg)
-{
- vector<string> vec;
- nmg.toStringVector(&vec);
-
- string acl;
- for(const auto& s : vec) {
- if (!acl.empty()) {
- acl += ", ";
- }
- acl += "\"" + s + "\"";
- }
-
- string content = "setACL({" + acl + "})";
- apiWriteConfigFile("acl", content);
-}
-#endif /* DISABLE_WEB_CONFIG */
-
-static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& apiKey)
-{
- if (!apiKey) {
- return false;
- }
-
- const auto header = req.headers.find("x-api-key");
- if (header != req.headers.end()) {
- return apiKey->matches(header->second);
- }
-
- return false;
-}
-
-static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& password, bool dashboardRequiresAuthentication)
-{
- if (!dashboardRequiresAuthentication) {
- return true;
- }
-
- static const char basicStr[] = "basic ";
-
- const auto header = req.headers.find("authorization");
-
- if (header != req.headers.end() && toLower(header->second).find(basicStr) == 0) {
- string cookie = header->second.substr(sizeof(basicStr) - 1);
-
- string plain;
- B64Decode(cookie, plain);
-
- vector<string> cparts;
- stringtok(cparts, plain, ":");
-
- if (cparts.size() == 2) {
- if (password) {
- return password->matches(cparts.at(1));
- }
- return true;
- }
- }
-
- return false;
-}
-
-static bool isAnAPIRequest(const YaHTTP::Request& req)
-{
- return req.url.path.find("/api/") == 0;
-}
-
-static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request& req)
-{
- return req.url.path == "/api/v1/servers/localhost";
-}
-
-static bool isAStatsRequest(const YaHTTP::Request& req)
-{
- return req.url.path == "/jsonstat" || req.url.path == "/metrics";
-}
-
-static bool handleAuthorization(const YaHTTP::Request& req)
-{
- auto config = g_webserverConfig.lock();
-
- if (isAStatsRequest(req)) {
- if (config->statsRequireAuthentication) {
- /* Access to the stats is allowed for both API and Web users */
- return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
- }
- return true;
- }
-
- if (isAnAPIRequest(req)) {
- /* Access to the API requires a valid API key */
- if (!config->apiRequiresAuthentication || checkAPIKey(req, config->apiKey)) {
- return true;
- }
-
- return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
- }
-
- return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
-}
-
-static bool isMethodAllowed(const YaHTTP::Request& req)
-{
- if (req.method == "GET") {
- return true;
- }
- if (req.method == "PUT" && g_apiReadWrite) {
- if (req.url.path == "/api/v1/servers/localhost/config/allow-from") {
- return true;
- }
- }
-#ifndef DISABLE_WEB_CACHE_MANAGEMENT
- if (req.method == "DELETE") {
- if (req.url.path == "/api/v1/cache") {
- return true;
- }
- }
-#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
- return false;
-}
-
-static bool isClientAllowedByACL(const ComboAddress& remote)
-{
- return g_webserverConfig.lock()->acl.match(remote);
-}
-
-static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- const auto origin = req.headers.find("Origin");
- if (origin != req.headers.end()) {
- if (req.method == "OPTIONS") {
- /* Pre-flight request */
- if (g_apiReadWrite) {
- resp.headers["Access-Control-Allow-Methods"] = "GET, PUT";
- }
- else {
- resp.headers["Access-Control-Allow-Methods"] = "GET";
- }
- resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
- }
-
- resp.headers["Access-Control-Allow-Origin"] = origin->second;
-
- if (isAStatsRequest(req) || isAnAPIRequestAllowedWithWebAuth(req)) {
- resp.headers["Access-Control-Allow-Credentials"] = "true";
- }
- }
-}
-
-static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string> >& customHeaders)
-{
- static const std::vector<std::pair<std::string, std::string> > headers = {
- { "X-Content-Type-Options", "nosniff" },
- { "X-Frame-Options", "deny" },
- { "X-Permitted-Cross-Domain-Policies", "none" },
- { "X-XSS-Protection", "1; mode=block" },
- { "Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'" },
- };
-
- for (const auto& h : headers) {
- if (customHeaders) {
- const auto& custom = customHeaders->find(h.first);
- if (custom != customHeaders->end()) {
- continue;
- }
- }
- resp.headers[h.first] = h.second;
- }
-}
-
-static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string> >& customHeaders)
-{
- if (!customHeaders)
- return;
-
- for (const auto& c : *customHeaders) {
- if (!c.second.empty()) {
- resp.headers[c.first] = c.second;
- }
- }
-}
-
-template<typename T>
-static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>* someResponseRules)
-{
- using namespace json11;
- Json::array responseRules;
- int num=0;
- auto localResponseRules = someResponseRules->getLocal();
- responseRules.reserve(localResponseRules->size());
- for (const auto& a : *localResponseRules) {
- responseRules.push_back(Json::object{
- {"id", num++},
- {"creationOrder", (double)a.d_creationOrder},
- {"uuid", boost::uuids::to_string(a.d_id)},
- {"name", a.d_name},
- {"matches", (double)a.d_rule->d_matches},
- {"rule", a.d_rule->toString()},
- {"action", a.d_action->toString()},
- });
- }
- return responseRules;
-}
-
-#ifndef DISABLE_PROMETHEUS
-template<typename T>
-static void addRulesToPrometheusOutput(std::ostringstream& output, GlobalStateHolder<vector<T> >& rules)
-{
- auto localRules = rules.getLocal();
- for (const auto& entry : *localRules) {
- std::string id = !entry.d_name.empty() ? entry.d_name : boost::uuids::to_string(entry.d_id);
- output << "dnsdist_rule_hits{id=\"" << id << "\"} " << entry.d_rule->d_matches << "\n";
- }
-}
-
-static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- handleCORS(req, resp);
- resp.status = 200;
-
- std::ostringstream output;
- static const std::set<std::string> metricBlacklist = { "special-memory-usage", "latency-count", "latency-sum" };
- for (const auto& e : g_stats.entries) {
- const auto& metricName = std::get<0>(e);
-
- if (metricBlacklist.count(metricName) != 0) {
- continue;
- }
-
- MetricDefinition metricDetails;
- if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
- vinfolog("Do not have metric details for %s", metricName);
- continue;
- }
-
- const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
- if (prometheusTypeName.empty()) {
- vinfolog("Unknown Prometheus type for %s", metricName);
- continue;
- }
-
- // Prometheus suggest using '_' instead of '-'
- std::string prometheusMetricName;
- if (metricDetails.customName.empty()) {
- prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
- }
- else {
- prometheusMetricName = metricDetails.customName;
- }
-
- // for these we have the help and types encoded in the sources:
- output << "# HELP " << prometheusMetricName << " " << metricDetails.description << "\n";
- output << "# TYPE " << prometheusMetricName << " " << prometheusTypeName << "\n";
- output << prometheusMetricName << " ";
-
- if (const auto& val = boost::get<pdns::stat_t*>(&std::get<1>(e))) {
- output << (*val)->load();
- }
- else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&std::get<1>(e))) {
- output << (*adval)->load();
- }
- else if (const auto& dval = boost::get<double*>(&std::get<1>(e))) {
- output << **dval;
- }
- else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&std::get<1>(e))) {
- output << (*func)(std::get<0>(e));
- }
-
- output << "\n";
- }
-
- // Latency histogram buckets
- output << "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n";
- output << "# TYPE dnsdist_latency histogram\n";
- uint64_t latency_amounts = g_stats.latency0_1;
- output << "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts << "\n";
- latency_amounts += g_stats.latency1_10;
- output << "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts << "\n";
- latency_amounts += g_stats.latency10_50;
- output << "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts << "\n";
- latency_amounts += g_stats.latency50_100;
- output << "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts << "\n";
- latency_amounts += g_stats.latency100_1000;
- output << "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts << "\n";
- latency_amounts += g_stats.latencySlow; // Should be the same as latency_count
- output << "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts << "\n";
- output << "dnsdist_latency_sum " << g_stats.latencySum << "\n";
- output << "dnsdist_latency_count " << g_stats.latencyCount << "\n";
-
- auto states = g_dstates.getLocal();
- const string statesbase = "dnsdist_server_";
-
- output << "# HELP " << statesbase << "status " << "Whether this backend is up (1) or down (0)" << "\n";
- output << "# TYPE " << statesbase << "status " << "gauge" << "\n";
- output << "# HELP " << statesbase << "queries " << "Amount of queries relayed to server" << "\n";
- output << "# TYPE " << statesbase << "queries " << "counter" << "\n";
- output << "# HELP " << statesbase << "responses " << "Amount of responses received from this server" << "\n";
- output << "# TYPE " << statesbase << "responses " << "counter" << "\n";
- output << "# HELP " << statesbase << "noncompliantresponses " << "Amount of non-compliant responses received from this server" << "\n";
- output << "# TYPE " << statesbase << "noncompliantresponses " << "counter" << "\n";
- output << "# HELP " << statesbase << "drops " << "Amount of queries not answered by server" << "\n";
- output << "# TYPE " << statesbase << "drops " << "counter" << "\n";
- output << "# HELP " << statesbase << "latency " << "Server's latency when answering questions in milliseconds" << "\n";
- output << "# TYPE " << statesbase << "latency " << "gauge" << "\n";
- output << "# HELP " << statesbase << "senderrors " << "Total number of OS send errors while relaying queries" << "\n";
- output << "# TYPE " << statesbase << "senderrors " << "counter" << "\n";
- output << "# HELP " << statesbase << "outstanding " << "Current number of queries that are waiting for a backend response" << "\n";
- output << "# TYPE " << statesbase << "outstanding " << "gauge" << "\n";
- output << "# HELP " << statesbase << "order " << "The order in which this server is picked" << "\n";
- output << "# TYPE " << statesbase << "order " << "gauge" << "\n";
- output << "# HELP " << statesbase << "weight " << "The weight within the order in which this server is picked" << "\n";
- output << "# TYPE " << statesbase << "weight " << "gauge" << "\n";
- output << "# HELP " << statesbase << "tcpdiedsendingquery " << "The number of TCP I/O errors while sending the query" << "\n";
- output << "# TYPE " << statesbase << "tcpdiedsendingquery " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpdiedreadingresponse " << "The number of TCP I/O errors while reading the response" << "\n";
- output << "# TYPE " << statesbase << "tcpdiedreadingresponse " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpgaveup " << "The number of TCP connections failing after too many attempts" << "\n";
- output << "# TYPE " << statesbase << "tcpgaveup " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpconnecttimeouts " << "The number of TCP connect timeouts" << "\n";
- output << "# TYPE " << statesbase << "tcpconnecttimeouts " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpreadtimeouts " << "The number of TCP read timeouts" << "\n";
- output << "# TYPE " << statesbase << "tcpreadtimeouts " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpwritetimeouts " << "The number of TCP write timeouts" << "\n";
- output << "# TYPE " << statesbase << "tcpwritetimeouts " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpcurrentconnections " << "The number of current TCP connections" << "\n";
- output << "# TYPE " << statesbase << "tcpcurrentconnections " << "gauge" << "\n";
- output << "# HELP " << statesbase << "tcpmaxconcurrentconnections " << "The maximum number of concurrent TCP connections" << "\n";
- output << "# TYPE " << statesbase << "tcpmaxconcurrentconnections " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcptoomanyconcurrentconnections " << "Number of times we had to enforce the maximum number of concurrent TCP connections" << "\n";
- output << "# TYPE " << statesbase << "tcptoomanyconcurrentconnections " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpnewconnections " << "The number of established TCP connections in total" << "\n";
- output << "# TYPE " << statesbase << "tcpnewconnections " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpreusedconnections " << "The number of times a TCP connection has been reused" << "\n";
- output << "# TYPE " << statesbase << "tcpreusedconnections " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcpavgqueriesperconn " << "The average number of queries per TCP connection" << "\n";
- output << "# TYPE " << statesbase << "tcpavgqueriesperconn " << "gauge" << "\n";
- output << "# HELP " << statesbase << "tcpavgconnduration " << "The average duration of a TCP connection (ms)" << "\n";
- output << "# TYPE " << statesbase << "tcpavgconnduration " << "gauge" << "\n";
- output << "# HELP " << statesbase << "tlsresumptions " << "The number of times a TLS session has been resumed" << "\n";
- output << "# TYPE " << statesbase << "tlsresumptions " << "counter" << "\n";
- output << "# HELP " << statesbase << "tcplatency " << "Server's latency when answering TCP questions in milliseconds" << "\n";
- output << "# TYPE " << statesbase << "tcplatency " << "gauge" << "\n";
-
- for (const auto& state : *states) {
- string serverName;
-
- if (state->getName().empty()) {
- serverName = state->d_config.remote.toStringWithPort();
- }
- else {
- serverName = state->getName();
- }
-
- boost::replace_all(serverName, ".", "_");
-
- const std::string label = boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
- % serverName % state->d_config.remote.toStringWithPort());
-
- output << statesbase << "status" << label << " " << (state->isUp() ? "1" : "0") << "\n";
- output << statesbase << "queries" << label << " " << state->queries.load() << "\n";
- output << statesbase << "responses" << label << " " << state->responses.load() << "\n";
- output << statesbase << "noncompliantresponses" << label << " " << state->nonCompliantResponses.load() << "\n";
- output << statesbase << "drops" << label << " " << state->reuseds.load() << "\n";
- if (state->isUp()) {
- output << statesbase << "latency" << label << " " << state->latencyUsec/1000.0 << "\n";
- output << statesbase << "tcplatency" << label << " " << state->latencyUsecTCP/1000.0 << "\n";
- }
- output << statesbase << "senderrors" << label << " " << state->sendErrors.load() << "\n";
- output << statesbase << "outstanding" << label << " " << state->outstanding.load() << "\n";
- output << statesbase << "order" << label << " " << state->d_config.order << "\n";
- output << statesbase << "weight" << label << " " << state->d_config.d_weight << "\n";
- output << statesbase << "tcpdiedsendingquery" << label << " " << state->tcpDiedSendingQuery << "\n";
- output << statesbase << "tcpdiedreadingresponse" << label << " " << state->tcpDiedReadingResponse << "\n";
- output << statesbase << "tcpgaveup" << label << " " << state->tcpGaveUp << "\n";
- output << statesbase << "tcpreadtimeouts" << label << " " << state->tcpReadTimeouts << "\n";
- output << statesbase << "tcpwritetimeouts" << label << " " << state->tcpWriteTimeouts << "\n";
- output << statesbase << "tcpconnecttimeouts" << label << " " << state->tcpConnectTimeouts << "\n";
- output << statesbase << "tcpcurrentconnections" << label << " " << state->tcpCurrentConnections << "\n";
- output << statesbase << "tcpmaxconcurrentconnections" << label << " " << state->tcpMaxConcurrentConnections << "\n";
- output << statesbase << "tcptoomanyconcurrentconnections" << label << " " << state->tcpTooManyConcurrentConnections << "\n";
- output << statesbase << "tcpnewconnections" << label << " " << state->tcpNewConnections << "\n";
- output << statesbase << "tcpreusedconnections" << label << " " << state->tcpReusedConnections << "\n";
- output << statesbase << "tcpavgqueriesperconn" << label << " " << state->tcpAvgQueriesPerConnection << "\n";
- output << statesbase << "tcpavgconnduration" << label << " " << state->tcpAvgConnectionDuration << "\n";
- output << statesbase << "tlsresumptions" << label << " " << state->tlsResumptions << "\n";
- }
-
- const string frontsbase = "dnsdist_frontend_";
- output << "# HELP " << frontsbase << "queries " << "Amount of queries received by this frontend" << "\n";
- output << "# TYPE " << frontsbase << "queries " << "counter" << "\n";
- output << "# HELP " << frontsbase << "noncompliantqueries " << "Amount of non-compliant queries received by this frontend" << "\n";
- output << "# TYPE " << frontsbase << "noncompliantqueries " << "counter" << "\n";
- output << "# HELP " << frontsbase << "responses " << "Amount of responses sent by this frontend" << "\n";
- output << "# TYPE " << frontsbase << "responses " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n";
- output << "# TYPE " << frontsbase << "tcpdiedreadingquery " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n";
- output << "# TYPE " << frontsbase << "tcpdiedsendingresponse " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tcpgaveup " << "Amount of TCP connections terminated after too many attempts to get a connection to the backend" << "\n";
- output << "# TYPE " << frontsbase << "tcpgaveup " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tcpclientimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n";
- output << "# TYPE " << frontsbase << "tcpclientimeouts " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tcpdownstreamtimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n";
- output << "# TYPE " << frontsbase << "tcpdownstreamtimeouts " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n";
- output << "# TYPE " << frontsbase << "tcpcurrentconnections " << "gauge" << "\n";
- output << "# HELP " << frontsbase << "tcpmaxconcurrentconnections " << "Maximum number of concurrent incoming TCP connections from clients" << "\n";
- output << "# TYPE " << frontsbase << "tcpmaxconcurrentconnections " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n";
- output << "# TYPE " << frontsbase << "tcpavgqueriesperconnection " << "gauge" << "\n";
- output << "# HELP " << frontsbase << "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n";
- output << "# TYPE " << frontsbase << "tcpavgconnectionduration " << "gauge" << "\n";
- output << "# HELP " << frontsbase << "tlsqueries " << "Number of queries received by dnsdist over TLS, by TLS version" << "\n";
- output << "# TYPE " << frontsbase << "tlsqueries " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tlsnewsessions " << "Amount of new TLS sessions negotiated" << "\n";
- output << "# TYPE " << frontsbase << "tlsnewsessions " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tlsresumptions " << "Amount of TLS sessions resumed" << "\n";
- output << "# TYPE " << frontsbase << "tlsresumptions " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tlsunknownticketkeys " << "Amount of attempts to resume TLS session from an unknown key (possibly expired)" << "\n";
- output << "# TYPE " << frontsbase << "tlsunknownticketkeys " << "counter" << "\n";
- output << "# HELP " << frontsbase << "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n";
- output << "# TYPE " << frontsbase << "tlsinactiveticketkeys " << "counter" << "\n";
-
- output << "# HELP " << frontsbase << "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n";
- output << "# TYPE " << frontsbase << "tlshandshakefailures " << "counter" << "\n";
-
- std::map<std::string,uint64_t> frontendDuplicates;
- for (const auto& front : g_frontends) {
- if (front->udpFD == -1 && front->tcpFD == -1)
- continue;
-
- const string frontName = front->local.toStringWithPort();
- const string proto = front->getType();
- const string fullName = frontName + "_" + proto;
- uint64_t threadNumber = 0;
- auto dupPair = frontendDuplicates.emplace(fullName, 1);
- if (!dupPair.second) {
- threadNumber = dupPair.first->second;
- ++(dupPair.first->second);
- }
- const std::string label = boost::str(boost::format("{frontend=\"%1%\",proto=\"%2%\",thread=\"%3%\"} ")
- % frontName % proto % threadNumber);
-
- output << frontsbase << "queries" << label << front->queries.load() << "\n";
- output << frontsbase << "noncompliantqueries" << label << front->nonCompliantQueries.load() << "\n";
- output << frontsbase << "responses" << label << front->responses.load() << "\n";
- if (front->isTCP()) {
- output << frontsbase << "tcpdiedreadingquery" << label << front->tcpDiedReadingQuery.load() << "\n";
- output << frontsbase << "tcpdiedsendingresponse" << label << front->tcpDiedSendingResponse.load() << "\n";
- output << frontsbase << "tcpgaveup" << label << front->tcpGaveUp.load() << "\n";
- output << frontsbase << "tcpclientimeouts" << label << front->tcpClientTimeouts.load() << "\n";
- output << frontsbase << "tcpdownstreamtimeouts" << label << front->tcpDownstreamTimeouts.load() << "\n";
- output << frontsbase << "tcpcurrentconnections" << label << front->tcpCurrentConnections.load() << "\n";
- output << frontsbase << "tcpmaxconcurrentconnections" << label << front->tcpMaxConcurrentConnections.load() << "\n";
- output << frontsbase << "tcpavgqueriesperconnection" << label << front->tcpAvgQueriesPerConnection.load() << "\n";
- output << frontsbase << "tcpavgconnectionduration" << label << front->tcpAvgConnectionDuration.load() << "\n";
- if (front->hasTLS()) {
- output << frontsbase << "tlsnewsessions" << label << front->tlsNewSessions.load() << "\n";
- output << frontsbase << "tlsresumptions" << label << front->tlsResumptions.load() << "\n";
- output << frontsbase << "tlsunknownticketkeys" << label << front->tlsUnknownTicketKey.load() << "\n";
- output << frontsbase << "tlsinactiveticketkeys" << label << front->tlsInactiveTicketKey.load() << "\n";
-
- output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls10\"} " << front->tls10queries.load() << "\n";
- output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls11\"} " << front->tls11queries.load() << "\n";
- output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls12\"} " << front->tls12queries.load() << "\n";
- output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls13\"} " << front->tls13queries.load() << "\n";
- output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"unknown\"} " << front->tlsUnknownqueries.load() << "\n";
-
- const TLSErrorCounters* errorCounters = nullptr;
- if (front->tlsFrontend != nullptr) {
- errorCounters = &front->tlsFrontend->d_tlsCounters;
- }
- else if (front->dohFrontend != nullptr) {
- errorCounters = &front->dohFrontend->d_tlsCounters;
- }
-
- if (errorCounters != nullptr) {
- output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"dhKeyTooSmall\"} " << errorCounters->d_dhKeyTooSmall << "\n";
- output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"inappropriateFallBack\"} " << errorCounters->d_inappropriateFallBack << "\n";
- output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"noSharedCipher\"} " << errorCounters->d_noSharedCipher << "\n";
- output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownCipherType\"} " << errorCounters->d_unknownCipherType << "\n";
- output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownKeyExchangeType\"} " << errorCounters->d_unknownKeyExchangeType << "\n";
- output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownProtocol\"} " << errorCounters->d_unknownProtocol << "\n";
- output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unsupportedEC\"} " << errorCounters->d_unsupportedEC << "\n";
- output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unsupportedProtocol\"} " << errorCounters->d_unsupportedProtocol << "\n";
- }
- }
- }
- }
-
- output << "# HELP " << frontsbase << "http_connects " << "Number of DoH TCP connections established to this frontend" << "\n";
- output << "# TYPE " << frontsbase << "http_connects " << "counter" << "\n";
-
- output << "# HELP " << frontsbase << "doh_http_method_queries " << "Number of DoH queries received by dnsdist, by HTTP method" << "\n";
- output << "# TYPE " << frontsbase << "doh_http_method_queries " << "counter" << "\n";
-
- output << "# HELP " << frontsbase << "doh_http_version_queries " << "Number of DoH queries received by dnsdist, by HTTP version" << "\n";
- output << "# TYPE " << frontsbase << "doh_http_version_queries " << "counter" << "\n";
-
- output << "# HELP " << frontsbase << "doh_bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n";
- output << "# TYPE " << frontsbase << "doh_bad_requests " << "counter" << "\n";
-
- output << "# HELP " << frontsbase << "doh_responses " << "Number of responses sent, by type" << "\n";
- output << "# TYPE " << frontsbase << "doh_responses " << "counter" << "\n";
-
- output << "# HELP " << frontsbase << "doh_version_status_responses " << "Number of requests that could not be converted to a DNS query" << "\n";
- output << "# TYPE " << frontsbase << "doh_version_status_responses " << "counter" << "\n";
-
-#ifdef HAVE_DNS_OVER_HTTPS
- std::map<std::string,uint64_t> dohFrontendDuplicates;
- for(const auto& doh : g_dohlocals) {
- const string frontName = doh->d_local.toStringWithPort();
- uint64_t threadNumber = 0;
- auto dupPair = frontendDuplicates.emplace(frontName, 1);
- if (!dupPair.second) {
- threadNumber = dupPair.first->second;
- ++(dupPair.first->second);
- }
- const std::string addrlabel = boost::str(boost::format("frontend=\"%1%\",thread=\"%2%\"") % frontName % threadNumber);
- const std::string label = "{" + addrlabel + "} ";
-
- output << frontsbase << "http_connects" << label << doh->d_httpconnects << "\n";
- output << frontsbase << "doh_http_method_queries{method=\"get\"," << addrlabel << "} " << doh->d_getqueries << "\n";
- output << frontsbase << "doh_http_method_queries{method=\"post\"," << addrlabel << "} " << doh->d_postqueries << "\n";
-
- output << frontsbase << "doh_http_version_queries{version=\"1\"," << addrlabel << "} " << doh->d_http1Stats.d_nbQueries << "\n";
- output << frontsbase << "doh_http_version_queries{version=\"2\"," << addrlabel << "} " << doh->d_http2Stats.d_nbQueries << "\n";
-
- output << frontsbase << "doh_bad_requests{" << addrlabel << "} " << doh->d_badrequests << "\n";
-
- output << frontsbase << "doh_responses{type=\"error\"," << addrlabel << "} " << doh->d_errorresponses << "\n";
- output << frontsbase << "doh_responses{type=\"redirect\"," << addrlabel << "} " << doh->d_redirectresponses << "\n";
- output << frontsbase << "doh_responses{type=\"valid\"," << addrlabel << "} " << doh->d_validresponses << "\n";
-
- output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"200\"," << addrlabel << "} " << doh->d_http1Stats.d_nb200Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"400\"," << addrlabel << "} " << doh->d_http1Stats.d_nb400Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"403\"," << addrlabel << "} " << doh->d_http1Stats.d_nb403Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"500\"," << addrlabel << "} " << doh->d_http1Stats.d_nb500Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"502\"," << addrlabel << "} " << doh->d_http1Stats.d_nb502Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"other\"," << addrlabel << "} " << doh->d_http1Stats.d_nbOtherResponses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"200\"," << addrlabel << "} " << doh->d_http2Stats.d_nb200Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"400\"," << addrlabel << "} " << doh->d_http2Stats.d_nb400Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"403\"," << addrlabel << "} " << doh->d_http2Stats.d_nb403Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"500\"," << addrlabel << "} " << doh->d_http2Stats.d_nb500Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"502\"," << addrlabel << "} " << doh->d_http2Stats.d_nb502Responses << "\n";
- output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"other\"," << addrlabel << "} " << doh->d_http2Stats.d_nbOtherResponses << "\n";
- }
-#endif /* HAVE_DNS_OVER_HTTPS */
-
- auto localPools = g_pools.getLocal();
- const string cachebase = "dnsdist_pool_";
- output << "# HELP dnsdist_pool_servers " << "Number of servers in that pool" << "\n";
- output << "# TYPE dnsdist_pool_servers " << "gauge" << "\n";
- output << "# HELP dnsdist_pool_active_servers " << "Number of available servers in that pool" << "\n";
- output << "# TYPE dnsdist_pool_active_servers " << "gauge" << "\n";
-
- output << "# HELP dnsdist_pool_cache_size " << "Maximum number of entries that this cache can hold" << "\n";
- output << "# TYPE dnsdist_pool_cache_size " << "gauge" << "\n";
- output << "# HELP dnsdist_pool_cache_entries " << "Number of entries currently present in that cache" << "\n";
- output << "# TYPE dnsdist_pool_cache_entries " << "gauge" << "\n";
- output << "# HELP dnsdist_pool_cache_hits " << "Number of hits from that cache" << "\n";
- output << "# TYPE dnsdist_pool_cache_hits " << "counter" << "\n";
- output << "# HELP dnsdist_pool_cache_misses " << "Number of misses from that cache" << "\n";
- output << "# TYPE dnsdist_pool_cache_misses " << "counter" << "\n";
- output << "# HELP dnsdist_pool_cache_deferred_inserts " << "Number of insertions into that cache skipped because it was already locked" << "\n";
- output << "# TYPE dnsdist_pool_cache_deferred_inserts " << "counter" << "\n";
- output << "# HELP dnsdist_pool_cache_deferred_lookups " << "Number of lookups into that cache skipped because it was already locked" << "\n";
- output << "# TYPE dnsdist_pool_cache_deferred_lookups " << "counter" << "\n";
- output << "# HELP dnsdist_pool_cache_lookup_collisions " << "Number of lookups into that cache that triggered a collision (same hash but different entry)" << "\n";
- output << "# TYPE dnsdist_pool_cache_lookup_collisions " << "counter" << "\n";
- output << "# HELP dnsdist_pool_cache_insert_collisions " << "Number of insertions into that cache that triggered a collision (same hash but different entry)" << "\n";
- output << "# TYPE dnsdist_pool_cache_insert_collisions " << "counter" << "\n";
- output << "# HELP dnsdist_pool_cache_ttl_too_shorts " << "Number of insertions into that cache skipped because the TTL of the answer was not long enough" << "\n";
- output << "# TYPE dnsdist_pool_cache_ttl_too_shorts " << "counter" << "\n";
- output << "# HELP dnsdist_pool_cache_cleanup_count_total " << "Number of times the cache has been scanned to remove expired entries, if any" << "\n";
- output << "# TYPE dnsdist_pool_cache_cleanup_count_total " << "counter" << "\n";
-
- for (const auto& entry : *localPools) {
- string poolName = entry.first;
-
- if (poolName.empty()) {
- poolName = "_default_";
- }
- const string label = "{pool=\"" + poolName + "\"}";
- const std::shared_ptr<ServerPool> pool = entry.second;
- output << "dnsdist_pool_servers" << label << " " << pool->countServers(false) << "\n";
- output << "dnsdist_pool_active_servers" << label << " " << pool->countServers(true) << "\n";
-
- if (pool->packetCache != nullptr) {
- const auto& cache = pool->packetCache;
-
- output << cachebase << "cache_size" <<label << " " << cache->getMaxEntries() << "\n";
- output << cachebase << "cache_entries" <<label << " " << cache->getEntriesCount() << "\n";
- output << cachebase << "cache_hits" <<label << " " << cache->getHits() << "\n";
- output << cachebase << "cache_misses" <<label << " " << cache->getMisses() << "\n";
- output << cachebase << "cache_deferred_inserts" <<label << " " << cache->getDeferredInserts() << "\n";
- output << cachebase << "cache_deferred_lookups" <<label << " " << cache->getDeferredLookups() << "\n";
- output << cachebase << "cache_lookup_collisions" <<label << " " << cache->getLookupCollisions() << "\n";
- output << cachebase << "cache_insert_collisions" <<label << " " << cache->getInsertCollisions() << "\n";
- output << cachebase << "cache_ttl_too_shorts" <<label << " " << cache->getTTLTooShorts() << "\n";
- output << cachebase << "cache_cleanup_count_total" <<label << " " << cache->getCleanupCount() << "\n";
- }
- }
-
- output << "# HELP dnsdist_rule_hits " << "Number of hits of that rule" << "\n";
- output << "# TYPE dnsdist_rule_hits " << "counter" << "\n";
- addRulesToPrometheusOutput(output, g_ruleactions);
- addRulesToPrometheusOutput(output, g_respruleactions);
- addRulesToPrometheusOutput(output, g_cachehitrespruleactions);
- addRulesToPrometheusOutput(output, g_cacheInsertedRespRuleActions);
- addRulesToPrometheusOutput(output, g_selfansweredrespruleactions);
-
-#ifndef DISABLE_DYNBLOCKS
- output << "# HELP dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "Number of hits per second blocked by Dynamic Blocks (netmasks) for the top offenders, averaged over the last 60s" << "\n";
- output << "# TYPE dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "gauge" << "\n";
- auto topNetmasksByReason = DynBlockMaintenance::getHitsForTopNetmasks();
- for (const auto& entry : topNetmasksByReason) {
- for (const auto& netmask : entry.second) {
- output << "dnsdist_dynblocks_nmg_top_offenders_hits_per_second{reason=\"" << entry.first << "\",netmask=\"" << netmask.first.toString() << "\"} " << netmask.second << "\n";
- }
- }
-
- output << "# HELP dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "Number of this per second blocked by Dynamic Blocks (suffixes) for the top offenders, averaged over the last 60s" << "\n";
- output << "# TYPE dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "gauge" << "\n";
- auto topSuffixesByReason = DynBlockMaintenance::getHitsForTopSuffixes();
- for (const auto& entry : topSuffixesByReason) {
- for (const auto& suffix : entry.second) {
- output << "dnsdist_dynblocks_smt_top_offenders_hits_per_second{reason=\"" << entry.first << "\",suffix=\"" << suffix.first.toString() << "\"} " << suffix.second << "\n";
- }
- }
-#endif /* DISABLE_DYNBLOCKS */
-
- output << "# HELP dnsdist_info " << "Info from dnsdist, value is always 1" << "\n";
- output << "# TYPE dnsdist_info " << "gauge" << "\n";
- output << "dnsdist_info{version=\"" << VERSION << "\"} " << "1" << "\n";
-
- resp.body = output.str();
- resp.headers["Content-Type"] = "text/plain";
-}
-#endif /* DISABLE_PROMETHEUS */
-
-using namespace json11;
-
-static void addStatsToJSONObject(Json::object& obj)
-{
- for (const auto& e : g_stats.entries) {
- if (e.first == "special-memory-usage") {
- continue; // Too expensive for get-all
- }
- if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
- obj.emplace(e.first, (double)(*val)->load());
- } else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
- obj.emplace(e.first, (*adval)->load());
- } else if (const auto& dval = boost::get<double*>(&e.second)) {
- obj.emplace(e.first, (**dval));
- } else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
- obj.emplace(e.first, (double)(*func)(e.first));
- }
- }
-}
-
-#ifndef DISABLE_BUILTIN_HTML
-static void handleJSONStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- handleCORS(req, resp);
- resp.status = 200;
-
- if (req.getvars.count("command") == 0) {
- resp.status = 404;
- return;
- }
-
- const string& command = req.getvars.at("command");
-
- if (command == "stats") {
- auto obj=Json::object {
- { "packetcache-hits", 0},
- { "packetcache-misses", 0},
- { "over-capacity-drops", 0 },
- { "too-old-drops", 0 },
- { "server-policy", g_policy.getLocal()->getName()}
- };
-
- addStatsToJSONObject(obj);
-
- Json my_json = obj;
- resp.body = my_json.dump();
- resp.headers["Content-Type"] = "application/json";
- }
- else if (command == "dynblocklist") {
- Json::object obj;
-#ifndef DISABLE_DYNBLOCKS
- auto nmg = g_dynblockNMG.getLocal();
- struct timespec now;
- gettime(&now);
- for (const auto& e: *nmg) {
- if(now < e.second.until ) {
- Json::object thing{
- {"reason", e.second.reason},
- {"seconds", (double)(e.second.until.tv_sec - now.tv_sec)},
- {"blocks", (double)e.second.blocks},
- {"action", DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) },
- {"warning", e.second.warning }
- };
- obj.emplace(e.first.toString(), thing);
- }
- }
-
- auto smt = g_dynblockSMT.getLocal();
- smt->visit([&now,&obj](const SuffixMatchTree<DynBlock>& node) {
- if(now <node.d_value.until) {
- string dom("empty");
- if(!node.d_value.domain.empty())
- dom = node.d_value.domain.toString();
- Json::object thing{
- {"reason", node.d_value.reason},
- {"seconds", (double)(node.d_value.until.tv_sec - now.tv_sec)},
- {"blocks", (double)node.d_value.blocks},
- {"action", DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) }
- };
- obj.emplace(dom, thing);
- }
- });
-#endif /* DISABLE_DYNBLOCKS */
- Json my_json = obj;
- resp.body = my_json.dump();
- resp.headers["Content-Type"] = "application/json";
- }
- else if (command == "ebpfblocklist") {
- Json::object obj;
-#ifdef HAVE_EBPF
- struct timespec now;
- gettime(&now);
- for (const auto& dynbpf : g_dynBPFFilters) {
- std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > addrStats = dynbpf->getAddrStats();
- for (const auto& entry : addrStats) {
- Json::object thing
- {
- {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)},
- {"blocks", (double)(std::get<1>(entry))}
- };
- obj.emplace(std::get<0>(entry).toString(), thing );
- }
- }
-#endif /* HAVE_EBPF */
- Json my_json = obj;
- resp.body = my_json.dump();
- resp.headers["Content-Type"] = "application/json";
- }
- else {
- resp.status = 404;
- }
-}
-#endif /* DISABLE_BUILTIN_HTML */
-
-static void addServerToJSON(Json::array& servers, int id, const std::shared_ptr<DownstreamState>& a)
-{
- string status;
- if (a->d_config.availability == DownstreamState::Availability::Up) {
- status = "UP";
- }
- else if (a->d_config.availability == DownstreamState::Availability::Down) {
- status = "DOWN";
- }
- else {
- status = (a->upStatus ? "up" : "down");
- }
-
- Json::array pools;
- pools.reserve(a->d_config.pools.size());
- for (const auto& p: a->d_config.pools) {
- pools.push_back(p);
- }
-
- Json::object server {
- {"id", id},
- {"name", a->getName()},
- {"address", a->d_config.remote.toStringWithPort()},
- {"state", status},
- {"protocol", a->getProtocol().toPrettyString()},
- {"qps", (double)a->queryLoad},
- {"qpsLimit", (double)a->qps.getRate()},
- {"outstanding", (double)a->outstanding},
- {"reuseds", (double)a->reuseds},
- {"weight", (double)a->d_config.d_weight},
- {"order", (double)a->d_config.order},
- {"pools", std::move(pools)},
- {"latency", (double)(a->latencyUsec/1000.0)},
- {"queries", (double)a->queries},
- {"responses", (double)a->responses},
- {"nonCompliantResponses", (double)a->nonCompliantResponses},
- {"sendErrors", (double)a->sendErrors},
- {"tcpDiedSendingQuery", (double)a->tcpDiedSendingQuery},
- {"tcpDiedReadingResponse", (double)a->tcpDiedReadingResponse},
- {"tcpGaveUp", (double)a->tcpGaveUp},
- {"tcpConnectTimeouts", (double)a->tcpConnectTimeouts},
- {"tcpReadTimeouts", (double)a->tcpReadTimeouts},
- {"tcpWriteTimeouts", (double)a->tcpWriteTimeouts},
- {"tcpCurrentConnections", (double)a->tcpCurrentConnections},
- {"tcpMaxConcurrentConnections", (double)a->tcpMaxConcurrentConnections},
- {"tcpTooManyConcurrentConnections", (double)a->tcpTooManyConcurrentConnections},
- {"tcpNewConnections", (double)a->tcpNewConnections},
- {"tcpReusedConnections", (double)a->tcpReusedConnections},
- {"tcpAvgQueriesPerConnection", (double)a->tcpAvgQueriesPerConnection},
- {"tcpAvgConnectionDuration", (double)a->tcpAvgConnectionDuration},
- {"tlsResumptions", (double)a->tlsResumptions},
- {"tcpLatency", (double)(a->latencyUsecTCP/1000.0)},
- {"dropRate", (double)a->dropRate}
- };
-
- /* sending a latency for a DOWN server doesn't make sense */
- if (a->d_config.availability == DownstreamState::Availability::Down) {
- server["latency"] = nullptr;
- server["tcpLatency"] = nullptr;
- }
-
- servers.push_back(std::move(server));
-}
-
-static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- handleCORS(req, resp);
- resp.status = 200;
-
- int num = 0;
-
- Json::array servers;
- {
- auto localServers = g_dstates.getLocal();
- servers.reserve(localServers->size());
- for (const auto& a : *localServers) {
- addServerToJSON(servers, num++, a);
- }
- }
-
- Json::array frontends;
- num = 0;
- frontends.reserve(g_frontends.size());
- for (const auto& front : g_frontends) {
- if (front->udpFD == -1 && front->tcpFD == -1)
- continue;
- Json::object frontend {
- { "id", num++ },
- { "address", front->local.toStringWithPort() },
- { "udp", front->udpFD >= 0 },
- { "tcp", front->tcpFD >= 0 },
- { "type", front->getType() },
- { "queries", (double) front->queries.load() },
- { "nonCompliantQueries", (double) front->nonCompliantQueries.load() },
- { "responses", (double) front->responses.load() },
- { "tcpDiedReadingQuery", (double) front->tcpDiedReadingQuery.load() },
- { "tcpDiedSendingResponse", (double) front->tcpDiedSendingResponse.load() },
- { "tcpGaveUp", (double) front->tcpGaveUp.load() },
- { "tcpClientTimeouts", (double) front->tcpClientTimeouts },
- { "tcpDownstreamTimeouts", (double) front->tcpDownstreamTimeouts },
- { "tcpCurrentConnections", (double) front->tcpCurrentConnections },
- { "tcpMaxConcurrentConnections", (double) front->tcpMaxConcurrentConnections },
- { "tcpAvgQueriesPerConnection", (double) front->tcpAvgQueriesPerConnection },
- { "tcpAvgConnectionDuration", (double) front->tcpAvgConnectionDuration },
- { "tlsNewSessions", (double) front->tlsNewSessions },
- { "tlsResumptions", (double) front->tlsResumptions },
- { "tlsUnknownTicketKey", (double) front->tlsUnknownTicketKey },
- { "tlsInactiveTicketKey", (double) front->tlsInactiveTicketKey },
- { "tls10Queries", (double) front->tls10queries },
- { "tls11Queries", (double) front->tls11queries },
- { "tls12Queries", (double) front->tls12queries },
- { "tls13Queries", (double) front->tls13queries },
- { "tlsUnknownQueries", (double) front->tlsUnknownqueries },
- };
- const TLSErrorCounters* errorCounters = nullptr;
- if (front->tlsFrontend != nullptr) {
- errorCounters = &front->tlsFrontend->d_tlsCounters;
- }
- else if (front->dohFrontend != nullptr) {
- errorCounters = &front->dohFrontend->d_tlsCounters;
- }
- if (errorCounters != nullptr) {
- frontend["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters->d_dhKeyTooSmall;
- frontend["tlsHandshakeFailuresInappropriateFallBack"] = (double)errorCounters->d_inappropriateFallBack;
- frontend["tlsHandshakeFailuresNoSharedCipher"] = (double)errorCounters->d_noSharedCipher;
- frontend["tlsHandshakeFailuresUnknownCipher"] = (double)errorCounters->d_unknownCipherType;
- frontend["tlsHandshakeFailuresUnknownKeyExchangeType"] = (double)errorCounters->d_unknownKeyExchangeType;
- frontend["tlsHandshakeFailuresUnknownProtocol"] = (double)errorCounters->d_unknownProtocol;
- frontend["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters->d_unsupportedEC;
- frontend["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters->d_unsupportedProtocol;
- }
- frontends.push_back(std::move(frontend));
- }
-
- Json::array dohs;
-#ifdef HAVE_DNS_OVER_HTTPS
- {
- dohs.reserve(g_dohlocals.size());
- num = 0;
- for (const auto& doh : g_dohlocals) {
- dohs.emplace_back(Json::object{
- { "id", num++ },
- { "address", doh->d_local.toStringWithPort() },
- { "http-connects", (double) doh->d_httpconnects },
- { "http1-queries", (double) doh->d_http1Stats.d_nbQueries },
- { "http2-queries", (double) doh->d_http2Stats.d_nbQueries },
- { "http1-200-responses", (double) doh->d_http1Stats.d_nb200Responses },
- { "http2-200-responses", (double) doh->d_http2Stats.d_nb200Responses },
- { "http1-400-responses", (double) doh->d_http1Stats.d_nb400Responses },
- { "http2-400-responses", (double) doh->d_http2Stats.d_nb400Responses },
- { "http1-403-responses", (double) doh->d_http1Stats.d_nb403Responses },
- { "http2-403-responses", (double) doh->d_http2Stats.d_nb403Responses },
- { "http1-500-responses", (double) doh->d_http1Stats.d_nb500Responses },
- { "http2-500-responses", (double) doh->d_http2Stats.d_nb500Responses },
- { "http1-502-responses", (double) doh->d_http1Stats.d_nb502Responses },
- { "http2-502-responses", (double) doh->d_http2Stats.d_nb502Responses },
- { "http1-other-responses", (double) doh->d_http1Stats.d_nbOtherResponses },
- { "http2-other-responses", (double) doh->d_http2Stats.d_nbOtherResponses },
- { "get-queries", (double) doh->d_getqueries },
- { "post-queries", (double) doh->d_postqueries },
- { "bad-requests", (double) doh->d_badrequests },
- { "error-responses", (double) doh->d_errorresponses },
- { "redirect-responses", (double) doh->d_redirectresponses },
- { "valid-responses", (double) doh->d_validresponses }
- });
- }
- }
-#endif /* HAVE_DNS_OVER_HTTPS */
-
- Json::array pools;
- {
- auto localPools = g_pools.getLocal();
- num = 0;
- pools.reserve(localPools->size());
- for (const auto& pool : *localPools) {
- const auto& cache = pool.second->packetCache;
- Json::object entry {
- { "id", num++ },
- { "name", pool.first },
- { "serversCount", (double) pool.second->countServers(false) },
- { "cacheSize", (double) (cache ? cache->getMaxEntries() : 0) },
- { "cacheEntries", (double) (cache ? cache->getEntriesCount() : 0) },
- { "cacheHits", (double) (cache ? cache->getHits() : 0) },
- { "cacheMisses", (double) (cache ? cache->getMisses() : 0) },
- { "cacheDeferredInserts", (double) (cache ? cache->getDeferredInserts() : 0) },
- { "cacheDeferredLookups", (double) (cache ? cache->getDeferredLookups() : 0) },
- { "cacheLookupCollisions", (double) (cache ? cache->getLookupCollisions() : 0) },
- { "cacheInsertCollisions", (double) (cache ? cache->getInsertCollisions() : 0) },
- { "cacheTTLTooShorts", (double) (cache ? cache->getTTLTooShorts() : 0) },
- { "cacheCleanupCount", (double) (cache ? cache->getCleanupCount() : 0) }
- };
- pools.push_back(std::move(entry));
- }
- }
-
- Json::array rules;
- /* unfortunately DNSActions have getStats(),
- and DNSResponseActions do not. */
- {
- auto localRules = g_ruleactions.getLocal();
- num = 0;
- rules.reserve(localRules->size());
- for (const auto& a : *localRules) {
- Json::object rule{
- {"id", num++},
- {"creationOrder", (double)a.d_creationOrder},
- {"uuid", boost::uuids::to_string(a.d_id)},
- {"matches", (double)a.d_rule->d_matches},
- {"rule", a.d_rule->toString()},
- {"action", a.d_action->toString()},
- {"action-stats", a.d_action->getStats()}
- };
- rules.push_back(std::move(rule));
- }
- }
- auto responseRules = someResponseRulesToJson(&g_respruleactions);
- auto cacheHitResponseRules = someResponseRulesToJson(&g_cachehitrespruleactions);
- auto cacheInsertedResponseRules = someResponseRulesToJson(&g_cacheInsertedRespRuleActions);
- auto selfAnsweredResponseRules = someResponseRulesToJson(&g_selfansweredrespruleactions);
-
- string acl;
- {
- vector<string> vec;
- g_ACL.getLocal()->toStringVector(&vec);
-
- for (const auto& s : vec) {
- if (!acl.empty()) {
- acl += ", ";
- }
- acl += s;
- }
- }
-
- string localaddressesStr;
- {
- std::set<std::string> localaddresses;
- for (const auto& front : g_frontends) {
- localaddresses.insert(front->local.toStringWithPort());
- }
- for (const auto& addr : localaddresses) {
- if (!localaddressesStr.empty()) {
- localaddressesStr += ", ";
- }
- localaddressesStr += addr;
- }
- }
-
- Json::object stats;
- addStatsToJSONObject(stats);
-
- Json responseObject(Json::object({
- { "daemon_type", "dnsdist" },
- { "version", VERSION },
- { "servers", std::move(servers) },
- { "frontends", std::move(frontends) },
- { "pools", std::move(pools) },
- { "rules", std::move(rules) },
- { "response-rules", std::move(responseRules) },
- { "cache-hit-response-rules", std::move(cacheHitResponseRules) },
- { "cache-inserted-response-rules", std::move(cacheInsertedResponseRules) },
- { "self-answered-response-rules", std::move(selfAnsweredResponseRules) },
- { "acl", std::move(acl) },
- { "local", std::move(localaddressesStr) },
- { "dohFrontends", std::move(dohs) },
- { "statistics", std::move(stats) }
- }));
-
- resp.headers["Content-Type"] = "application/json";
- resp.body = responseObject.dump();
-}
-
-static void handlePoolStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- handleCORS(req, resp);
- const auto poolName = req.getvars.find("name");
- if (poolName == req.getvars.end()) {
- resp.status = 400;
- return;
- }
-
- resp.status = 200;
- Json::array doc;
-
- auto localPools = g_pools.getLocal();
- const auto poolIt = localPools->find(poolName->second);
- if (poolIt == localPools->end()) {
- resp.status = 404;
- return;
- }
-
- const auto& pool = poolIt->second;
- const auto& cache = pool->packetCache;
- Json::object entry {
- { "name", poolName->second },
- { "serversCount", (double) pool->countServers(false) },
- { "cacheSize", (double) (cache ? cache->getMaxEntries() : 0) },
- { "cacheEntries", (double) (cache ? cache->getEntriesCount() : 0) },
- { "cacheHits", (double) (cache ? cache->getHits() : 0) },
- { "cacheMisses", (double) (cache ? cache->getMisses() : 0) },
- { "cacheDeferredInserts", (double) (cache ? cache->getDeferredInserts() : 0) },
- { "cacheDeferredLookups", (double) (cache ? cache->getDeferredLookups() : 0) },
- { "cacheLookupCollisions", (double) (cache ? cache->getLookupCollisions() : 0) },
- { "cacheInsertCollisions", (double) (cache ? cache->getInsertCollisions() : 0) },
- { "cacheTTLTooShorts", (double) (cache ? cache->getTTLTooShorts() : 0) },
- { "cacheCleanupCount", (double) (cache ? cache->getCleanupCount() : 0) }
- };
-
- Json::array servers;
- int num = 0;
- for (const auto& a : *pool->getServers()) {
- addServerToJSON(servers, num, a.second);
- num++;
- }
-
- resp.headers["Content-Type"] = "application/json";
- Json my_json = Json::object {
- { "stats", entry },
- { "servers", servers }
- };
-
- resp.body = my_json.dump();
-}
-
-static void handleStatsOnly(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- handleCORS(req, resp);
- resp.status = 200;
-
- Json::array doc;
- for(const auto& item : g_stats.entries) {
- if (item.first == "special-memory-usage")
- continue; // Too expensive for get-all
-
- if (const auto& val = boost::get<pdns::stat_t*>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", (double)(*val)->load() }
- });
- }
- else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", (*adval)->load() }
- });
- }
- else if (const auto& dval = boost::get<double*>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", (**dval) }
- });
- }
- else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", (double)(*func)(item.first) }
- });
- }
- }
- Json my_json = doc;
- resp.body = my_json.dump();
- resp.headers["Content-Type"] = "application/json";
-}
-
-#ifndef DISABLE_WEB_CONFIG
-static void handleConfigDump(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- handleCORS(req, resp);
- resp.status = 200;
-
- Json::array doc;
- typedef boost::variant<bool, double, std::string> configentry_t;
- std::vector<std::pair<std::string, configentry_t> > configEntries {
- { "acl", g_ACL.getLocal()->toString() },
- { "allow-empty-response", g_allowEmptyResponse },
- { "control-socket", g_serverControl.toStringWithPort() },
- { "ecs-override", g_ECSOverride },
- { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4 },
- { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6 },
- { "fixup-case", g_fixupCase },
- { "max-outstanding", (double) g_maxOutstanding },
- { "server-policy", g_policy.getLocal()->getName() },
- { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL },
- { "tcp-recv-timeout", (double) g_tcpRecvTimeout },
- { "tcp-send-timeout", (double) g_tcpSendTimeout },
- { "truncate-tc", g_truncateTC },
- { "verbose", g_verbose },
- { "verbose-health-checks", g_verboseHealthChecks }
- };
- for(const auto& item : configEntries) {
- if (const auto& bval = boost::get<bool>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "ConfigSetting" },
- { "name", item.first },
- { "value", *bval }
- });
- }
- else if (const auto& sval = boost::get<string>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "ConfigSetting" },
- { "name", item.first },
- { "value", *sval }
- });
- }
- else if (const auto& dval = boost::get<double>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "ConfigSetting" },
- { "name", item.first },
- { "value", *dval }
- });
- }
- }
- Json my_json = doc;
- resp.body = my_json.dump();
- resp.headers["Content-Type"] = "application/json";
-}
-
-static void handleAllowFrom(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- handleCORS(req, resp);
-
- resp.headers["Content-Type"] = "application/json";
- resp.status = 200;
-
- if (req.method == "PUT") {
- std::string err;
- Json doc = Json::parse(req.body, err);
-
- if (!doc.is_null()) {
- NetmaskGroup nmg;
- auto aclList = doc["value"];
- if (aclList.is_array()) {
-
- for (const auto& value : aclList.array_items()) {
- try {
- nmg.addMask(value.string_value());
- } catch (NetmaskException &e) {
- resp.status = 400;
- break;
- }
- }
-
- if (resp.status == 200) {
- infolog("Updating the ACL via the API to %s", nmg.toString());
- g_ACL.setState(nmg);
- apiSaveACL(nmg);
- }
- }
- else {
- resp.status = 400;
- }
- }
- else {
- resp.status = 400;
- }
- }
- if (resp.status == 200) {
- Json::array acl;
- vector<string> vec;
- g_ACL.getLocal()->toStringVector(&vec);
-
- for(const auto& s : vec) {
- acl.push_back(s);
- }
-
- Json::object obj{
- { "type", "ConfigSetting" },
- { "name", "allow-from" },
- { "value", acl }
- };
- Json my_json = obj;
- resp.body = my_json.dump();
- }
-}
-#endif /* DISABLE_WEB_CONFIG */
-
-#ifndef DISABLE_WEB_CACHE_MANAGEMENT
-static void handleCacheManagement(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- handleCORS(req, resp);
-
- resp.headers["Content-Type"] = "application/json";
- resp.status = 200;
-
- if (req.method != "DELETE") {
- resp.status = 400;
- Json::object obj{
- { "status", "denied" },
- { "error", "invalid method" }
- };
- resp.body = Json(obj).dump();
- return;
- }
-
- const auto poolName = req.getvars.find("pool");
- const auto expungeName = req.getvars.find("name");
- const auto expungeType = req.getvars.find("type");
- const auto suffix = req.getvars.find("suffix");
- if (poolName == req.getvars.end() || expungeName == req.getvars.end()) {
- resp.status = 400;
- Json::object obj{
- { "status", "denied" },
- { "error", "missing 'pool' or 'name' parameter" },
- };
- resp.body = Json(obj).dump();
- return;
- }
-
- DNSName name;
- QType type(QType::ANY);
- try {
- name = DNSName(expungeName->second);
- }
- catch (const std::exception& e) {
- resp.status = 400;
- Json::object obj{
- { "status", "error" },
- { "error", "unable to parse the requested name" },
- };
- resp.body = Json(obj).dump();
- return;
- }
- if (expungeType != req.getvars.end()) {
- type = QType::chartocode(expungeType->second.c_str());
- }
-
- std::shared_ptr<ServerPool> pool;
- try {
- pool = getPool(g_pools.getCopy(), poolName->second);
- }
- catch (const std::exception& e) {
- resp.status = 404;
- Json::object obj{
- { "status", "not found" },
- { "error", "the requested pool does not exist" },
- };
- resp.body = Json(obj).dump();
- return;
- }
-
- auto cache = pool->getCache();
- if (cache == nullptr) {
- resp.status = 404;
- Json::object obj{
- { "status", "not found" },
- { "error", "there is no cache associated with the requested pool" },
- };
- resp.body = Json(obj).dump();
- return;
- }
-
- auto removed = cache->expungeByName(name, type.getCode(), suffix != req.getvars.end());
-
- Json::object obj{
- { "status", "purged" },
- { "count", std::to_string(removed) }
- };
- resp.body = Json(obj).dump();
-}
-#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
-
-static std::unordered_map<std::string, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)>> s_webHandlers;
-
-void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler);
-
-void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler)
-{
- s_webHandlers[endpoint] = handler;
-}
-
-void clearWebHandlers()
-{
- s_webHandlers.clear();
-}
-
-#ifndef DISABLE_BUILTIN_HTML
-#include "htmlfiles.h"
-
-static void redirectToIndex(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- const string charset = "; charset=utf-8";
- resp.body.assign(s_urlmap.at("index.html"));
- resp.headers["Content-Type"] = "text/html" + charset;
- resp.status = 200;
-}
-
-static void handleBuiltInFiles(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
- if (req.url.path.empty() || !s_urlmap.count(req.url.path.c_str()+1)) {
- resp.status = 404;
- return;
- }
-
- resp.body.assign(s_urlmap.at(req.url.path.c_str()+1));
-
- vector<string> parts;
- stringtok(parts, req.url.path, ".");
- static const std::unordered_map<std::string, std::string> contentTypeMap = {
- { "html", "text/html" },
- { "css", "text/css" },
- { "js", "application/javascript" },
- { "png", "image/png" },
- };
-
- const auto& it = contentTypeMap.find(parts.back());
- if (it != contentTypeMap.end()) {
- const string charset = "; charset=utf-8";
- resp.headers["Content-Type"] = it->second + charset;
- }
-
- resp.status = 200;
-}
-#endif /* DISABLE_BUILTIN_HTML */
-
-void registerBuiltInWebHandlers()
-{
-#ifndef DISABLE_BUILTIN_HTML
- registerWebHandler("/jsonstat", handleJSONStats);
-#endif /* DISABLE_BUILTIN_HTML */
-#ifndef DISABLE_PROMETHEUS
- registerWebHandler("/metrics", handlePrometheus);
-#endif /* DISABLE_PROMETHEUS */
- registerWebHandler("/api/v1/servers/localhost", handleStats);
- registerWebHandler("/api/v1/servers/localhost/pool", handlePoolStats);
- registerWebHandler("/api/v1/servers/localhost/statistics", handleStatsOnly);
-#ifndef DISABLE_WEB_CONFIG
- registerWebHandler("/api/v1/servers/localhost/config", handleConfigDump);
- registerWebHandler("/api/v1/servers/localhost/config/allow-from", handleAllowFrom);
-#endif /* DISABLE_WEB_CONFIG */
-#ifndef DISABLE_WEB_CACHE_MANAGEMENT
- registerWebHandler("/api/v1/cache", handleCacheManagement);
-#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
-#ifndef DISABLE_BUILTIN_HTML
- registerWebHandler("/", redirectToIndex);
-
- for (const auto& path : s_urlmap) {
- registerWebHandler("/" + path.first, handleBuiltInFiles);
- }
-#endif /* DISABLE_BUILTIN_HTML */
-}
-
-static void connectionThread(WebClientConnection&& conn)
-{
- setThreadName("dnsdist/webConn");
-
- vinfolog("Webserver handling connection from %s", conn.getClient().toStringWithPort());
-
- try {
- YaHTTP::AsyncRequestLoader yarl;
- YaHTTP::Request req;
- bool finished = false;
-
- yarl.initialize(&req);
- while (!finished) {
- int bytes;
- char buf[1024];
- bytes = read(conn.getSocket().getHandle(), buf, sizeof(buf));
- if (bytes > 0) {
- string data = string(buf, bytes);
- finished = yarl.feed(data);
- } else {
- // read error OR EOF
- break;
- }
- }
- yarl.finalize();
-
- req.getvars.erase("_"); // jQuery cache buster
-
- YaHTTP::Response resp;
- resp.version = req.version;
-
- {
- auto config = g_webserverConfig.lock();
-
- addCustomHeaders(resp, config->customHeaders);
- addSecurityHeaders(resp, config->customHeaders);
- }
- /* indicate that the connection will be closed after completion of the response */
- resp.headers["Connection"] = "close";
-
- /* no need to send back the API key if any */
- resp.headers.erase("X-API-Key");
-
- if (req.method == "OPTIONS") {
- /* the OPTIONS method should not require auth, otherwise it breaks CORS */
- handleCORS(req, resp);
- resp.status = 200;
- }
- else if (!handleAuthorization(req)) {
- YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization");
- if (header != req.headers.end()) {
- vinfolog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, conn.getClient().toStringWithPort());
- }
- resp.status = 401;
- resp.body = "<h1>Unauthorized</h1>";
- resp.headers["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
- }
- else if (!isMethodAllowed(req)) {
- resp.status = 405;
- }
- else {
- const auto it = s_webHandlers.find(req.url.path);
- if (it != s_webHandlers.end()) {
- it->second(req, resp);
- }
- else {
- resp.status = 404;
- }
- }
-
- std::ostringstream ofs;
- ofs << resp;
- string done = ofs.str();
- writen2(conn.getSocket().getHandle(), done.c_str(), done.size());
- }
- catch (const YaHTTP::ParseError& e) {
- vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", conn.getClient().toStringWithPort(), e.what());
- }
- catch (const std::exception& e) {
- errlog("Webserver thread died with exception while processing a request from %s: %s", conn.getClient().toStringWithPort(), e.what());
- }
- catch (...) {
- errlog("Webserver thread died with exception while processing a request from %s", conn.getClient().toStringWithPort());
- }
-}
-
-void setWebserverAPIKey(std::unique_ptr<CredentialsHolder>&& apiKey)
-{
- auto config = g_webserverConfig.lock();
-
- if (apiKey) {
- config->apiKey = std::move(apiKey);
- } else {
- config->apiKey.reset();
- }
-}
-
-void setWebserverPassword(std::unique_ptr<CredentialsHolder>&& password)
-{
- g_webserverConfig.lock()->password = std::move(password);
-}
-
-void setWebserverACL(const std::string& acl)
-{
- NetmaskGroup newACL;
- newACL.toMasks(acl);
-
- g_webserverConfig.lock()->acl = std::move(newACL);
-}
-
-void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string> > customHeaders)
-{
- g_webserverConfig.lock()->customHeaders = customHeaders;
-}
-
-void setWebserverStatsRequireAuthentication(bool require)
-{
- g_webserverConfig.lock()->statsRequireAuthentication = require;
-}
-
-void setWebserverAPIRequiresAuthentication(bool require)
-{
- g_webserverConfig.lock()->apiRequiresAuthentication = require;
-}
-
-void setWebserverDashboardRequiresAuthentication(bool require)
-{
- g_webserverConfig.lock()->dashboardRequiresAuthentication = require;
-}
-
-void setWebserverMaxConcurrentConnections(size_t max)
-{
- s_connManager.setMaxConcurrentConnections(max);
-}
-
-void dnsdistWebserverThread(int sock, const ComboAddress& local)
-{
- setThreadName("dnsdist/webserv");
- infolog("Webserver launched on %s", local.toStringWithPort());
-
- {
- auto config = g_webserverConfig.lock();
- if (!config->password && config->dashboardRequiresAuthentication) {
- warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
- }
- }
-
- for (;;) {
- try {
- ComboAddress remote(local);
- int fd = SAccept(sock, remote);
-
- if (!isClientAllowedByACL(remote)) {
- vinfolog("Connection to webserver from client %s is not allowed, closing", remote.toStringWithPort());
- close(fd);
- continue;
- }
-
- WebClientConnection conn(remote, fd);
- vinfolog("Got a connection to the webserver from %s", remote.toStringWithPort());
-
- std::thread t(connectionThread, std::move(conn));
- t.detach();
- }
- catch (const std::exception& e) {
- errlog("Had an error accepting new webserver connection: %s", e.what());
- }
- }
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "dnsdist-xpf.hh"
-
-#include "dnsparser.hh"
-#include "xpf.hh"
-
-bool addXPF(DNSQuestion& dq, uint16_t optionCode)
-{
- std::string payload = generateXPFPayload(dq.overTCP(), dq.ids.origRemote, dq.ids.origDest);
- uint8_t root = '\0';
- dnsrecordheader drh;
- drh.d_type = htons(optionCode);
- drh.d_class = htons(QClass::IN);
- drh.d_ttl = 0;
- drh.d_clen = htons(payload.size());
- size_t recordHeaderLen = sizeof(root) + sizeof(drh);
-
- if (!dq.hasRoomFor(payload.size() + recordHeaderLen)) {
- return false;
- }
-
- size_t xpfSize = sizeof(root) + sizeof(drh) + payload.size();
- auto& data = dq.getMutableData();
- uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(data.data()), data.size());
- data.resize(realPacketLen + xpfSize);
-
- size_t pos = realPacketLen;
- memcpy(reinterpret_cast<char*>(&data.at(pos)), &root, sizeof(root));
- pos += sizeof(root);
- memcpy(reinterpret_cast<char*>(&data.at(pos)), &drh, sizeof(drh));
- pos += sizeof(drh);
- memcpy(reinterpret_cast<char*>(&data.at(pos)), payload.data(), payload.size());
- pos += payload.size();
- (void) pos;
-
- dq.getHeader()->arcount = htons(ntohs(dq.getHeader()->arcount) + 1);
-
- return true;
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-
-#include <cstdint>
-#include <fstream>
-#include <getopt.h>
-#include <grp.h>
-#include <limits>
-#include <netinet/tcp.h>
-#include <pwd.h>
-#include <sys/resource.h>
-#include <unistd.h>
-
-#ifdef HAVE_LIBEDIT
-#if defined (__OpenBSD__) || defined(__NetBSD__)
-// If this is not undeffed, __attribute__ wil be redefined by /usr/include/readline/rlstdc.h
-#undef __STRICT_ANSI__
-#include <readline/readline.h>
-#else
-#include <editline/readline.h>
-#endif
-#endif /* HAVE_LIBEDIT */
-
-#include "dnsdist-systemd.hh"
-#ifdef HAVE_SYSTEMD
-#include <systemd/sd-daemon.h>
-#endif
-
-#include "dnsdist.hh"
-#include "dnsdist-async.hh"
-#include "dnsdist-cache.hh"
-#include "dnsdist-carbon.hh"
-#include "dnsdist-console.hh"
-#include "dnsdist-discovery.hh"
-#include "dnsdist-dynblocks.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-healthchecks.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-nghttp2.hh"
-#include "dnsdist-proxy-protocol.hh"
-#include "dnsdist-random.hh"
-#include "dnsdist-rings.hh"
-#include "dnsdist-secpoll.hh"
-#include "dnsdist-tcp.hh"
-#include "dnsdist-web.hh"
-#include "dnsdist-xpf.hh"
-
-#include "base64.hh"
-#include "capabilities.hh"
-#include "delaypipe.hh"
-#include "dolog.hh"
-#include "dnsname.hh"
-#include "dnsparser.hh"
-#include "ednsoptions.hh"
-#include "gettime.hh"
-#include "lock.hh"
-#include "misc.hh"
-#include "sodcrypto.hh"
-#include "sstuff.hh"
-#include "threadname.hh"
-
-/* Known sins:
-
- Receiver is currently single threaded
- not *that* bad actually, but now that we are thread safe, might want to scale
-*/
-
-/* the RuleAction plan
- Set of Rules, if one matches, it leads to an Action
- Both rules and actions could conceivably be Lua based.
- On the C++ side, both could be inherited from a class Rule and a class Action,
- on the Lua side we can't do that. */
-
-using std::thread;
-bool g_verbose;
-std::optional<std::ofstream> g_verboseStream{std::nullopt};
-
-struct DNSDistStats g_stats;
-
-uint16_t g_maxOutstanding{std::numeric_limits<uint16_t>::max()};
-uint32_t g_staleCacheEntriesTTL{0};
-bool g_syslog{true};
-bool g_logtimestamps{false};
-bool g_allowEmptyResponse{false};
-
-GlobalStateHolder<NetmaskGroup> g_ACL;
-string g_outputBuffer;
-
-std::vector<std::shared_ptr<TLSFrontend>> g_tlslocals;
-std::vector<std::shared_ptr<DOHFrontend>> g_dohlocals;
-std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
-
-shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
-std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
-
-std::vector<std::unique_ptr<ClientState>> g_frontends;
-GlobalStateHolder<pools_t> g_pools;
-size_t g_udpVectorSize{1};
-std::vector<uint32_t> g_TCPFastOpenKey;
-/* UDP: the grand design. Per socket we listen on for incoming queries there is one thread.
- Then we have a bunch of connected sockets for talking to downstream servers.
- We send directly to those sockets.
-
- For the return path, per downstream server we have a thread that listens to responses.
-
- Per socket there is an array of 2^16 states, when we send out a packet downstream, we note
- there the original requestor and the original id. The new ID is the offset in the array.
-
- When an answer comes in on a socket, we look up the offset by the id, and lob it to the
- original requestor.
-
- IDs are assigned by atomic increments of the socket offset.
- */
-
-GlobalStateHolder<vector<DNSDistRuleAction> > g_ruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_respruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cachehitrespruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cacheInsertedRespRuleActions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_selfansweredrespruleactions;
-
-Rings g_rings;
-QueryCount g_qcount;
-
-GlobalStateHolder<servers_t> g_dstates;
-
-bool g_servFailOnNoPolicy{false};
-bool g_truncateTC{false};
-bool g_fixupCase{false};
-bool g_dropEmptyQueries{false};
-uint32_t g_socketUDPSendBuffer{0};
-uint32_t g_socketUDPRecvBuffer{0};
-
-std::set<std::string> g_capabilitiesToRetain;
-
-static size_t const s_initialUDPPacketBufferSize = s_maxPacketCacheEntrySize + DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE;
-static_assert(s_initialUDPPacketBufferSize <= UINT16_MAX, "Packet size should fit in a uint16_t");
-
-static ssize_t sendfromto(int sock, const void* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& to)
-{
- if (from.sin4.sin_family == 0) {
- return sendto(sock, data, len, flags, reinterpret_cast<const struct sockaddr*>(&to), to.getSocklen());
- }
- struct msghdr msgh;
- struct iovec iov;
- cmsgbuf_aligned cbuf;
-
- /* Set up iov and msgh structures. */
- memset(&msgh, 0, sizeof(struct msghdr));
- iov.iov_base = const_cast<void*>(data);
- iov.iov_len = len;
- msgh.msg_iov = &iov;
- msgh.msg_iovlen = 1;
- msgh.msg_name = (struct sockaddr*)&to;
- msgh.msg_namelen = to.getSocklen();
-
- if (from.sin4.sin_family) {
- addCMsgSrcAddr(&msgh, &cbuf, &from, 0);
- }
- else {
- msgh.msg_control=nullptr;
- }
- return sendmsg(sock, &msgh, flags);
-}
-
-static void truncateTC(PacketBuffer& packet, size_t maximumSize, unsigned int qnameWireLength)
-{
- try
- {
- bool hadEDNS = false;
- uint16_t payloadSize = 0;
- uint16_t z = 0;
-
- if (g_addEDNSToSelfGeneratedResponses) {
- hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &z);
- }
-
- packet.resize(static_cast<uint16_t>(sizeof(dnsheader)+qnameWireLength+DNS_TYPE_SIZE+DNS_CLASS_SIZE));
- struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(packet.data());
- dh->ancount = dh->arcount = dh->nscount = 0;
-
- if (hadEDNS) {
- addEDNS(packet, maximumSize, z & EDNS_HEADER_FLAG_DO, payloadSize, 0);
- }
- }
- catch(...)
- {
- ++g_stats.truncFail;
- }
-}
-
-#ifndef DISABLE_DELAY_PIPE
-struct DelayedPacket
-{
- int fd;
- PacketBuffer packet;
- ComboAddress destination;
- ComboAddress origDest;
- void operator()()
- {
- ssize_t res = sendfromto(fd, packet.data(), packet.size(), 0, origDest, destination);
- if (res == -1) {
- int err = errno;
- vinfolog("Error sending delayed response to %s: %s", destination.toStringWithPort(), strerror(err));
- }
- }
-};
-
-static DelayPipe<DelayedPacket>* g_delay = nullptr;
-#endif /* DISABLE_DELAY_PIPE */
-
-std::string DNSQuestion::getTrailingData() const
-{
- const char* message = reinterpret_cast<const char*>(this->getHeader());
- const uint16_t messageLen = getDNSPacketLength(message, this->data.size());
- return std::string(message + messageLen, this->getData().size() - messageLen);
-}
-
-bool DNSQuestion::setTrailingData(const std::string& tail)
-{
- const char* message = reinterpret_cast<const char*>(this->data.data());
- const uint16_t messageLen = getDNSPacketLength(message, this->data.size());
- this->data.resize(messageLen);
- if (tail.size() > 0) {
- if (!hasRoomFor(tail.size())) {
- return false;
- }
- this->data.insert(this->data.end(), tail.begin(), tail.end());
- }
- return true;
-}
-
-static void doLatencyStats(dnsdist::Protocol protocol, double udiff)
-{
- constexpr auto doAvg = [](double& var, double n, double weight) {
- var = (weight -1) * var/weight + n/weight;
- };
-
- if (protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP) {
- if (udiff < 1000) {
- ++g_stats.latency0_1;
- }
- else if (udiff < 10000) {
- ++g_stats.latency1_10;
- }
- else if (udiff < 50000) {
- ++g_stats.latency10_50;
- }
- else if (udiff < 100000) {
- ++g_stats.latency50_100;
- }
- else if (udiff < 1000000) {
- ++g_stats.latency100_1000;
- }
- else {
- ++g_stats.latencySlow;
- }
-
- g_stats.latencySum += udiff / 1000;
- ++g_stats.latencyCount;
-
- doAvg(g_stats.latencyAvg100, udiff, 100);
- doAvg(g_stats.latencyAvg1000, udiff, 1000);
- doAvg(g_stats.latencyAvg10000, udiff, 10000);
- doAvg(g_stats.latencyAvg1000000, udiff, 1000000);
- }
- else if (protocol == dnsdist::Protocol::DoTCP || protocol == dnsdist::Protocol::DNSCryptTCP) {
- doAvg(g_stats.latencyTCPAvg100, udiff, 100);
- doAvg(g_stats.latencyTCPAvg1000, udiff, 1000);
- doAvg(g_stats.latencyTCPAvg10000, udiff, 10000);
- doAvg(g_stats.latencyTCPAvg1000000, udiff, 1000000);
- }
- else if (protocol == dnsdist::Protocol::DoT) {
- doAvg(g_stats.latencyDoTAvg100, udiff, 100);
- doAvg(g_stats.latencyDoTAvg1000, udiff, 1000);
- doAvg(g_stats.latencyDoTAvg10000, udiff, 10000);
- doAvg(g_stats.latencyDoTAvg1000000, udiff, 1000000);
- }
- else if (protocol == dnsdist::Protocol::DoH) {
- doAvg(g_stats.latencyDoHAvg100, udiff, 100);
- doAvg(g_stats.latencyDoHAvg1000, udiff, 1000);
- doAvg(g_stats.latencyDoHAvg10000, udiff, 10000);
- doAvg(g_stats.latencyDoHAvg1000000, udiff, 1000000);
- }
-}
-
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote, unsigned int& qnameWireLength)
-{
- if (response.size() < sizeof(dnsheader)) {
- return false;
- }
-
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(response.data());
- if (dh->qr == 0) {
- ++g_stats.nonCompliantResponses;
- if (remote) {
- ++remote->nonCompliantResponses;
- }
- return false;
- }
-
- if (dh->qdcount == 0) {
- if ((dh->rcode != RCode::NoError && dh->rcode != RCode::NXDomain) || g_allowEmptyResponse) {
- return true;
- }
- else {
- ++g_stats.nonCompliantResponses;
- if (remote) {
- ++remote->nonCompliantResponses;
- }
- return false;
- }
- }
-
- uint16_t rqtype, rqclass;
- DNSName rqname;
- try {
- rqname = DNSName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, &rqtype, &rqclass, &qnameWireLength);
- }
- catch (const std::exception& e) {
- if (remote && response.size() > 0 && static_cast<size_t>(response.size()) > sizeof(dnsheader)) {
- infolog("Backend %s sent us a response with id %d that did not parse: %s", remote->d_config.remote.toStringWithPort(), ntohs(dh->id), e.what());
- }
- ++g_stats.nonCompliantResponses;
- if (remote) {
- ++remote->nonCompliantResponses;
- }
- return false;
- }
-
- if (rqtype != qtype || rqclass != qclass || rqname != qname) {
- return false;
- }
-
- return true;
-}
-
-static void restoreFlags(struct dnsheader* dh, uint16_t origFlags)
-{
- static const uint16_t rdMask = 1 << FLAGS_RD_OFFSET;
- static const uint16_t cdMask = 1 << FLAGS_CD_OFFSET;
- static const uint16_t restoreFlagsMask = UINT16_MAX & ~(rdMask | cdMask);
- uint16_t* flags = getFlagsFromDNSHeader(dh);
- /* clear the flags we are about to restore */
- *flags &= restoreFlagsMask;
- /* only keep the flags we want to restore */
- origFlags &= ~restoreFlagsMask;
- /* set the saved flags as they were */
- *flags |= origFlags;
-}
-
-static bool fixUpQueryTurnedResponse(DNSQuestion& dq, const uint16_t origFlags)
-{
- restoreFlags(dq.getHeader(), origFlags);
-
- return addEDNSToQueryTurnedResponse(dq);
-}
-
-static bool fixUpResponse(PacketBuffer& response, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, bool* zeroScope)
-{
- if (response.size() < sizeof(dnsheader)) {
- return false;
- }
-
- struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
- restoreFlags(dh, origFlags);
-
- if (response.size() == sizeof(dnsheader)) {
- return true;
- }
-
- if (g_fixupCase) {
- const auto& realname = qname.getStorage();
- if (response.size() >= (sizeof(dnsheader) + realname.length())) {
- memcpy(&response.at(sizeof(dnsheader)), realname.c_str(), realname.length());
- }
- }
-
- if (ednsAdded || ecsAdded) {
- uint16_t optStart;
- size_t optLen = 0;
- bool last = false;
-
- int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
-
- if (res == 0) {
- if (zeroScope) { // this finds if an EDNS Client Subnet scope was set, and if it is 0
- size_t optContentStart = 0;
- uint16_t optContentLen = 0;
- /* we need at least 4 bytes after the option length (family: 2, source prefix-length: 1, scope prefix-length: 1) */
- if (isEDNSOptionInOpt(response, optStart, optLen, EDNSOptionCode::ECS, &optContentStart, &optContentLen) && optContentLen >= 4) {
- /* see if the EDNS Client Subnet SCOPE PREFIX-LENGTH byte in position 3 is set to 0, which is the only thing
- we care about. */
- *zeroScope = response.at(optContentStart + 3) == 0;
- }
- }
-
- if (ednsAdded) {
- /* we added the entire OPT RR,
- therefore we need to remove it entirely */
- if (last) {
- /* simply remove the last AR */
- response.resize(response.size() - optLen);
- dh = reinterpret_cast<struct dnsheader*>(response.data());
- uint16_t arcount = ntohs(dh->arcount);
- arcount--;
- dh->arcount = htons(arcount);
- }
- else {
- /* Removing an intermediary RR could lead to compression error */
- PacketBuffer rewrittenResponse;
- if (rewriteResponseWithoutEDNS(response, rewrittenResponse) == 0) {
- response = std::move(rewrittenResponse);
- }
- else {
- warnlog("Error rewriting content");
- }
- }
- }
- else {
- /* the OPT RR was already present, but without ECS,
- we need to remove the ECS option if any */
- if (last) {
- /* nothing after the OPT RR, we can simply remove the
- ECS option */
- size_t existingOptLen = optLen;
- removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
- response.resize(response.size() - (existingOptLen - optLen));
- }
- else {
- PacketBuffer rewrittenResponse;
- /* Removing an intermediary RR could lead to compression error */
- if (rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, rewrittenResponse) == 0) {
- response = std::move(rewrittenResponse);
- }
- else {
- warnlog("Error rewriting content");
- }
- }
- }
- }
- }
-
- return true;
-}
-
-#ifdef HAVE_DNSCRYPT
-static bool encryptResponse(PacketBuffer& response, size_t maximumSize, bool tcp, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery)
-{
- if (dnsCryptQuery) {
- int res = dnsCryptQuery->encryptResponse(response, maximumSize, tcp);
- if (res != 0) {
- /* dropping response */
- vinfolog("Error encrypting the response, dropping.");
- return false;
- }
- }
- return true;
-}
-#endif /* HAVE_DNSCRYPT */
-
-static bool applyRulesToResponse(const std::vector<DNSDistResponseRuleAction>& respRuleActions, DNSResponse& dr)
-{
- DNSResponseAction::Action action = DNSResponseAction::Action::None;
- std::string ruleresult;
- for (const auto& lr : respRuleActions) {
- if (lr.d_rule->matches(&dr)) {
- ++lr.d_rule->d_matches;
- action = (*lr.d_action)(&dr, &ruleresult);
- switch (action) {
- case DNSResponseAction::Action::Allow:
- return true;
- break;
- case DNSResponseAction::Action::Drop:
- return false;
- break;
- case DNSResponseAction::Action::HeaderModify:
- return true;
- break;
- case DNSResponseAction::Action::ServFail:
- dr.getHeader()->rcode = RCode::ServFail;
- return true;
- break;
- /* non-terminal actions follow */
- case DNSResponseAction::Action::Delay:
- pdns::checked_stoi_into(dr.ids.delayMsec, ruleresult); // sorry
- break;
- case DNSResponseAction::Action::None:
- break;
- }
- }
- }
-
- return true;
-}
-
-bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
-{
- bool zeroScope = false;
- if (!fixUpResponse(response, dr.ids.qname, dr.ids.origFlags, dr.ids.ednsAdded, dr.ids.ecsAdded, dr.ids.useZeroScope ? &zeroScope : nullptr)) {
- return false;
- }
-
- if (dr.ids.packetCache && !dr.ids.selfGenerated && !dr.ids.skipCache && response.size() <= s_maxPacketCacheEntrySize) {
- if (!dr.ids.useZeroScope) {
- /* if the query was not suitable for zero-scope, for
- example because it had an existing ECS entry so the hash is
- not really 'no ECS', so just insert it for the existing subnet
- since:
- - we don't have the correct hash for a non-ECS query
- - inserting with hash computed before the ECS replacement but with
- the subnet extracted _after_ the replacement would not work.
- */
- zeroScope = false;
- }
- uint32_t cacheKey = dr.ids.cacheKey;
- if (dr.ids.protocol == dnsdist::Protocol::DoH && dr.ids.forwardedOverUDP) {
- cacheKey = dr.ids.cacheKeyUDP;
- }
- else if (zeroScope) {
- // if zeroScope, pass the pre-ECS hash-key and do not pass the subnet to the cache
- cacheKey = dr.ids.cacheKeyNoECS;
- }
-
- dr.ids.packetCache->insert(cacheKey, zeroScope ? boost::none : dr.ids.subnet, dr.ids.cacheFlags, dr.ids.dnssecOK, dr.ids.qname, dr.ids.qtype, dr.ids.qclass, response, dr.ids.forwardedOverUDP, dr.getHeader()->rcode, dr.ids.tempFailureTTL);
-
- if (!applyRulesToResponse(cacheInsertedRespRuleActions, dr)) {
- return false;
- }
- }
-
- if (dr.ids.ttlCap > 0) {
- std::string result;
- LimitTTLResponseAction ac(0, dr.ids.ttlCap, {});
- ac(&dr, &result);
- }
-
-#ifdef HAVE_DNSCRYPT
- if (!muted) {
- if (!encryptResponse(response, dr.getMaximumSize(), dr.overTCP(), dr.ids.dnsCryptQuery)) {
- return false;
- }
- }
-#endif /* HAVE_DNSCRYPT */
-
- return true;
-}
-
-bool processResponse(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& respRuleActions, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
-{
- if (!applyRulesToResponse(respRuleActions, dr)) {
- return false;
- }
-
- if (dr.isAsynchronous()) {
- return true;
- }
-
- return processResponseAfterRules(response, cacheInsertedRespRuleActions, dr, muted);
-}
-
-static size_t getInitialUDPPacketBufferSize()
-{
- static_assert(s_udpIncomingBufferSize <= s_initialUDPPacketBufferSize, "The incoming buffer size should not be larger than s_initialUDPPacketBufferSize");
-
- if (g_proxyProtocolACL.empty()) {
- return s_initialUDPPacketBufferSize;
- }
-
- return s_initialUDPPacketBufferSize + g_proxyProtocolMaximumSize;
-}
-
-static size_t getMaximumIncomingPacketSize(const ClientState& cs)
-{
- if (cs.dnscryptCtx) {
- return getInitialUDPPacketBufferSize();
- }
-
- if (g_proxyProtocolACL.empty()) {
- return s_udpIncomingBufferSize;
- }
-
- return s_udpIncomingBufferSize + g_proxyProtocolMaximumSize;
-}
-
-bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
-{
-#ifndef DISABLE_DELAY_PIPE
- if (delayMsec && g_delay) {
- DelayedPacket dp{origFD, response, origRemote, origDest};
- g_delay->submit(dp, delayMsec);
- return true;
- }
-#endif /* DISABLE_DELAY_PIPE */
- ssize_t res = sendfromto(origFD, response.data(), response.size(), 0, origDest, origRemote);
- if (res == -1) {
- int err = errno;
- vinfolog("Error sending response to %s: %s", origRemote.toStringWithPort(), stringerror(err));
- }
-
- return true;
-}
-
-void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend)
-{
- handleResponseSent(ids.qname, ids.qtype, udiff, client, backend, size, cleartextDH, outgoingProtocol, ids.protocol, fromBackend);
-}
-
-void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend)
-{
- if (g_rings.shouldRecordResponses()) {
- struct timespec ts;
- gettime(&ts);
- g_rings.insertResponse(ts, client, qname, qtype, static_cast<unsigned int>(udiff), size, cleartextDH, backend, outgoingProtocol);
- }
-
- switch (cleartextDH.rcode) {
- case RCode::NXDomain:
- ++g_stats.frontendNXDomain;
- break;
- case RCode::ServFail:
- if (fromBackend) {
- ++g_stats.servfailResponses;
- }
- ++g_stats.frontendServFail;
- break;
- case RCode::NoError:
- ++g_stats.frontendNoError;
- break;
- }
-
- doLatencyStats(incomingProtocol, udiff);
-}
-
-static void handleResponseForUDPClient(InternalQueryState& ids, PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& respRuleActions, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, const std::shared_ptr<DownstreamState>& ds, bool isAsync, bool selfGenerated)
-{
- DNSResponse dr(ids, response, ds);
-
- if (ids.udpPayloadSize > 0 && response.size() > ids.udpPayloadSize) {
- vinfolog("Got a response of size %d while the initial UDP payload size was %d, truncating", response.size(), ids.udpPayloadSize);
- truncateTC(dr.getMutableData(), dr.getMaximumSize(), dr.ids.qname.wirelength());
- dr.getHeader()->tc = true;
- }
- else if (dr.getHeader()->tc && g_truncateTC) {
- truncateTC(response, dr.getMaximumSize(), dr.ids.qname.wirelength());
- }
-
- /* when the answer is encrypted in place, we need to get a copy
- of the original header before encryption to fill the ring buffer */
- dnsheader cleartextDH;
- memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
-
- if (!isAsync) {
- if (!processResponse(response, respRuleActions, cacheInsertedRespRuleActions, dr, ids.cs && ids.cs->muted)) {
- return;
- }
-
- if (dr.isAsynchronous()) {
- return;
- }
- }
-
- ++g_stats.responses;
- if (ids.cs) {
- ++ids.cs->responses;
- }
-
- bool muted = true;
- if (ids.cs && !ids.cs->muted) {
- ComboAddress empty;
- empty.sin4.sin_family = 0;
- sendUDPResponse(ids.cs->udpFD, response, dr.ids.delayMsec, ids.hopLocal, ids.hopRemote);
- muted = false;
- }
-
- if (!selfGenerated) {
- double udiff = ids.queryRealTime.udiff();
- if (!muted) {
- vinfolog("Got answer from %s, relayed to %s (UDP), took %f us", ds->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
- }
- else {
- vinfolog("Got answer from %s, NOT relayed to %s (UDP) since that frontend is muted, took %f us", ds->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
- }
-
- handleResponseSent(ids, udiff, dr.ids.origRemote, ds->d_config.remote, response.size(), cleartextDH, ds->getProtocol(), true);
- }
- else {
- handleResponseSent(ids, 0., dr.ids.origRemote, ComboAddress(), response.size(), cleartextDH, dnsdist::Protocol::DoUDP, false);
- }
-}
-
-// listens on a dedicated socket, lobs answers from downstream servers to original requestors
-void responderThread(std::shared_ptr<DownstreamState> dss)
-{
- try {
- setThreadName("dnsdist/respond");
- auto localRespRuleActions = g_respruleactions.getLocal();
- auto localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
- const size_t initialBufferSize = getInitialUDPPacketBufferSize();
- PacketBuffer response(initialBufferSize);
- uint16_t queryId = 0;
- std::vector<int> sockets;
- sockets.reserve(dss->sockets.size());
-
- for(;;) {
- try {
- dss->pickSocketsReadyForReceiving(sockets);
- if (dss->isStopped()) {
- break;
- }
-
- for (const auto& fd : sockets) {
- response.resize(initialBufferSize);
- ssize_t got = recv(fd, response.data(), response.size(), 0);
-
- if (got == 0 && dss->isStopped()) {
- break;
- }
-
- if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader)) {
- continue;
- }
-
- response.resize(static_cast<size_t>(got));
- dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
- queryId = dh->id;
-
- auto ids = dss->getState(queryId);
- if (!ids) {
- continue;
- }
-
- unsigned int qnameWireLength = 0;
- if (fd != ids->backendFD || !responseContentMatches(response, ids->qname, ids->qtype, ids->qclass, dss, qnameWireLength)) {
- dss->restoreState(queryId, std::move(*ids));
- continue;
- }
-
- auto du = std::move(ids->du);
-
- dh->id = ids->origID;
- ++dss->responses;
-
- double udiff = ids->queryRealTime.udiff();
- // do that _before_ the processing, otherwise it's not fair to the backend
- dss->latencyUsec = (127.0 * dss->latencyUsec / 128.0) + udiff / 128.0;
- dss->reportResponse(dh->rcode);
-
- /* don't call processResponse for DOH */
- if (du) {
-#ifdef HAVE_DNS_OVER_HTTPS
- // DoH query, we cannot touch du after that
- handleUDPResponseForDoH(std::move(du), std::move(response), std::move(*ids));
-#endif
- continue;
- }
-
- handleResponseForUDPClient(*ids, response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dss, false, false);
- }
- }
- catch (const std::exception& e) {
- vinfolog("Got an error in UDP responder thread while parsing a response from %s, id %d: %s", dss->d_config.remote.toStringWithPort(), queryId, e.what());
- }
- }
-}
-catch (const std::exception& e) {
- errlog("UDP responder thread died because of exception: %s", e.what());
-}
-catch (const PDNSException& e) {
- errlog("UDP responder thread died because of PowerDNS exception: %s", e.reason);
-}
-catch (...) {
- errlog("UDP responder thread died because of an exception: %s", "unknown");
-}
-}
-
-LockGuarded<LuaContext> g_lua{LuaContext()};
-ComboAddress g_serverControl{"127.0.0.1:5199"};
-
-
-static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent, bool raw)
-{
- string result;
-
- if (raw) {
- std::vector<std::string> raws;
- stringtok(raws, spoofContent, ",");
- SpoofAction sa(raws);
- sa(&dq, &result);
- }
- else {
- std::vector<std::string> addrs;
- stringtok(addrs, spoofContent, " ,");
-
- if (addrs.size() == 1) {
- try {
- ComboAddress spoofAddr(spoofContent);
- SpoofAction sa({spoofAddr});
- sa(&dq, &result);
- }
- catch(const PDNSException &e) {
- DNSName cname(spoofContent);
- SpoofAction sa(cname); // CNAME then
- sa(&dq, &result);
- }
- } else {
- std::vector<ComboAddress> cas;
- for (const auto& addr : addrs) {
- try {
- cas.push_back(ComboAddress(addr));
- }
- catch (...) {
- }
- }
- SpoofAction sa(cas);
- sa(&dq, &result);
- }
- }
-}
-
-static void spoofPacketFromString(DNSQuestion& dq, const string& spoofContent)
-{
- string result;
-
- SpoofAction sa(spoofContent.c_str(), spoofContent.size());
- sa(&dq, &result);
-}
-
-bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop)
-{
- if (dq.isAsynchronous()) {
- return false;
- }
-
- switch(action) {
- case DNSAction::Action::Allow:
- return true;
- break;
- case DNSAction::Action::Drop:
- ++g_stats.ruleDrop;
- drop = true;
- return true;
- break;
- case DNSAction::Action::Nxdomain:
- dq.getHeader()->rcode = RCode::NXDomain;
- dq.getHeader()->qr = true;
- return true;
- break;
- case DNSAction::Action::Refused:
- dq.getHeader()->rcode = RCode::Refused;
- dq.getHeader()->qr = true;
- return true;
- break;
- case DNSAction::Action::ServFail:
- dq.getHeader()->rcode = RCode::ServFail;
- dq.getHeader()->qr = true;
- return true;
- break;
- case DNSAction::Action::Spoof:
- spoofResponseFromString(dq, ruleresult, false);
- return true;
- break;
- case DNSAction::Action::SpoofPacket:
- spoofPacketFromString(dq, ruleresult);
- return true;
- break;
- case DNSAction::Action::SpoofRaw:
- spoofResponseFromString(dq, ruleresult, true);
- return true;
- break;
- case DNSAction::Action::Truncate:
- if (!dq.overTCP()) {
- dq.getHeader()->tc = true;
- dq.getHeader()->qr = true;
- dq.getHeader()->ra = dq.getHeader()->rd;
- dq.getHeader()->aa = false;
- dq.getHeader()->ad = false;
- ++g_stats.ruleTruncated;
- return true;
- }
- break;
- case DNSAction::Action::HeaderModify:
- return true;
- break;
- case DNSAction::Action::Pool:
- /* we need to keep this because a custom Lua action can return
- DNSAction.Spoof, 'poolname' */
- dq.ids.poolName = ruleresult;
- return true;
- break;
- case DNSAction::Action::NoRecurse:
- dq.getHeader()->rd = false;
- return true;
- break;
- /* non-terminal actions follow */
- case DNSAction::Action::Delay:
- pdns::checked_stoi_into(dq.ids.delayMsec, ruleresult); // sorry
- break;
- case DNSAction::Action::None:
- /* fall-through */
- case DNSAction::Action::NoOp:
- break;
- }
-
- /* false means that we don't stop the processing */
- return false;
-}
-
-
-static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const struct timespec& now)
-{
- if (g_rings.shouldRecordQueries()) {
- g_rings.insertQuery(now, dq.ids.origRemote, dq.ids.qname, dq.ids.qtype, dq.getData().size(), *dq.getHeader(), dq.getProtocol());
- }
-
- if (g_qcount.enabled) {
- string qname = dq.ids.qname.toLogString();
- bool countQuery{true};
- if (g_qcount.filter) {
- auto lock = g_lua.lock();
- std::tie (countQuery, qname) = g_qcount.filter(&dq);
- }
-
- if (countQuery) {
- auto records = g_qcount.records.write_lock();
- if (!records->count(qname)) {
- (*records)[qname] = 0;
- }
- (*records)[qname]++;
- }
- }
-
-#ifndef DISABLE_DYNBLOCKS
- /* the Dynamic Block mechanism supports address and port ranges, so we need to pass the full address and port */
- if (auto got = holders.dynNMGBlock->lookup(AddressAndPortRange(dq.ids.origRemote, dq.ids.origRemote.isIPv4() ? 32 : 128, 16))) {
- auto updateBlockStats = [&got]() {
- ++g_stats.dynBlocked;
- got->second.blocks++;
- };
-
- if (now < got->second.until) {
- DNSAction::Action action = got->second.action;
- if (action == DNSAction::Action::None) {
- action = g_dynBlockAction;
- }
- switch (action) {
- case DNSAction::Action::NoOp:
- /* do nothing */
- break;
-
- case DNSAction::Action::Nxdomain:
- vinfolog("Query from %s turned into NXDomain because of dynamic block", dq.ids.origRemote.toStringWithPort());
- updateBlockStats();
-
- dq.getHeader()->rcode = RCode::NXDomain;
- dq.getHeader()->qr=true;
- return true;
-
- case DNSAction::Action::Refused:
- vinfolog("Query from %s refused because of dynamic block", dq.ids.origRemote.toStringWithPort());
- updateBlockStats();
-
- dq.getHeader()->rcode = RCode::Refused;
- dq.getHeader()->qr = true;
- return true;
-
- case DNSAction::Action::Truncate:
- if (!dq.overTCP()) {
- updateBlockStats();
- vinfolog("Query from %s truncated because of dynamic block", dq.ids.origRemote.toStringWithPort());
- dq.getHeader()->tc = true;
- dq.getHeader()->qr = true;
- dq.getHeader()->ra = dq.getHeader()->rd;
- dq.getHeader()->aa = false;
- dq.getHeader()->ad = false;
- return true;
- }
- else {
- vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
- }
- break;
- case DNSAction::Action::NoRecurse:
- updateBlockStats();
- vinfolog("Query from %s setting rd=0 because of dynamic block", dq.ids.origRemote.toStringWithPort());
- dq.getHeader()->rd = false;
- return true;
- default:
- updateBlockStats();
- vinfolog("Query from %s dropped because of dynamic block", dq.ids.origRemote.toStringWithPort());
- return false;
- }
- }
- }
-
- if (auto got = holders.dynSMTBlock->lookup(dq.ids.qname)) {
- auto updateBlockStats = [&got]() {
- ++g_stats.dynBlocked;
- got->blocks++;
- };
-
- if (now < got->until) {
- DNSAction::Action action = got->action;
- if (action == DNSAction::Action::None) {
- action = g_dynBlockAction;
- }
- switch (action) {
- case DNSAction::Action::NoOp:
- /* do nothing */
- break;
- case DNSAction::Action::Nxdomain:
- vinfolog("Query from %s for %s turned into NXDomain because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
- updateBlockStats();
-
- dq.getHeader()->rcode = RCode::NXDomain;
- dq.getHeader()->qr = true;
- return true;
- case DNSAction::Action::Refused:
- vinfolog("Query from %s for %s refused because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
- updateBlockStats();
-
- dq.getHeader()->rcode = RCode::Refused;
- dq.getHeader()->qr = true;
- return true;
- case DNSAction::Action::Truncate:
- if (!dq.overTCP()) {
- updateBlockStats();
-
- vinfolog("Query from %s for %s truncated because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
- dq.getHeader()->tc = true;
- dq.getHeader()->qr = true;
- dq.getHeader()->ra = dq.getHeader()->rd;
- dq.getHeader()->aa = false;
- dq.getHeader()->ad = false;
- return true;
- }
- else {
- vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
- }
- break;
- case DNSAction::Action::NoRecurse:
- updateBlockStats();
- vinfolog("Query from %s setting rd=0 because of dynamic block", dq.ids.origRemote.toStringWithPort());
- dq.getHeader()->rd = false;
- return true;
- default:
- updateBlockStats();
- vinfolog("Query from %s for %s dropped because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
- return false;
- }
- }
- }
-#endif /* DISABLE_DYNBLOCKS */
-
- DNSAction::Action action = DNSAction::Action::None;
- string ruleresult;
- bool drop = false;
- for (const auto& lr : *holders.ruleactions) {
- if (lr.d_rule->matches(&dq)) {
- lr.d_rule->d_matches++;
- action = (*lr.d_action)(&dq, &ruleresult);
- if (processRulesResult(action, dq, ruleresult, drop)) {
- break;
- }
- }
- }
-
- if (drop) {
- return false;
- }
-
- return true;
-}
-
-ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ss, const int sd, const PacketBuffer& request, bool healthCheck)
-{
- ssize_t result;
-
- if (ss->d_config.sourceItf == 0) {
- result = send(sd, request.data(), request.size(), 0);
- }
- else {
- struct msghdr msgh;
- struct iovec iov;
- cmsgbuf_aligned cbuf;
- ComboAddress remote(ss->d_config.remote);
- fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), const_cast<char*>(reinterpret_cast<const char *>(request.data())), request.size(), &remote);
- addCMsgSrcAddr(&msgh, &cbuf, &ss->d_config.sourceAddr, ss->d_config.sourceItf);
- result = sendmsg(sd, &msgh, 0);
- }
-
- if (result == -1) {
- int savederrno = errno;
- vinfolog("Error sending request to backend %s: %s", ss->d_config.remote.toStringWithPort(), stringerror(savederrno));
-
- /* This might sound silly, but on Linux send() might fail with EINVAL
- if the interface the socket was bound to doesn't exist anymore.
- We don't want to reconnect the real socket if the healthcheck failed,
- because it's not using the same socket.
- */
- if (!healthCheck && (savederrno == EINVAL || savederrno == ENODEV || savederrno == ENETUNREACH)) {
- ss->reconnect();
- }
- }
-
- return result;
-}
-
-static bool isUDPQueryAcceptable(ClientState& cs, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, bool& expectProxyProtocol)
-{
- if (msgh->msg_flags & MSG_TRUNC) {
- /* message was too large for our buffer */
- vinfolog("Dropping message too large for our buffer");
- ++cs.nonCompliantQueries;
- ++g_stats.nonCompliantQueries;
- return false;
- }
-
- expectProxyProtocol = expectProxyProtocolFrom(remote);
- if (!holders.acl->match(remote) && !expectProxyProtocol) {
- vinfolog("Query from %s dropped because of ACL", remote.toStringWithPort());
- ++g_stats.aclDrops;
- return false;
- }
-
- if (HarvestDestinationAddress(msgh, &dest)) {
- /* so it turns out that sometimes the kernel lies to us:
- the address is set to 0.0.0.0:0 which makes our sendfromto() use
- the wrong address. In that case it's better to let the kernel
- do the work by itself and use sendto() instead.
- This is indicated by setting the family to 0 which is acted upon
- in sendUDPResponse() and DelayedPacket::().
- */
- const ComboAddress bogusV4("0.0.0.0:0");
- const ComboAddress bogusV6("[::]:0");
- if (dest.sin4.sin_family == AF_INET && dest == bogusV4) {
- dest.sin4.sin_family = 0;
- }
- else if (dest.sin4.sin_family == AF_INET6 && dest == bogusV6) {
- dest.sin4.sin_family = 0;
- }
- else {
- /* we don't get the port, only the address */
- dest.sin4.sin_port = cs.local.sin4.sin_port;
- }
- }
- else {
- dest.sin4.sin_family = 0;
- }
-
- ++cs.queries;
- ++g_stats.queries;
-
- return true;
-}
-
-bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp)
-{
- if (cs.dnscryptCtx) {
-#ifdef HAVE_DNSCRYPT
- PacketBuffer response;
- dnsCryptQuery = std::make_unique<DNSCryptQuery>(cs.dnscryptCtx);
-
- bool decrypted = handleDNSCryptQuery(query, *dnsCryptQuery, tcp, now, response);
-
- if (!decrypted) {
- if (response.size() > 0) {
- query = std::move(response);
- return true;
- }
- throw std::runtime_error("Unable to decrypt DNSCrypt query, dropping.");
- }
-#endif /* HAVE_DNSCRYPT */
- }
- return false;
-}
-
-bool checkQueryHeaders(const struct dnsheader* dh, ClientState& cs)
-{
- if (dh->qr) { // don't respond to responses
- ++g_stats.nonCompliantQueries;
- ++cs.nonCompliantQueries;
- return false;
- }
-
- if (dh->qdcount == 0) {
- ++g_stats.emptyQueries;
- if (g_dropEmptyQueries) {
- return false;
- }
- }
-
- if (dh->rd) {
- ++g_stats.rdQueries;
- }
-
- return true;
-}
-
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-static void queueResponse(const ClientState& cs, const PacketBuffer& response, const ComboAddress& dest, const ComboAddress& remote, struct mmsghdr& outMsg, struct iovec* iov, cmsgbuf_aligned* cbuf)
-{
- outMsg.msg_len = 0;
- fillMSGHdr(&outMsg.msg_hdr, iov, nullptr, 0, const_cast<char*>(reinterpret_cast<const char *>(&response.at(0))), response.size(), const_cast<ComboAddress*>(&remote));
-
- if (dest.sin4.sin_family == 0) {
- outMsg.msg_hdr.msg_control = nullptr;
- }
- else {
- addCMsgSrcAddr(&outMsg.msg_hdr, cbuf, &dest, 0);
- }
-}
-#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
-#endif /* DISABLE_RECVMMSG */
-
-/* self-generated responses or cache hits */
-static bool prepareOutgoingResponse(LocalHolders& holders, const ClientState& cs, DNSQuestion& dq, bool cacheHit)
-{
- std::shared_ptr<DownstreamState> ds{nullptr};
- DNSResponse dr(dq.ids, dq.getMutableData(), ds);
- dr.d_incomingTCPState = dq.d_incomingTCPState;
- dr.ids.selfGenerated = true;
-
- if (!applyRulesToResponse(cacheHit ? *holders.cacheHitRespRuleactions : *holders.selfAnsweredRespRuleactions, dr)) {
- return false;
- }
-
- if (dr.ids.ttlCap > 0) {
- std::string result;
- LimitTTLResponseAction ac(0, dr.ids.ttlCap, {});
- ac(&dr, &result);
- }
-
- if (cacheHit) {
- ++g_stats.cacheHits;
- }
-
- if (dr.isAsynchronous()) {
- return false;
- }
-
-#ifdef HAVE_DNSCRYPT
- if (!cs.muted) {
- if (!encryptResponse(dq.getMutableData(), dq.getMaximumSize(), dq.overTCP(), dq.ids.dnsCryptQuery)) {
- return false;
- }
- }
-#endif /* HAVE_DNSCRYPT */
-
- return true;
-}
-
-ProcessQueryResult processQueryAfterRules(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
-{
- const uint16_t queryId = ntohs(dq.getHeader()->id);
-
- try {
- if (dq.getHeader()->qr) { // something turned it into a response
- fixUpQueryTurnedResponse(dq, dq.ids.origFlags);
-
- if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, false)) {
- return ProcessQueryResult::Drop;
- }
-
- const auto rcode = dq.getHeader()->rcode;
- if (rcode == RCode::NXDomain) {
- ++g_stats.ruleNXDomain;
- }
- else if (rcode == RCode::Refused) {
- ++g_stats.ruleRefused;
- }
- else if (rcode == RCode::ServFail) {
- ++g_stats.ruleServFail;
- }
-
- ++g_stats.selfAnswered;
- ++dq.ids.cs->responses;
- return ProcessQueryResult::SendAnswer;
- }
-
- std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dq.ids.poolName);
- std::shared_ptr<ServerPolicy> poolPolicy = serverPool->policy;
- dq.ids.packetCache = serverPool->packetCache;
- const auto& policy = poolPolicy != nullptr ? *poolPolicy : *(holders.policy);
- const auto servers = serverPool->getServers();
- selectedBackend = policy.getSelectedBackend(*servers, dq);
-
- uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL;
-
- if (dq.ids.packetCache && !dq.ids.skipCache) {
- dq.ids.dnssecOK = (getEDNSZ(dq) & EDNS_HEADER_FLAG_DO);
- }
-
- if (dq.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool->getECS()))) {
- // we special case our cache in case a downstream explicitly gave us a universally valid response with a 0 scope
- // we need ECS parsing (parseECS) to be true so we can be sure that the initial incoming query did not have an existing
- // ECS option, which would make it unsuitable for the zero-scope feature.
- if (dq.ids.packetCache && !dq.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dq.ids.packetCache->isECSParsingEnabled()) {
- if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKeyNoECS, dq.ids.subnet, dq.ids.dnssecOK, !dq.overTCP(), allowExpired, false, true, false)) {
-
- vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), dq.ids.protocol.toString(), dq.getData().size());
-
- if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
- return ProcessQueryResult::Drop;
- }
-
- ++g_stats.responses;
- ++dq.ids.cs->responses;
- return ProcessQueryResult::SendAnswer;
- }
-
- if (!dq.ids.subnet) {
- /* there was no existing ECS on the query, enable the zero-scope feature */
- dq.ids.useZeroScope = true;
- }
- }
-
- if (!handleEDNSClientSubnet(dq, dq.ids.ednsAdded, dq.ids.ecsAdded)) {
- vinfolog("Dropping query from %s because we couldn't insert the ECS value", dq.ids.origRemote.toStringWithPort());
- return ProcessQueryResult::Drop;
- }
- }
-
- if (dq.ids.packetCache && !dq.ids.skipCache) {
- bool forwardedOverUDP = !dq.overTCP();
- if (selectedBackend && selectedBackend->isTCPOnly()) {
- forwardedOverUDP = false;
- }
-
- if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKey, dq.ids.subnet, dq.ids.dnssecOK, forwardedOverUDP, allowExpired, false, true, true)) {
-
- restoreFlags(dq.getHeader(), dq.ids.origFlags);
-
- vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), dq.ids.protocol.toString(), dq.getData().size());
-
- if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
- return ProcessQueryResult::Drop;
- }
-
- ++g_stats.responses;
- ++dq.ids.cs->responses;
- return ProcessQueryResult::SendAnswer;
- }
- else if (dq.ids.protocol == dnsdist::Protocol::DoH && !forwardedOverUDP) {
- /* do a second-lookup for UDP responses, but we do not want TC=1 answers */
- if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKeyUDP, dq.ids.subnet, dq.ids.dnssecOK, true, allowExpired, false, false, false)) {
- if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
- return ProcessQueryResult::Drop;
- }
-
- ++g_stats.responses;
- ++dq.ids.cs->responses;
- return ProcessQueryResult::SendAnswer;
- }
- }
-
- vinfolog("Packet cache miss for query for %s|%s from %s (%s, %d bytes)", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), dq.ids.protocol.toString(), dq.getData().size());
-
- ++g_stats.cacheMisses;
- }
-
- if (!selectedBackend) {
- ++g_stats.noPolicy;
-
- vinfolog("%s query for %s|%s from %s, no downstream server available", g_servFailOnNoPolicy ? "ServFailed" : "Dropped", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort());
- if (g_servFailOnNoPolicy) {
- dq.getHeader()->rcode = RCode::ServFail;
- dq.getHeader()->qr = true;
-
- fixUpQueryTurnedResponse(dq, dq.ids.origFlags);
-
- if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, false)) {
- return ProcessQueryResult::Drop;
- }
- ++g_stats.responses;
- ++dq.ids.cs->responses;
- // no response-only statistics counter to update.
- return ProcessQueryResult::SendAnswer;
- }
-
- return ProcessQueryResult::Drop;
- }
-
- /* save the DNS flags as sent to the backend so we can cache the answer with the right flags later */
- dq.ids.cacheFlags = *getFlagsFromDNSHeader(dq.getHeader());
-
- if (dq.addXPF && selectedBackend->d_config.xpfRRCode != 0) {
- addXPF(dq, selectedBackend->d_config.xpfRRCode);
- }
-
- selectedBackend->incQueriesCount();
- return ProcessQueryResult::PassToBackend;
- }
- catch (const std::exception& e){
- vinfolog("Got an error while parsing a %s query (after applying rules) from %s, id %d: %s", (dq.overTCP() ? "TCP" : "UDP"), dq.ids.origRemote.toStringWithPort(), queryId, e.what());
- }
- return ProcessQueryResult::Drop;
-}
-
-class UDPTCPCrossQuerySender : public TCPQuerySender
-{
-public:
- UDPTCPCrossQuerySender()
- {
- }
-
- ~UDPTCPCrossQuerySender()
- {
- }
-
- bool active() const override
- {
- return true;
- }
-
- void handleResponse(const struct timeval& now, TCPResponse&& response) override
- {
- if (!response.d_ds && !response.d_idstate.selfGenerated) {
- throw std::runtime_error("Passing a cross-protocol answer originated from UDP without a valid downstream");
- }
-
- auto& ids = response.d_idstate;
-
- static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
- static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
-
- handleResponseForUDPClient(ids, response.d_buffer, *localRespRuleActions, *localCacheInsertedRespRuleActions, response.d_ds, response.isAsync(), response.d_idstate.selfGenerated);
- }
-
- void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
- {
- return handleResponse(now, std::move(response));
- }
-
- void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
- {
- // nothing to do
- }
-};
-
-class UDPCrossProtocolQuery : public CrossProtocolQuery
-{
-public:
- UDPCrossProtocolQuery(PacketBuffer&& buffer_, InternalQueryState&& ids_, std::shared_ptr<DownstreamState> ds): CrossProtocolQuery(InternalQuery(std::move(buffer_), std::move(ids_)), ds)
- {
- auto& ids = query.d_idstate;
- const auto& buffer = query.d_buffer;
-
- if (ids.udpPayloadSize == 0) {
- uint16_t z = 0;
- getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(buffer.data()), buffer.size(), &ids.udpPayloadSize, &z);
- if (ids.udpPayloadSize < 512) {
- ids.udpPayloadSize = 512;
- }
- }
- }
-
- ~UDPCrossProtocolQuery()
- {
- }
-
- std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
- {
- return s_sender;
- }
-private:
- static std::shared_ptr<UDPTCPCrossQuerySender> s_sender;
-};
-
-std::shared_ptr<UDPTCPCrossQuerySender> UDPCrossProtocolQuery::s_sender = std::make_shared<UDPTCPCrossQuerySender>();
-
-std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dq);
-std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dq)
-{
- dq.ids.origID = dq.getHeader()->id;
- return std::make_unique<UDPCrossProtocolQuery>(std::move(dq.getMutableData()), std::move(dq.ids), nullptr);
-}
-
-ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
-{
- const uint16_t queryId = ntohs(dq.getHeader()->id);
-
- try {
- /* we need an accurate ("real") value for the response and
- to store into the IDS, but not for insertion into the
- rings for example */
- struct timespec now;
- gettime(&now);
-
- if (!applyRulesToQuery(holders, dq, now)) {
- return ProcessQueryResult::Drop;
- }
-
- if (dq.isAsynchronous()) {
- return ProcessQueryResult::Asynchronous;
- }
-
- return processQueryAfterRules(dq, holders, selectedBackend);
- }
- catch (const std::exception& e){
- vinfolog("Got an error while parsing a %s query from %s, id %d: %s", (dq.overTCP() ? "TCP" : "UDP"), dq.ids.origRemote.toStringWithPort(), queryId, e.what());
- }
- return ProcessQueryResult::Drop;
-}
-
-bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& ds, uint16_t queryID, DNSQuestion& dq, PacketBuffer& query, ComboAddress& dest)
-{
- bool doh = dq.ids.du != nullptr;
-
- bool failed = false;
- size_t proxyPayloadSize = 0;
- if (ds->d_config.useProxyProtocol) {
- try {
- if (addProxyProtocol(dq, &proxyPayloadSize)) {
- if (dq.ids.du) {
- dq.ids.du->proxyProtocolPayloadSize = proxyPayloadSize;
- }
- }
- }
- catch (const std::exception& e) {
- vinfolog("Adding proxy protocol payload to %s query from %s failed: %s", (dq.ids.du ? "DoH" : ""), dq.ids.origDest.toStringWithPort(), e.what());
- return false;
- }
- }
-
- try {
- int fd = ds->pickSocketForSending();
- dq.ids.backendFD = fd;
- dq.ids.origID = queryID;
- dq.ids.forwardedOverUDP = true;
-
- vinfolog("Got query for %s|%s from %s%s, relayed to %s", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), (doh ? " (https)" : ""), ds->getNameWithAddr());
-
- auto idOffset = ds->saveState(std::move(dq.ids));
- /* set the correct ID */
- memcpy(query.data() + proxyPayloadSize, &idOffset, sizeof(idOffset));
-
- /* you can't touch ids or du after this line, unless the call returned a non-negative value,
- because it might already have been freed */
- ssize_t ret = udpClientSendRequestToBackend(ds, fd, query);
-
- if (ret < 0) {
- failed = true;
- }
-
- if (failed) {
- /* clear up the state. In the very unlikely event it was reused
- in the meantime, so be it. */
- auto cleared = ds->getState(idOffset);
- if (cleared) {
- dq.ids.du = std::move(cleared->du);
- if (dq.ids.du) {
- dq.ids.du->status_code = 502;
- }
- }
- ++g_stats.downstreamSendErrors;
- ++ds->sendErrors;
- return false;
- }
- }
- catch (const std::exception& e) {
- throw;
- }
-
- return true;
-}
-
-static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, PacketBuffer& query, struct mmsghdr* responsesVect, unsigned int* queuedResponses, struct iovec* respIOV, cmsgbuf_aligned* respCBuf)
-{
- assert(responsesVect == nullptr || (queuedResponses != nullptr && respIOV != nullptr && respCBuf != nullptr));
- uint16_t queryId = 0;
- InternalQueryState ids;
- ids.cs = &cs;
- ids.origRemote = remote;
- ids.hopRemote = remote;
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- try {
- bool expectProxyProtocol = false;
- if (!isUDPQueryAcceptable(cs, holders, msgh, remote, dest, expectProxyProtocol)) {
- return;
- }
- /* dest might have been updated, if we managed to harvest the destination address */
- if (dest.sin4.sin_family != 0) {
- ids.origDest = dest;
- ids.hopLocal = dest;
- }
- else {
- /* if we have not been able to harvest the destination address,
- we do NOT want to update dest or hopLocal, to let the kernel
- pick the less terrible option, but we want to update origDest
- which is used by rules and actions to at least the correct
- address family */
- ids.origDest = cs.local;
- ids.hopLocal.sin4.sin_family = 0;
- }
-
- std::vector<ProxyProtocolValue> proxyProtocolValues;
- if (expectProxyProtocol && !handleProxyProtocol(remote, false, *holders.acl, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
- return;
- }
-
- ids.queryRealTime.start();
-
- auto dnsCryptResponse = checkDNSCryptQuery(cs, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
- if (dnsCryptResponse) {
- sendUDPResponse(cs.udpFD, query, 0, dest, remote);
- return;
- }
-
- {
- /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
- struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query.data());
- queryId = ntohs(dh->id);
-
- if (!checkQueryHeaders(dh, cs)) {
- return;
- }
-
- if (dh->qdcount == 0) {
- dh->rcode = RCode::NotImp;
- dh->qr = true;
- sendUDPResponse(cs.udpFD, query, 0, dest, remote);
- return;
- }
- }
-
- ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- if (ids.dnsCryptQuery) {
- ids.protocol = dnsdist::Protocol::DNSCryptUDP;
- }
- DNSQuestion dq(ids, query);
- const uint16_t* flags = getFlagsFromDNSHeader(dq.getHeader());
- ids.origFlags = *flags;
-
- if (!proxyProtocolValues.empty()) {
- dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
- }
-
- std::shared_ptr<DownstreamState> ss{nullptr};
- auto result = processQuery(dq, holders, ss);
-
- if (result == ProcessQueryResult::Drop || result == ProcessQueryResult::Asynchronous) {
- return;
- }
-
- // the buffer might have been invalidated by now (resized)
- struct dnsheader* dh = dq.getHeader();
- if (result == ProcessQueryResult::SendAnswer) {
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
- if (dq.ids.delayMsec == 0 && responsesVect != nullptr) {
- queueResponse(cs, query, dest, remote, responsesVect[*queuedResponses], respIOV, respCBuf);
- (*queuedResponses)++;
- return;
- }
-#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
-#endif /* DISABLE_RECVMMSG */
- /* we use dest, always, because we don't want to use the listening address to send a response since it could be 0.0.0.0 */
- sendUDPResponse(cs.udpFD, query, dq.ids.delayMsec, dest, remote);
-
- handleResponseSent(dq.ids.qname, dq.ids.qtype, 0., remote, ComboAddress(), query.size(), *dh, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
- return;
- }
-
- if (result != ProcessQueryResult::PassToBackend || ss == nullptr) {
- return;
- }
-
- if (ss->isTCPOnly()) {
- std::string proxyProtocolPayload;
- /* we need to do this _before_ creating the cross protocol query because
- after that the buffer will have been moved */
- if (ss->d_config.useProxyProtocol) {
- proxyProtocolPayload = getProxyProtocolPayload(dq);
- }
-
- ids.origID = dh->id;
- auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), ss);
- cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
-
- ss->passCrossProtocolQuery(std::move(cpq));
- return;
- }
-
- assignOutgoingUDPQueryToBackend(ss, dh->id, dq, query, dest);
- }
- catch(const std::exception& e){
- vinfolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", ids.origRemote.toStringWithPort(), queryId, e.what());
- }
-}
-
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-static void MultipleMessagesUDPClientThread(ClientState* cs, LocalHolders& holders)
-{
- struct MMReceiver
- {
- PacketBuffer packet;
- ComboAddress remote;
- ComboAddress dest;
- struct iovec iov;
- /* used by HarvestDestinationAddress */
- cmsgbuf_aligned cbuf;
- };
- const size_t vectSize = g_udpVectorSize;
-
- if (vectSize > std::numeric_limits<uint16_t>::max()) {
- throw std::runtime_error("The value of setUDPMultipleMessagesVectorSize is too high, the maximum value is " + std::to_string(std::numeric_limits<uint16_t>::max()));
- }
-
- auto recvData = std::make_unique<MMReceiver[]>(vectSize);
- auto msgVec = std::make_unique<struct mmsghdr[]>(vectSize);
- auto outMsgVec = std::make_unique<struct mmsghdr[]>(vectSize);
-
- /* the actual buffer is larger because:
- - we may have to add EDNS and/or ECS
- - we use it for self-generated responses (from rule or cache)
- but we only accept incoming payloads up to that size
- */
- const size_t initialBufferSize = getInitialUDPPacketBufferSize();
- const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*cs);
-
- /* initialize the structures needed to receive our messages */
- for (size_t idx = 0; idx < vectSize; idx++) {
- recvData[idx].remote.sin4.sin_family = cs->local.sin4.sin_family;
- recvData[idx].packet.resize(initialBufferSize);
- fillMSGHdr(&msgVec[idx].msg_hdr, &recvData[idx].iov, &recvData[idx].cbuf, sizeof(recvData[idx].cbuf), reinterpret_cast<char*>(&recvData[idx].packet.at(0)), maxIncomingPacketSize, &recvData[idx].remote);
- }
-
- /* go now */
- for(;;) {
-
- /* reset the IO vector, since it's also used to send the vector of responses
- to avoid having to copy the data around */
- for (size_t idx = 0; idx < vectSize; idx++) {
- recvData[idx].packet.resize(initialBufferSize);
- recvData[idx].iov.iov_base = &recvData[idx].packet.at(0);
- recvData[idx].iov.iov_len = recvData[idx].packet.size();
- }
-
- /* block until we have at least one message ready, but return
- as many as possible to save the syscall costs */
- int msgsGot = recvmmsg(cs->udpFD, msgVec.get(), vectSize, MSG_WAITFORONE | MSG_TRUNC, nullptr);
-
- if (msgsGot <= 0) {
- vinfolog("Getting UDP messages via recvmmsg() failed with: %s", stringerror());
- continue;
- }
-
- unsigned int msgsToSend = 0;
-
- /* process the received messages */
- for (int msgIdx = 0; msgIdx < msgsGot; msgIdx++) {
- const struct msghdr* msgh = &msgVec[msgIdx].msg_hdr;
- unsigned int got = msgVec[msgIdx].msg_len;
- const ComboAddress& remote = recvData[msgIdx].remote;
-
- if (static_cast<size_t>(got) < sizeof(struct dnsheader)) {
- ++g_stats.nonCompliantQueries;
- ++cs->nonCompliantQueries;
- continue;
- }
-
- recvData[msgIdx].packet.resize(got);
- processUDPQuery(*cs, holders, msgh, remote, recvData[msgIdx].dest, recvData[msgIdx].packet, outMsgVec.get(), &msgsToSend, &recvData[msgIdx].iov, &recvData[msgIdx].cbuf);
- }
-
- /* immediate (not delayed or sent to a backend) responses (mostly from a rule, dynamic block
- or the cache) can be sent in batch too */
-
- if (msgsToSend > 0 && msgsToSend <= static_cast<unsigned int>(msgsGot)) {
- int sent = sendmmsg(cs->udpFD, outMsgVec.get(), msgsToSend, 0);
-
- if (sent < 0 || static_cast<unsigned int>(sent) != msgsToSend) {
- vinfolog("Error sending responses with sendmmsg() (%d on %u): %s", sent, msgsToSend, stringerror());
- }
- }
-
- }
-}
-#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
-#endif /* DISABLE_RECVMMSG */
-
-// listens to incoming queries, sends out to downstream servers, noting the intended return path
-static void udpClientThread(std::vector<ClientState*> states)
-{
- try {
- setThreadName("dnsdist/udpClie");
- LocalHolders holders;
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
- if (g_udpVectorSize > 1) {
- MultipleMessagesUDPClientThread(states.at(0), holders);
- }
- else
-#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
-#endif /* DISABLE_RECVMMSG */
- {
- /* the actual buffer is larger because:
- - we may have to add EDNS and/or ECS
- - we use it for self-generated responses (from rule or cache)
- but we only accept incoming payloads up to that size
- */
- struct UDPStateParam
- {
- ClientState* cs{nullptr};
- size_t maxIncomingPacketSize{0};
- int socket{-1};
- };
- const size_t initialBufferSize = getInitialUDPPacketBufferSize();
- PacketBuffer packet(initialBufferSize);
-
- struct msghdr msgh;
- struct iovec iov;
- ComboAddress remote;
- ComboAddress dest;
-
- auto handleOnePacket = [&packet, &iov, &holders, &msgh, &remote, &dest, initialBufferSize](const UDPStateParam& param) {
- packet.resize(initialBufferSize);
- iov.iov_base = &packet.at(0);
- iov.iov_len = packet.size();
-
- ssize_t got = recvmsg(param.socket, &msgh, 0);
-
- if (got < 0 || static_cast<size_t>(got) < sizeof(struct dnsheader)) {
- ++g_stats.nonCompliantQueries;
- ++param.cs->nonCompliantQueries;
- return;
- }
-
- packet.resize(static_cast<size_t>(got));
-
- processUDPQuery(*param.cs, holders, &msgh, remote, dest, packet, nullptr, nullptr, nullptr, nullptr);
- };
-
- std::vector<UDPStateParam> params;
- for (auto& state : states) {
- const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*state);
- params.emplace_back(UDPStateParam{state, maxIncomingPacketSize, state->udpFD});
- }
-
- if (params.size() == 1) {
- auto param = params.at(0);
- remote.sin4.sin_family = param.cs->local.sin4.sin_family;
- /* used by HarvestDestinationAddress */
- cmsgbuf_aligned cbuf;
- fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param.maxIncomingPacketSize, &remote);
- while (true) {
- try {
- handleOnePacket(param);
- }
- catch (const std::bad_alloc& e) {
- /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
- in which case we DO NOT want to log (as it would trigger another memory allocation attempt
- that might throw as well) but wait a bit (one millisecond) and then try to recover */
- usleep(1000);
- }
- }
- }
- else {
- auto callback = [&remote, &msgh, &iov, &packet, &handleOnePacket, initialBufferSize](int socket, FDMultiplexer::funcparam_t& funcparam) {
- auto param = boost::any_cast<const UDPStateParam*>(funcparam);
- try {
- remote.sin4.sin_family = param->cs->local.sin4.sin_family;
- packet.resize(initialBufferSize);
- /* used by HarvestDestinationAddress */
- cmsgbuf_aligned cbuf;
- fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param->maxIncomingPacketSize, &remote);
- handleOnePacket(*param);
- }
- catch (const std::bad_alloc& e) {
- /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
- in which case we DO NOT want to log (as it would trigger another memory allocation attempt
- that might throw as well) but wait a bit (one millisecond) and then try to recover */
- usleep(1000);
- }
- };
- auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
- for (size_t idx = 0; idx < params.size(); idx++) {
- const auto& param = params.at(idx);
- mplexer->addReadFD(param.socket, callback, ¶m);
- }
-
- struct timeval tv;
- while (true) {
- mplexer->run(&tv, -1);
- }
- }
- }
- }
- catch (const std::exception &e) {
- errlog("UDP client thread died because of exception: %s", e.what());
- }
- catch (const PDNSException &e) {
- errlog("UDP client thread died because of PowerDNS exception: %s", e.reason);
- }
- catch (...) {
- errlog("UDP client thread died because of an exception: %s", "unknown");
- }
-}
-
-boost::optional<uint64_t> g_maxTCPClientThreads{boost::none};
-pdns::stat16_t g_cacheCleaningDelay{60};
-pdns::stat16_t g_cacheCleaningPercentage{100};
-
-static void maintThread()
-{
- setThreadName("dnsdist/main");
- int interval = 1;
- size_t counter = 0;
- int32_t secondsToWaitLog = 0;
-
- for (;;) {
- sleep(interval);
-
- {
- auto lua = g_lua.lock();
- auto f = lua->readVariable<boost::optional<std::function<void()> > >("maintenance");
- if (f) {
- try {
- (*f)();
- secondsToWaitLog = 0;
- }
- catch(const std::exception &e) {
- if (secondsToWaitLog <= 0) {
- infolog("Error during execution of maintenance function: %s", e.what());
- secondsToWaitLog = 61;
- }
- secondsToWaitLog -= interval;
- }
- }
- }
-
- counter++;
- if (counter >= g_cacheCleaningDelay) {
- /* keep track, for each cache, of whether we should keep
- expired entries */
- std::map<std::shared_ptr<DNSDistPacketCache>, bool> caches;
-
- /* gather all caches actually used by at least one pool, and see
- if something prevents us from cleaning the expired entries */
- auto localPools = g_pools.getLocal();
- for (const auto& entry : *localPools) {
- auto& pool = entry.second;
-
- auto packetCache = pool->packetCache;
- if (!packetCache) {
- continue;
- }
-
- auto pair = caches.insert({packetCache, false});
- auto& iter = pair.first;
- /* if we need to keep stale data for this cache (ie, not clear
- expired entries when at least one pool using this cache
- has all its backends down) */
- if (packetCache->keepStaleData() && iter->second == false) {
- /* so far all pools had at least one backend up */
- if (pool->countServers(true) == 0) {
- iter->second = true;
- }
- }
- }
-
- const time_t now = time(nullptr);
- for (const auto& pair : caches) {
- /* shall we keep expired entries ? */
- if (pair.second == true) {
- continue;
- }
- auto& packetCache = pair.first;
- size_t upTo = (packetCache->getMaxEntries()* (100 - g_cacheCleaningPercentage)) / 100;
- packetCache->purgeExpired(upTo, now);
- }
- counter = 0;
- }
- }
-}
-
-#ifndef DISABLE_DYNBLOCKS
-static void dynBlockMaintenanceThread()
-{
- setThreadName("dnsdist/dynBloc");
-
- DynBlockMaintenance::run();
-}
-#endif
-
-#ifndef DISABLE_SECPOLL
-static void secPollThread()
-{
- setThreadName("dnsdist/secpoll");
-
- for (;;) {
- try {
- doSecPoll(g_secPollSuffix);
- }
- catch(...) {
- }
- // coverity[store_truncates_time_t]
- sleep(g_secPollInterval);
- }
-}
-#endif /* DISABLE_SECPOLL */
-
-static void healthChecksThread()
-{
- setThreadName("dnsdist/healthC");
-
- constexpr int interval = 1;
- auto states = g_dstates.getLocal(); // this points to the actual shared_ptrs!
-
- for (;;) {
- sleep(interval);
-
- std::unique_ptr<FDMultiplexer> mplexer{nullptr};
- for (auto& dss : *states) {
- auto delta = dss->sw.udiffAndSet()/1000000.0;
- dss->queryLoad.store(1.0*(dss->queries.load() - dss->prev.queries.load())/delta);
- dss->dropRate.store(1.0*(dss->reuseds.load() - dss->prev.reuseds.load())/delta);
- dss->prev.queries.store(dss->queries.load());
- dss->prev.reuseds.store(dss->reuseds.load());
-
- dss->handleUDPTimeouts();
-
- if (!dss->healthCheckRequired()) {
- continue;
- }
-
- if (!mplexer) {
- mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states->size()));
- }
-
- if (!queueHealthCheck(mplexer, dss)) {
- dss->submitHealthCheckResult(false, false);
- }
- }
-
- if (mplexer) {
- handleQueuedHealthChecks(*mplexer);
- }
- }
-}
-
-static void bindAny(int af, int sock)
-{
- __attribute__((unused)) int one = 1;
-
-#ifdef IP_FREEBIND
- if (setsockopt(sock, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0)
- warnlog("Warning: IP_FREEBIND setsockopt failed: %s", stringerror());
-#endif
-
-#ifdef IP_BINDANY
- if (af == AF_INET)
- if (setsockopt(sock, IPPROTO_IP, IP_BINDANY, &one, sizeof(one)) < 0)
- warnlog("Warning: IP_BINDANY setsockopt failed: %s", stringerror());
-#endif
-#ifdef IPV6_BINDANY
- if (af == AF_INET6)
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_BINDANY, &one, sizeof(one)) < 0)
- warnlog("Warning: IPV6_BINDANY setsockopt failed: %s", stringerror());
-#endif
-#ifdef SO_BINDANY
- if (setsockopt(sock, SOL_SOCKET, SO_BINDANY, &one, sizeof(one)) < 0)
- warnlog("Warning: SO_BINDANY setsockopt failed: %s", stringerror());
-#endif
-}
-
-static void dropGroupPrivs(gid_t gid)
-{
- if (gid) {
- if (setgid(gid) == 0) {
- if (setgroups(0, NULL) < 0) {
- warnlog("Warning: Unable to drop supplementary gids: %s", stringerror());
- }
- }
- else {
- warnlog("Warning: Unable to set group ID to %d: %s", gid, stringerror());
- }
- }
-}
-
-static void dropUserPrivs(uid_t uid)
-{
- if(uid) {
- if(setuid(uid) < 0) {
- warnlog("Warning: Unable to set user ID to %d: %s", uid, stringerror());
- }
- }
-}
-
-static void checkFileDescriptorsLimits(size_t udpBindsCount, size_t tcpBindsCount)
-{
- /* stdin, stdout, stderr */
- rlim_t requiredFDsCount = 3;
- auto backends = g_dstates.getLocal();
- /* UDP sockets to backends */
- size_t backendUDPSocketsCount = 0;
- for (const auto& backend : *backends) {
- backendUDPSocketsCount += backend->sockets.size();
- }
- requiredFDsCount += backendUDPSocketsCount;
- /* TCP sockets to backends */
- if (g_maxTCPClientThreads) {
- requiredFDsCount += (backends->size() * (*g_maxTCPClientThreads));
- }
- /* listening sockets */
- requiredFDsCount += udpBindsCount;
- requiredFDsCount += tcpBindsCount;
- /* number of TCP connections currently served, assuming 1 connection per worker thread which is of course not right */
- if (g_maxTCPClientThreads) {
- requiredFDsCount += *g_maxTCPClientThreads;
- /* max pipes for communicating between TCP acceptors and client threads */
- requiredFDsCount += (*g_maxTCPClientThreads * 2);
- }
- /* max TCP queued connections */
- requiredFDsCount += g_maxTCPQueuedConnections;
- /* DelayPipe pipe */
- requiredFDsCount += 2;
- /* syslog socket */
- requiredFDsCount++;
- /* webserver main socket */
- requiredFDsCount++;
- /* console main socket */
- requiredFDsCount++;
- /* carbon export */
- requiredFDsCount++;
- /* history file */
- requiredFDsCount++;
- struct rlimit rl;
- getrlimit(RLIMIT_NOFILE, &rl);
- if (rl.rlim_cur <= requiredFDsCount) {
- warnlog("Warning, this configuration can use more than %d file descriptors, web server and console connections not included, and the current limit is %d.", std::to_string(requiredFDsCount), std::to_string(rl.rlim_cur));
-#ifdef HAVE_SYSTEMD
- warnlog("You can increase this value by using LimitNOFILE= in the systemd unit file or ulimit.");
-#else
- warnlog("You can increase this value by using ulimit.");
-#endif
- }
-}
-
-static bool g_warned_ipv6_recvpktinfo = false;
-
-static void setUpLocalBind(std::unique_ptr<ClientState>& cstate)
-{
- auto setupSocket = [](ClientState& cs, const ComboAddress& addr, int& socket, bool tcp, bool warn) {
- (void) warn;
- socket = SSocket(addr.sin4.sin_family, tcp == false ? SOCK_DGRAM : SOCK_STREAM, 0);
-
- if (tcp) {
- SSetsockopt(socket, SOL_SOCKET, SO_REUSEADDR, 1);
-#ifdef TCP_DEFER_ACCEPT
- SSetsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
-#endif
- if (cs.fastOpenQueueSize > 0) {
-#ifdef TCP_FASTOPEN
- SSetsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, cs.fastOpenQueueSize);
-#ifdef TCP_FASTOPEN_KEY
- if (!g_TCPFastOpenKey.empty()) {
- auto res = setsockopt(socket, IPPROTO_IP, TCP_FASTOPEN_KEY, g_TCPFastOpenKey.data(), g_TCPFastOpenKey.size() * sizeof(g_TCPFastOpenKey[0]));
- if (res == -1) {
- throw runtime_error("setsockopt for level IPPROTO_TCP and opname TCP_FASTOPEN_KEY failed: " + stringerror());
- }
- }
-#endif /* TCP_FASTOPEN_KEY */
-#else /* TCP_FASTOPEN */
- if (warn) {
- warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", addr.toStringWithPort());
- }
-#endif /* TCP_FASTOPEN */
- }
- }
-
- if (addr.sin4.sin_family == AF_INET6) {
- SSetsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, 1);
- }
-
- bindAny(addr.sin4.sin_family, socket);
-
- if (!tcp && IsAnyAddress(addr)) {
- int one = 1;
- (void) setsockopt(socket, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
-#ifdef IPV6_RECVPKTINFO
- if (addr.isIPv6() && setsockopt(socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) < 0 &&
- !g_warned_ipv6_recvpktinfo) {
- warnlog("Warning: IPV6_RECVPKTINFO setsockopt failed: %s", stringerror());
- g_warned_ipv6_recvpktinfo = true;
- }
-#endif
- }
-
- if (cs.reuseport) {
- if (!setReusePort(socket)) {
- if (warn) {
- /* no need to warn again if configured but support is not available, we already did for UDP */
- warnlog("SO_REUSEPORT has been configured on local address '%s' but is not supported", addr.toStringWithPort());
- }
- }
- }
-
- /* Only set this on IPv4 UDP sockets.
- Don't set it for DNSCrypt binds. DNSCrypt pads queries for privacy
- purposes, so we do receive large, sometimes fragmented datagrams. */
- if (!tcp && !cs.dnscryptCtx) {
- try {
- setSocketIgnorePMTU(socket, addr.sin4.sin_family);
- }
- catch (const std::exception& e) {
- warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
- }
- }
-
- if (!tcp) {
- if (g_socketUDPSendBuffer > 0) {
- try {
- setSocketSendBuffer(socket, g_socketUDPSendBuffer);
- }
- catch (const std::exception& e) {
- warnlog(e.what());
- }
- }
-
- if (g_socketUDPRecvBuffer > 0) {
- try {
- setSocketReceiveBuffer(socket, g_socketUDPRecvBuffer);
- }
- catch (const std::exception& e) {
- warnlog(e.what());
- }
- }
- }
-
- const std::string& itf = cs.interface;
- if (!itf.empty()) {
-#ifdef SO_BINDTODEVICE
- int res = setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, itf.c_str(), itf.length());
- if (res != 0) {
- warnlog("Error setting up the interface on local address '%s': %s", addr.toStringWithPort(), stringerror());
- }
-#else
- if (warn) {
- warnlog("An interface has been configured on local address '%s' but SO_BINDTODEVICE is not supported", addr.toStringWithPort());
- }
-#endif
- }
-
-#ifdef HAVE_EBPF
- if (g_defaultBPFFilter && !g_defaultBPFFilter->isExternal()) {
- cs.attachFilter(g_defaultBPFFilter, socket);
- vinfolog("Attaching default BPF Filter to %s frontend %s", (!tcp ? "UDP" : "TCP"), addr.toStringWithPort());
- }
-#endif /* HAVE_EBPF */
-
- SBind(socket, addr);
-
- if (tcp) {
- SListen(socket, cs.tcpListenQueueSize);
-
- if (cs.tlsFrontend != nullptr) {
- infolog("Listening on %s for TLS", addr.toStringWithPort());
- }
- else if (cs.dohFrontend != nullptr) {
- infolog("Listening on %s for DoH", addr.toStringWithPort());
- }
- else if (cs.dnscryptCtx != nullptr) {
- infolog("Listening on %s for DNSCrypt", addr.toStringWithPort());
- }
- else {
- infolog("Listening on %s", addr.toStringWithPort());
- }
- }
- };
-
- /* skip some warnings if there is an identical UDP context */
- bool warn = cstate->tcp == false || cstate->tlsFrontend != nullptr || cstate->dohFrontend != nullptr;
- int& fd = cstate->tcp == false ? cstate->udpFD : cstate->tcpFD;
- (void) warn;
-
- setupSocket(*cstate, cstate->local, fd, cstate->tcp, warn);
-
- for (auto& [addr, socket] : cstate->d_additionalAddresses) {
- setupSocket(*cstate, addr, socket, true, false);
- }
-
- if (cstate->tlsFrontend != nullptr) {
- if (!cstate->tlsFrontend->setupTLS()) {
- errlog("Error while setting up TLS on local address '%s', exiting", cstate->local.toStringWithPort());
- _exit(EXIT_FAILURE);
- }
- }
-
- if (cstate->dohFrontend != nullptr) {
- cstate->dohFrontend->setup();
- }
-
- cstate->ready = true;
-}
-
-struct
-{
- vector<string> locals;
- vector<string> remotes;
- bool checkConfig{false};
- bool beClient{false};
- bool beSupervised{false};
- string command;
- string config;
- string uid;
- string gid;
-} g_cmdLine;
-
-std::atomic<bool> g_configurationDone{false};
-
-static void usage()
-{
- cout<<endl;
- cout<<"Syntax: dnsdist [-C,--config file] [-c,--client [IP[:PORT]]]\n";
- cout<<"[-e,--execute cmd] [-h,--help] [-l,--local addr]\n";
- cout<<"[-v,--verbose] [--check-config] [--version]\n";
- cout<<"\n";
- cout<<"-a,--acl netmask Add this netmask to the ACL\n";
- cout<<"-C,--config file Load configuration from 'file'\n";
- cout<<"-c,--client Operate as a client, connect to dnsdist. This reads\n";
- cout<<" controlSocket from your configuration file, but also\n";
- cout<<" accepts an IP:PORT argument\n";
-#ifdef HAVE_LIBSODIUM
- cout<<"-k,--setkey KEY Use KEY for encrypted communication to dnsdist. This\n";
- cout<<" is similar to setting setKey in the configuration file.\n";
- cout<<" NOTE: this will leak this key in your shell's history\n";
- cout<<" and in the systems running process list.\n";
-#endif
- cout<<"--check-config Validate the configuration file and exit. The exit-code\n";
- cout<<" reflects the validation, 0 is OK, 1 means an error.\n";
- cout<<" Any errors are printed as well.\n";
- cout<<"-e,--execute cmd Connect to dnsdist and execute 'cmd'\n";
- cout<<"-g,--gid gid Change the process group ID after binding sockets\n";
- cout<<"-h,--help Display this helpful message\n";
- cout<<"-l,--local address Listen on this local address\n";
- cout<<"--supervised Don't open a console, I'm supervised\n";
- cout<<" (use with e.g. systemd and daemontools)\n";
- cout<<"--disable-syslog Don't log to syslog, only to stdout\n";
- cout<<" (use with e.g. systemd)\n";
- cout<<"--log-timestamps Prepend timestamps to messages logged to stdout.\n";
- cout<<"-u,--uid uid Change the process user ID after binding sockets\n";
- cout<<"-v,--verbose Enable verbose mode\n";
- cout<<"-V,--version Show dnsdist version information and exit\n";
-}
-
-#ifdef COVERAGE
-extern "C"
-{
- void __gcov_dump(void);
-}
-
-static void cleanupLuaObjects()
-{
- /* when our coverage mode is enabled, we need to make
- that the Lua objects destroyed before the Lua contexts. */
- g_ruleactions.setState({});
- g_respruleactions.setState({});
- g_cachehitrespruleactions.setState({});
- g_selfansweredrespruleactions.setState({});
- g_dstates.setState({});
- g_policy.setState(ServerPolicy());
- clearWebHandlers();
-}
-
-static void sigTermHandler(int)
-{
- cleanupLuaObjects();
- __gcov_dump();
- _exit(EXIT_SUCCESS);
-}
-#else /* COVERAGE */
-
-/* g++ defines __SANITIZE_THREAD__
- clang++ supports the nice __has_feature(thread_sanitizer),
- let's merge them */
-#if defined(__has_feature)
-#if __has_feature(thread_sanitizer)
-#define __SANITIZE_THREAD__ 1
-#endif
-#endif
-
-static void sigTermHandler(int)
-{
-#if !defined(__SANITIZE_THREAD__)
- /* TSAN is rightfully unhappy about this:
- WARNING: ThreadSanitizer: signal-unsafe call inside of a signal
- This is not a real problem for us, as the worst case is that
- we crash trying to exit, but let's try to avoid the warnings
- in our tests.
- */
- if (g_syslog) {
- syslog(LOG_INFO, "Exiting on user request");
- }
- std::cout<<"Exiting on user request"<<std::endl;
-#endif /* __SANITIZE_THREAD__ */
-
- _exit(EXIT_SUCCESS);
-}
-#endif /* COVERAGE */
-
-int main(int argc, char** argv)
-{
- try {
- size_t udpBindsCount = 0;
- size_t tcpBindsCount = 0;
-#ifdef HAVE_LIBEDIT
-#ifndef DISABLE_COMPLETION
- rl_attempted_completion_function = my_completion;
- rl_completion_append_character = 0;
-#endif /* DISABLE_COMPLETION */
-#endif /* HAVE_LIBEDIT */
-
- signal(SIGPIPE, SIG_IGN);
- signal(SIGCHLD, SIG_IGN);
- signal(SIGTERM, sigTermHandler);
-
- openlog("dnsdist", LOG_PID|LOG_NDELAY, LOG_DAEMON);
-
-#ifdef HAVE_LIBSODIUM
- if (sodium_init() == -1) {
- cerr<<"Unable to initialize crypto library"<<endl;
- exit(EXIT_FAILURE);
- }
-#endif
- dnsdist::initRandom();
- g_hashperturb = dnsdist::getRandomValue(0xffffffff);
-
- ComboAddress clientAddress = ComboAddress();
- g_cmdLine.config=SYSCONFDIR "/dnsdist.conf";
- struct option longopts[]={
- {"acl", required_argument, 0, 'a'},
- {"check-config", no_argument, 0, 1},
- {"client", no_argument, 0, 'c'},
- {"config", required_argument, 0, 'C'},
- {"disable-syslog", no_argument, 0, 2},
- {"execute", required_argument, 0, 'e'},
- {"gid", required_argument, 0, 'g'},
- {"help", no_argument, 0, 'h'},
- {"local", required_argument, 0, 'l'},
- {"log-timestamps", no_argument, 0, 4},
- {"setkey", required_argument, 0, 'k'},
- {"supervised", no_argument, 0, 3},
- {"uid", required_argument, 0, 'u'},
- {"verbose", no_argument, 0, 'v'},
- {"version", no_argument, 0, 'V'},
- {0,0,0,0}
- };
- int longindex=0;
- string optstring;
- for(;;) {
- int c=getopt_long(argc, argv, "a:cC:e:g:hk:l:u:vV", longopts, &longindex);
- if(c==-1)
- break;
- switch(c) {
- case 1:
- g_cmdLine.checkConfig=true;
- break;
- case 2:
- g_syslog=false;
- break;
- case 3:
- g_cmdLine.beSupervised=true;
- break;
- case 4:
- g_logtimestamps=true;
- break;
- case 'C':
- g_cmdLine.config=optarg;
- break;
- case 'c':
- g_cmdLine.beClient=true;
- break;
- case 'e':
- g_cmdLine.command=optarg;
- break;
- case 'g':
- g_cmdLine.gid=optarg;
- break;
- case 'h':
- cout<<"dnsdist "<<VERSION<<endl;
- usage();
- cout<<"\n";
- exit(EXIT_SUCCESS);
- break;
- case 'a':
- optstring=optarg;
- g_ACL.modify([optstring](NetmaskGroup& nmg) { nmg.addMask(optstring); });
- break;
- case 'k':
-#ifdef HAVE_LIBSODIUM
- if (B64Decode(string(optarg), g_consoleKey) < 0) {
- cerr<<"Unable to decode key '"<<optarg<<"'."<<endl;
- exit(EXIT_FAILURE);
- }
-#else
- cerr<<"dnsdist has been built without libsodium, -k/--setkey is unsupported."<<endl;
- exit(EXIT_FAILURE);
-#endif
- break;
- case 'l':
- g_cmdLine.locals.push_back(boost::trim_copy(string(optarg)));
- break;
- case 'u':
- g_cmdLine.uid=optarg;
- break;
- case 'v':
- g_verbose=true;
- break;
- case 'V':
-#ifdef LUAJIT_VERSION
- cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<" ["<<LUAJIT_VERSION<<"])"<<endl;
-#else
- cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<")"<<endl;
-#endif
- cout<<"Enabled features: ";
-#ifdef HAVE_CDB
- cout<<"cdb ";
-#endif
-#ifdef HAVE_DNS_OVER_TLS
- cout<<"dns-over-tls(";
-#ifdef HAVE_GNUTLS
- cout<<"gnutls";
-#ifdef HAVE_LIBSSL
- cout<<" ";
-#endif
-#endif
-#ifdef HAVE_LIBSSL
- cout<<"openssl";
-#endif
- cout<<") ";
-#endif
-#ifdef HAVE_DNS_OVER_HTTPS
- cout<<"dns-over-https(DOH) ";
-#endif
-#ifdef HAVE_DNSCRYPT
- cout<<"dnscrypt ";
-#endif
-#ifdef HAVE_EBPF
- cout<<"ebpf ";
-#endif
-#ifdef HAVE_FSTRM
- cout<<"fstrm ";
-#endif
-#ifdef HAVE_IPCIPHER
- cout<<"ipcipher ";
-#endif
-#ifdef HAVE_LIBEDIT
- cout<<"libeditr ";
-#endif
-#ifdef HAVE_LIBSODIUM
- cout<<"libsodium ";
-#endif
-#ifdef HAVE_LMDB
- cout<<"lmdb ";
-#endif
-#ifdef HAVE_NGHTTP2
- cout<<"outgoing-dns-over-https(nghttp2) ";
-#endif
-#ifndef DISABLE_PROTOBUF
- cout<<"protobuf ";
-#endif
-#ifdef HAVE_RE2
- cout<<"re2 ";
-#endif
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
- cout<<"recvmmsg/sendmmsg ";
-#endif
-#endif /* DISABLE_RECVMMSG */
-#ifdef HAVE_NET_SNMP
- cout<<"snmp ";
-#endif
-#ifdef HAVE_SYSTEMD
- cout<<"systemd";
-#endif
- cout<<endl;
- exit(EXIT_SUCCESS);
- break;
- case '?':
- //getopt_long printed an error message.
- usage();
- exit(EXIT_FAILURE);
- break;
- }
- }
-
- argc -= optind;
- argv += optind;
- (void) argc;
-
- for (auto p = argv; *p; ++p) {
- if(g_cmdLine.beClient) {
- clientAddress = ComboAddress(*p, 5199);
- } else {
- g_cmdLine.remotes.push_back(*p);
- }
- }
-
- ServerPolicy leastOutstandingPol{"leastOutstanding", leastOutstanding, false};
-
- g_policy.setState(leastOutstandingPol);
- if (g_cmdLine.beClient || !g_cmdLine.command.empty()) {
- setupLua(*(g_lua.lock()), true, false, g_cmdLine.config);
- if (clientAddress != ComboAddress())
- g_serverControl = clientAddress;
- doClient(g_serverControl, g_cmdLine.command);
-#ifdef COVERAGE
- exit(EXIT_SUCCESS);
-#else
- _exit(EXIT_SUCCESS);
-#endif
- }
-
- auto acl = g_ACL.getCopy();
- if(acl.empty()) {
- for(auto& addr : {"127.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "169.254.0.0/16", "192.168.0.0/16", "172.16.0.0/12", "::1/128", "fc00::/7", "fe80::/10"})
- acl.addMask(addr);
- g_ACL.setState(acl);
- }
-
- auto consoleACL = g_consoleACL.getCopy();
- for (const auto& mask : { "127.0.0.1/8", "::1/128" }) {
- consoleACL.addMask(mask);
- }
- g_consoleACL.setState(consoleACL);
- registerBuiltInWebHandlers();
-
- if (g_cmdLine.checkConfig) {
- setupLua(*(g_lua.lock()), false, true, g_cmdLine.config);
- // No exception was thrown
- infolog("Configuration '%s' OK!", g_cmdLine.config);
-#ifdef COVERAGE
- cleanupLuaObjects();
- exit(EXIT_SUCCESS);
-#else
- _exit(EXIT_SUCCESS);
-#endif
- }
-
- infolog("dnsdist %s comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2", VERSION);
-
- dnsdist::g_asyncHolder = std::make_unique<dnsdist::AsynchronousHolder>();
-
- auto todo = setupLua(*(g_lua.lock()), false, false, g_cmdLine.config);
-
- auto localPools = g_pools.getCopy();
- {
- bool precompute = false;
- if (g_policy.getLocal()->getName() == "chashed") {
- precompute = true;
- } else {
- for (const auto& entry: localPools) {
- if (entry.second->policy != nullptr && entry.second->policy->getName() == "chashed") {
- precompute = true;
- break ;
- }
- }
- }
- if (precompute) {
- vinfolog("Pre-computing hashes for consistent hash load-balancing policy");
- // pre compute hashes
- auto backends = g_dstates.getLocal();
- for (auto& backend: *backends) {
- if (backend->d_config.d_weight < 100) {
- vinfolog("Warning, the backend '%s' has a very low weight (%d), which will not yield a good distribution of queries with the 'chashed' policy. Please consider raising it to at least '100'.", backend->getName(), backend->d_config.d_weight);
- }
-
- backend->hash();
- }
- }
- }
-
- if (!g_cmdLine.locals.empty()) {
- for (auto it = g_frontends.begin(); it != g_frontends.end(); ) {
- /* DoH, DoT and DNSCrypt frontends are separate */
- if ((*it)->dohFrontend == nullptr && (*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr) {
- it = g_frontends.erase(it);
- }
- else {
- ++it;
- }
- }
-
- for (const auto& loc : g_cmdLine.locals) {
- /* UDP */
- g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(ComboAddress(loc, 53), false, false, 0, "", {})));
- /* TCP */
- g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(ComboAddress(loc, 53), true, false, 0, "", {})));
- }
- }
-
- if (g_frontends.empty()) {
- /* UDP */
- g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(ComboAddress("127.0.0.1", 53), false, false, 0, "", {})));
- /* TCP */
- g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(ComboAddress("127.0.0.1", 53), true, false, 0, "", {})));
- }
-
- g_configurationDone = true;
-
- g_rings.init();
-
- for(auto& frontend : g_frontends) {
- setUpLocalBind(frontend);
-
- if (frontend->tcp == false) {
- ++udpBindsCount;
- }
- else {
- ++tcpBindsCount;
- }
- }
-
- vector<string> vec;
- std::string acls;
- g_ACL.getLocal()->toStringVector(&vec);
- for(const auto& s : vec) {
- if (!acls.empty())
- acls += ", ";
- acls += s;
- }
- infolog("ACL allowing queries from: %s", acls.c_str());
- vec.clear();
- acls.clear();
- g_consoleACL.getLocal()->toStringVector(&vec);
- for (const auto& entry : vec) {
- if (!acls.empty()) {
- acls += ", ";
- }
- acls += entry;
- }
- infolog("Console ACL allowing connections from: %s", acls.c_str());
-
-#ifdef HAVE_LIBSODIUM
- if (g_consoleEnabled && g_consoleKey.empty()) {
- warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
- }
-#endif
-
- uid_t newgid=getegid();
- gid_t newuid=geteuid();
-
- if (!g_cmdLine.gid.empty()) {
- newgid = strToGID(g_cmdLine.gid);
- }
-
- if (!g_cmdLine.uid.empty()) {
- newuid = strToUID(g_cmdLine.uid);
- }
-
- bool retainedCapabilities = true;
- if (!g_capabilitiesToRetain.empty() &&
- (getegid() != newgid || geteuid() != newuid)) {
- retainedCapabilities = keepCapabilitiesAfterSwitchingIDs();
- }
-
- if (getegid() != newgid) {
- if (running_in_service_mgr()) {
- errlog("--gid/-g set on command-line, but dnsdist was started as a systemd service. Use the 'Group' setting in the systemd unit file to set the group to run as");
- _exit(EXIT_FAILURE);
- }
- dropGroupPrivs(newgid);
- }
-
- if (geteuid() != newuid) {
- if (running_in_service_mgr()) {
- errlog("--uid/-u set on command-line, but dnsdist was started as a systemd service. Use the 'User' setting in the systemd unit file to set the user to run as");
- _exit(EXIT_FAILURE);
- }
- dropUserPrivs(newuid);
- }
-
- if (retainedCapabilities) {
- dropCapabilitiesAfterSwitchingIDs();
- }
-
- try {
- /* we might still have capabilities remaining,
- for example if we have been started as root
- without --uid or --gid (please don't do that)
- or as an unprivileged user with ambient
- capabilities like CAP_NET_BIND_SERVICE.
- */
- dropCapabilities(g_capabilitiesToRetain);
- }
- catch (const std::exception& e) {
- warnlog("%s", e.what());
- }
-
- /* this need to be done _after_ dropping privileges */
-#ifndef DISABLE_DELAY_PIPE
- g_delay = new DelayPipe<DelayedPacket>();
-#endif /* DISABLE_DELAY_PIPE */
-
- if (g_snmpAgent) {
- g_snmpAgent->run();
- }
-
- if (!g_maxTCPClientThreads) {
- g_maxTCPClientThreads = static_cast<size_t>(10);
- }
- else if (*g_maxTCPClientThreads == 0 && tcpBindsCount > 0) {
- warnlog("setMaxTCPClientThreads() has been set to 0 while we are accepting TCP connections, raising to 1");
- g_maxTCPClientThreads = 1;
- }
-
- /* we need to create the TCP worker threads before the
- acceptor ones, otherwise we might crash when processing
- the first TCP query */
-#ifndef USE_SINGLE_ACCEPTOR_THREAD
- g_tcpclientthreads = std::make_unique<TCPClientCollection>(*g_maxTCPClientThreads, std::vector<ClientState*>());
-#endif
-
- initDoHWorkers();
-
- for (auto& t : todo) {
- t();
- }
-
- localPools = g_pools.getCopy();
- /* create the default pool no matter what */
- createPoolIfNotExists(localPools, "");
- if (g_cmdLine.remotes.size()) {
- for (const auto& address : g_cmdLine.remotes) {
- DownstreamState::Config config;
- config.remote = ComboAddress(address, 53);
- auto ret = std::make_shared<DownstreamState>(std::move(config), nullptr, true);
- addServerToPool(localPools, "", ret);
- ret->start();
- g_dstates.modify([&ret](servers_t& servers) { servers.push_back(std::move(ret)); });
- }
- }
- g_pools.setState(localPools);
-
- if (g_dstates.getLocal()->empty()) {
- errlog("No downstream servers defined: all packets will get dropped");
- // you might define them later, but you need to know
- }
-
- checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount);
-
- {
- auto states = g_dstates.getCopy(); // it is a copy, but the internal shared_ptrs are the real deal
- auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states.size()));
- for (auto& dss : states) {
- if (dss->d_config.availability == DownstreamState::Availability::Auto || dss->d_config.availability == DownstreamState::Availability::Lazy) {
- if (dss->d_config.availability == DownstreamState::Availability::Auto) {
- dss->d_nextCheck = dss->d_config.checkInterval;
- }
-
- if (!queueHealthCheck(mplexer, dss, true)) {
- dss->setUpStatus(false);
- warnlog("Marking downstream %s as 'down'", dss->getNameWithAddr());
- }
- }
- }
- handleQueuedHealthChecks(*mplexer, true);
- }
-
- std::vector<ClientState*> tcpStates;
- std::vector<ClientState*> udpStates;
- for(auto& cs : g_frontends) {
- if (cs->dohFrontend != nullptr) {
-#ifdef HAVE_DNS_OVER_HTTPS
- std::thread t1(dohThread, cs.get());
- if (!cs->cpus.empty()) {
- mapThreadToCPUList(t1.native_handle(), cs->cpus);
- }
- t1.detach();
-#endif /* HAVE_DNS_OVER_HTTPS */
- continue;
- }
- if (cs->udpFD >= 0) {
-#ifdef USE_SINGLE_ACCEPTOR_THREAD
- udpStates.push_back(cs.get());
-#else /* USE_SINGLE_ACCEPTOR_THREAD */
- thread t1(udpClientThread, std::vector<ClientState*>{ cs.get() });
- if (!cs->cpus.empty()) {
- mapThreadToCPUList(t1.native_handle(), cs->cpus);
- }
- t1.detach();
-#endif /* USE_SINGLE_ACCEPTOR_THREAD */
- }
- else if (cs->tcpFD >= 0) {
-#ifdef USE_SINGLE_ACCEPTOR_THREAD
- tcpStates.push_back(cs.get());
-#else /* USE_SINGLE_ACCEPTOR_THREAD */
- thread t1(tcpAcceptorThread, std::vector<ClientState*>{cs.get() });
- if (!cs->cpus.empty()) {
- mapThreadToCPUList(t1.native_handle(), cs->cpus);
- }
- t1.detach();
-#endif /* USE_SINGLE_ACCEPTOR_THREAD */
- }
- }
-#ifdef USE_SINGLE_ACCEPTOR_THREAD
- if (!udpStates.empty()) {
- thread udp(udpClientThread, udpStates);
- udp.detach();
- }
- if (!tcpStates.empty()) {
- g_tcpclientthreads = std::make_unique<TCPClientCollection>(1, tcpStates);
- }
-#endif /* USE_SINGLE_ACCEPTOR_THREAD */
- dnsdist::ServiceDiscovery::run();
-
-#ifndef DISABLE_CARBON
- dnsdist::Carbon::run();
-#endif /* DISABLE_CARBON */
-
- thread stattid(maintThread);
- stattid.detach();
-
- thread healththread(healthChecksThread);
-
-#ifndef DISABLE_DYNBLOCKS
- thread dynBlockMaintThread(dynBlockMaintenanceThread);
- dynBlockMaintThread.detach();
-#endif /* DISABLE_DYNBLOCKS */
-
-#ifndef DISABLE_SECPOLL
- if (!g_secPollSuffix.empty()) {
- thread secpollthread(secPollThread);
- secpollthread.detach();
- }
-#endif /* DISABLE_SECPOLL */
-
- if(g_cmdLine.beSupervised) {
-#ifdef HAVE_SYSTEMD
- sd_notify(0, "READY=1");
-#endif
- healththread.join();
- }
- else {
- healththread.detach();
- doConsole();
- }
-#ifdef COVERAGE
- cleanupLuaObjects();
- exit(EXIT_SUCCESS);
-#else
- _exit(EXIT_SUCCESS);
-#endif
- }
- catch (const LuaContext::ExecutionErrorException& e) {
- try {
- errlog("Fatal Lua error: %s", e.what());
- std::rethrow_if_nested(e);
- } catch(const std::exception& ne) {
- errlog("Details: %s", ne.what());
- }
- catch (const PDNSException &ae)
- {
- errlog("Fatal pdns error: %s", ae.reason);
- }
-#ifdef COVERAGE
- exit(EXIT_FAILURE);
-#else
- _exit(EXIT_FAILURE);
-#endif
- }
- catch (const std::exception &e)
- {
- errlog("Fatal error: %s", e.what());
-#ifdef COVERAGE
- exit(EXIT_FAILURE);
-#else
- _exit(EXIT_FAILURE);
-#endif
- }
- catch (const PDNSException &ae)
- {
- errlog("Fatal pdns error: %s", ae.reason);
-#ifdef COVERAGE
- exit(EXIT_FAILURE);
-#else
- _exit(EXIT_FAILURE);
-#endif
- }
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-#include "config.h"
-#include "ext/luawrapper/include/LuaContext.hpp"
-
-#include <memory>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <time.h>
-#include <unistd.h>
-#include <unordered_map>
-
-#include <boost/variant.hpp>
-
-#include "circular_buffer.hh"
-#include "dnscrypt.hh"
-#include "dnsdist-cache.hh"
-#include "dnsdist-dynbpf.hh"
-#include "dnsdist-idstate.hh"
-#include "dnsdist-lbpolicies.hh"
-#include "dnsdist-protocols.hh"
-#include "dnsname.hh"
-#include "doh.hh"
-#include "ednsoptions.hh"
-#include "iputils.hh"
-#include "misc.hh"
-#include "mplexer.hh"
-#include "noinitvector.hh"
-#include "sholder.hh"
-#include "tcpiohandler.hh"
-#include "uuid-utils.hh"
-#include "proxy-protocol.hh"
-#include "stat_t.hh"
-
-uint64_t uptimeOfProcess(const std::string& str);
-
-extern uint16_t g_ECSSourcePrefixV4;
-extern uint16_t g_ECSSourcePrefixV6;
-extern bool g_ECSOverride;
-
-using QTag = std::unordered_map<string, string>;
-
-class IncomingTCPConnectionState;
-
-struct ClientState;
-
-struct DNSQuestion
-{
- DNSQuestion(InternalQueryState& ids_, PacketBuffer& data_):
- data(data_), ids(ids_), ecsPrefixLength(ids.origRemote.sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), ecsOverride(g_ECSOverride) {
- }
- DNSQuestion(const DNSQuestion&) = delete;
- DNSQuestion& operator=(const DNSQuestion&) = delete;
- DNSQuestion(DNSQuestion&&) = default;
- virtual ~DNSQuestion() = default;
-
- std::string getTrailingData() const;
- bool setTrailingData(const std::string&);
- const PacketBuffer& getData() const
- {
- return data;
- }
- PacketBuffer& getMutableData()
- {
- return data;
- }
-
- dnsheader* getHeader()
- {
- if (data.size() < sizeof(dnsheader)) {
- throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
- }
- return reinterpret_cast<dnsheader*>(&data.at(0));
- }
-
- const dnsheader* getHeader() const
- {
- if (data.size() < sizeof(dnsheader)) {
- throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
- }
- return reinterpret_cast<const dnsheader*>(&data.at(0));
- }
-
- bool hasRoomFor(size_t more) const
- {
- return data.size() <= getMaximumSize() && (getMaximumSize() - data.size()) >= more;
- }
-
- size_t getMaximumSize() const
- {
- if (overTCP()) {
- return std::numeric_limits<uint16_t>::max();
- }
- return 4096;
- }
-
- dnsdist::Protocol getProtocol() const
- {
- return ids.protocol;
- }
-
- bool overTCP() const
- {
- return !(ids.protocol == dnsdist::Protocol::DoUDP || ids.protocol == dnsdist::Protocol::DNSCryptUDP);
- }
-
- void setTag(std::string&& key, std::string&& value) {
- if (!ids.qTag) {
- ids.qTag = std::make_unique<QTag>();
- }
- ids.qTag->insert_or_assign(std::move(key), std::move(value));
- }
-
- void setTag(const std::string& key, const std::string& value) {
- if (!ids.qTag) {
- ids.qTag = std::make_unique<QTag>();
- }
- ids.qTag->insert_or_assign(key, value);
- }
-
- const struct timespec& getQueryRealTime() const
- {
- return ids.queryRealTime.d_start;
- }
-
- bool isAsynchronous() const
- {
- return asynchronous;
- }
-
- std::shared_ptr<IncomingTCPConnectionState> getIncomingTCPState() const
- {
- return d_incomingTCPState;
- }
-
- ClientState* getFrontend() const
- {
- return ids.cs;
- }
-
-protected:
- PacketBuffer& data;
-
-public:
- InternalQueryState& ids;
- std::unique_ptr<Netmask> ecs{nullptr};
- std::string sni; /* Server Name Indication, if any (DoT or DoH) */
- mutable std::unique_ptr<EDNSOptionViewMap> ednsOptions; /* this needs to be mutable because it is parsed just in time, when DNSQuestion is read-only */
- std::shared_ptr<IncomingTCPConnectionState> d_incomingTCPState{nullptr};
- std::unique_ptr<std::vector<ProxyProtocolValue>> proxyProtocolValues{nullptr};
- uint16_t ecsPrefixLength;
- uint8_t ednsRCode{0};
- bool ecsOverride;
- bool useECS{true};
- bool addXPF{true};
- bool asynchronous{false};
-};
-
-struct DownstreamState;
-
-struct DNSResponse : DNSQuestion
-{
- DNSResponse(InternalQueryState& ids_, PacketBuffer& data_, const std::shared_ptr<DownstreamState>& downstream):
- DNSQuestion(ids_, data_), d_downstream(downstream) { }
- DNSResponse(const DNSResponse&) = delete;
- DNSResponse& operator=(const DNSResponse&) = delete;
- DNSResponse(DNSResponse&&) = default;
-
- const std::shared_ptr<DownstreamState>& d_downstream;
-};
-
-/* so what could you do:
- drop,
- fake up nxdomain,
- provide actual answer,
- allow & and stop processing,
- continue processing,
- modify header: (servfail|refused|notimp), set TC=1,
- send to pool */
-
-class DNSAction
-{
-public:
- enum class Action : uint8_t { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, Truncate, ServFail, None, NoOp, NoRecurse, SpoofRaw, SpoofPacket };
- static std::string typeToString(const Action& action)
- {
- switch(action) {
- case Action::Drop:
- return "Drop";
- case Action::Nxdomain:
- return "Send NXDomain";
- case Action::Refused:
- return "Send Refused";
- case Action::Spoof:
- return "Spoof an answer";
- case Action::SpoofPacket:
- return "Spoof a raw answer from bytes";
- case Action::SpoofRaw:
- return "Spoof an answer from raw bytes";
- case Action::Allow:
- return "Allow";
- case Action::HeaderModify:
- return "Modify the header";
- case Action::Pool:
- return "Route to a pool";
- case Action::Delay:
- return "Delay";
- case Action::Truncate:
- return "Truncate over UDP";
- case Action::ServFail:
- return "Send ServFail";
- case Action::None:
- case Action::NoOp:
- return "Do nothing";
- case Action::NoRecurse:
- return "Set rd=0";
- }
-
- return "Unknown";
- }
-
- virtual Action operator()(DNSQuestion*, string* ruleresult) const =0;
- virtual ~DNSAction()
- {
- }
- virtual string toString() const = 0;
- virtual std::map<string, double> getStats() const
- {
- return {{}};
- }
- virtual void reload()
- {
- }
-};
-
-class DNSResponseAction
-{
-public:
- enum class Action : uint8_t { Allow, Delay, Drop, HeaderModify, ServFail, None };
- virtual Action operator()(DNSResponse*, string* ruleresult) const =0;
- virtual ~DNSResponseAction()
- {
- }
- virtual string toString() const = 0;
- virtual void reload()
- {
- }
-};
-
-struct DynBlock
-{
- DynBlock(): action(DNSAction::Action::None), warning(false)
- {
- until.tv_sec = 0;
- until.tv_nsec = 0;
- }
-
- DynBlock(const std::string& reason_, const struct timespec& until_, const DNSName& domain_, DNSAction::Action action_): reason(reason_), domain(domain_), until(until_), action(action_), warning(false)
- {
- }
-
- DynBlock(const DynBlock& rhs): reason(rhs.reason), domain(rhs.domain), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
- {
- blocks.store(rhs.blocks);
- }
-
- DynBlock(DynBlock&& rhs): reason(std::move(rhs.reason)), domain(std::move(rhs.domain)), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
- {
- blocks.store(rhs.blocks);
- }
-
- DynBlock& operator=(const DynBlock& rhs)
- {
- reason = rhs.reason;
- until = rhs.until;
- domain = rhs.domain;
- action = rhs.action;
- blocks.store(rhs.blocks);
- warning = rhs.warning;
- bpf = rhs.bpf;
- return *this;
- }
-
- DynBlock& operator=(DynBlock&& rhs)
- {
- reason = std::move(rhs.reason);
- until = rhs.until;
- domain = std::move(rhs.domain);
- action = rhs.action;
- blocks.store(rhs.blocks);
- warning = rhs.warning;
- bpf = rhs.bpf;
- return *this;
- }
-
- string reason;
- DNSName domain;
- struct timespec until;
- mutable std::atomic<unsigned int> blocks;
- DNSAction::Action action{DNSAction::Action::None};
- bool warning{false};
- bool bpf{false};
-};
-
-extern GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
-
-extern vector<pair<struct timeval, std::string> > g_confDelta;
-
-using pdns::stat_t;
-
-struct DNSDistStats
-{
- stat_t responses{0};
- stat_t servfailResponses{0};
- stat_t queries{0};
- stat_t frontendNXDomain{0};
- stat_t frontendServFail{0};
- stat_t frontendNoError{0};
- stat_t nonCompliantQueries{0};
- stat_t nonCompliantResponses{0};
- stat_t rdQueries{0};
- stat_t emptyQueries{0};
- stat_t aclDrops{0};
- stat_t dynBlocked{0};
- stat_t ruleDrop{0};
- stat_t ruleNXDomain{0};
- stat_t ruleRefused{0};
- stat_t ruleServFail{0};
- stat_t ruleTruncated{0};
- stat_t selfAnswered{0};
- stat_t downstreamTimeouts{0};
- stat_t downstreamSendErrors{0};
- stat_t truncFail{0};
- stat_t noPolicy{0};
- stat_t cacheHits{0};
- stat_t cacheMisses{0};
- stat_t latency0_1{0}, latency1_10{0}, latency10_50{0}, latency50_100{0}, latency100_1000{0}, latencySlow{0}, latencySum{0}, latencyCount{0};
- stat_t securityStatus{0};
- stat_t dohQueryPipeFull{0};
- stat_t dohResponsePipeFull{0};
- stat_t outgoingDoHQueryPipeFull{0};
- stat_t proxyProtocolInvalid{0};
- stat_t tcpQueryPipeFull{0};
- stat_t tcpCrossProtocolQueryPipeFull{0};
- stat_t tcpCrossProtocolResponsePipeFull{0};
- double latencyAvg100{0}, latencyAvg1000{0}, latencyAvg10000{0}, latencyAvg1000000{0};
- double latencyTCPAvg100{0}, latencyTCPAvg1000{0}, latencyTCPAvg10000{0}, latencyTCPAvg1000000{0};
- double latencyDoTAvg100{0}, latencyDoTAvg1000{0}, latencyDoTAvg10000{0}, latencyDoTAvg1000000{0};
- double latencyDoHAvg100{0}, latencyDoHAvg1000{0}, latencyDoHAvg10000{0}, latencyDoHAvg1000000{0};
- typedef std::function<uint64_t(const std::string&)> statfunction_t;
- typedef boost::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t> entry_t;
-
- std::vector<std::pair<std::string, entry_t>> entries{
- {"responses", &responses},
- {"servfail-responses", &servfailResponses},
- {"queries", &queries},
- {"frontend-nxdomain", &frontendNXDomain},
- {"frontend-servfail", &frontendServFail},
- {"frontend-noerror", &frontendNoError},
- {"acl-drops", &aclDrops},
- {"rule-drop", &ruleDrop},
- {"rule-nxdomain", &ruleNXDomain},
- {"rule-refused", &ruleRefused},
- {"rule-servfail", &ruleServFail},
- {"rule-truncated", &ruleTruncated},
- {"self-answered", &selfAnswered},
- {"downstream-timeouts", &downstreamTimeouts},
- {"downstream-send-errors", &downstreamSendErrors},
- {"trunc-failures", &truncFail},
- {"no-policy", &noPolicy},
- {"latency0-1", &latency0_1},
- {"latency1-10", &latency1_10},
- {"latency10-50", &latency10_50},
- {"latency50-100", &latency50_100},
- {"latency100-1000", &latency100_1000},
- {"latency-slow", &latencySlow},
- {"latency-avg100", &latencyAvg100},
- {"latency-avg1000", &latencyAvg1000},
- {"latency-avg10000", &latencyAvg10000},
- {"latency-avg1000000", &latencyAvg1000000},
- {"latency-tcp-avg100", &latencyTCPAvg100},
- {"latency-tcp-avg1000", &latencyTCPAvg1000},
- {"latency-tcp-avg10000", &latencyTCPAvg10000},
- {"latency-tcp-avg1000000", &latencyTCPAvg1000000},
- {"latency-dot-avg100", &latencyDoTAvg100},
- {"latency-dot-avg1000", &latencyDoTAvg1000},
- {"latency-dot-avg10000", &latencyDoTAvg10000},
- {"latency-dot-avg1000000", &latencyDoTAvg1000000},
- {"latency-doh-avg100", &latencyDoHAvg100},
- {"latency-doh-avg1000", &latencyDoHAvg1000},
- {"latency-doh-avg10000", &latencyDoHAvg10000},
- {"latency-doh-avg1000000", &latencyDoHAvg1000000},
- {"uptime", uptimeOfProcess},
- {"real-memory-usage", getRealMemoryUsage},
- {"special-memory-usage", getSpecialMemoryUsage},
- {"udp-in-errors", std::bind(udpErrorStats, "udp-in-errors")},
- {"udp-noport-errors", std::bind(udpErrorStats, "udp-noport-errors")},
- {"udp-recvbuf-errors", std::bind(udpErrorStats, "udp-recvbuf-errors")},
- {"udp-sndbuf-errors", std::bind(udpErrorStats, "udp-sndbuf-errors")},
- {"udp-in-csum-errors", std::bind(udpErrorStats, "udp-in-csum-errors")},
- {"udp6-in-errors", std::bind(udp6ErrorStats, "udp6-in-errors")},
- {"udp6-recvbuf-errors", std::bind(udp6ErrorStats, "udp6-recvbuf-errors")},
- {"udp6-sndbuf-errors", std::bind(udp6ErrorStats, "udp6-sndbuf-errors")},
- {"udp6-noport-errors", std::bind(udp6ErrorStats, "udp6-noport-errors")},
- {"udp6-in-csum-errors", std::bind(udp6ErrorStats, "udp6-in-csum-errors")},
- {"tcp-listen-overflows", std::bind(tcpErrorStats, "ListenOverflows")},
- {"noncompliant-queries", &nonCompliantQueries},
- {"noncompliant-responses", &nonCompliantResponses},
- {"proxy-protocol-invalid", &proxyProtocolInvalid},
- {"rdqueries", &rdQueries},
- {"empty-queries", &emptyQueries},
- {"cache-hits", &cacheHits},
- {"cache-misses", &cacheMisses},
- {"cpu-iowait", getCPUIOWait},
- {"cpu-steal", getCPUSteal},
- {"cpu-sys-msec", getCPUTimeSystem},
- {"cpu-user-msec", getCPUTimeUser},
- {"fd-usage", getOpenFileDescriptors},
- {"dyn-blocked", &dynBlocked},
- {"dyn-block-nmg-size", [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }},
- {"security-status", &securityStatus},
- {"doh-query-pipe-full", &dohQueryPipeFull},
- {"doh-response-pipe-full", &dohResponsePipeFull},
- {"outgoing-doh-query-pipe-full", &outgoingDoHQueryPipeFull},
- {"tcp-query-pipe-full", &tcpQueryPipeFull},
- {"tcp-cross-protocol-query-pipe-full", &tcpCrossProtocolQueryPipeFull},
- {"tcp-cross-protocol-response-pipe-full", &tcpCrossProtocolResponsePipeFull},
- // Latency histogram
- {"latency-sum", &latencySum},
- {"latency-count", &latencyCount},
- };
- std::map<std::string, stat_t, std::less<>> customCounters;
- std::map<std::string, pdns::stat_t_trait<double>, std::less<>> customGauges;
-};
-
-extern struct DNSDistStats g_stats;
-
-class BasicQPSLimiter
-{
-public:
- BasicQPSLimiter()
- {
- }
-
- BasicQPSLimiter(unsigned int burst): d_tokens(burst)
- {
- d_prev.start();
- }
-
- virtual ~BasicQPSLimiter()
- {
- }
-
- bool check(unsigned int rate, unsigned int burst) const // this is not quite fair
- {
- if (checkOnly(rate, burst)) {
- addHit();
- return true;
- }
-
- return false;
- }
-
- bool checkOnly(unsigned int rate, unsigned int burst) const // this is not quite fair
- {
- auto delta = d_prev.udiffAndSet();
-
- if (delta > 0.0) { // time, frequently, does go backwards..
- d_tokens += 1.0 * rate * (delta/1000000.0);
- }
-
- if (d_tokens > burst) {
- d_tokens = burst;
- }
-
- bool ret = false;
- if (d_tokens >= 1.0) { // we need this because burst=1 is weird otherwise
- ret = true;
- }
-
- return ret;
- }
-
- virtual void addHit() const
- {
- --d_tokens;
- }
-
- bool seenSince(const struct timespec& cutOff) const
- {
- return cutOff < d_prev.d_start;
- }
-
-protected:
- mutable StopWatch d_prev;
- mutable double d_tokens{0.0};
-};
-
-class QPSLimiter : public BasicQPSLimiter
-{
-public:
- QPSLimiter(): BasicQPSLimiter()
- {
- }
-
- QPSLimiter(unsigned int rate, unsigned int burst): BasicQPSLimiter(burst), d_rate(rate), d_burst(burst), d_passthrough(false)
- {
- d_prev.start();
- }
-
- unsigned int getRate() const
- {
- return d_passthrough ? 0 : d_rate;
- }
-
- bool check() const // this is not quite fair
- {
- if (d_passthrough) {
- return true;
- }
-
- return BasicQPSLimiter::check(d_rate, d_burst);
- }
-
- bool checkOnly() const
- {
- if (d_passthrough) {
- return true;
- }
-
- return BasicQPSLimiter::checkOnly(d_rate, d_burst);
- }
-
- void addHit() const override
- {
- if (!d_passthrough) {
- --d_tokens;
- }
- }
-
-private:
- unsigned int d_rate{0};
- unsigned int d_burst{0};
- bool d_passthrough{true};
-};
-
-typedef std::unordered_map<string, unsigned int> QueryCountRecords;
-typedef std::function<std::tuple<bool, string>(const DNSQuestion* dq)> QueryCountFilter;
-struct QueryCount {
- QueryCount()
- {
- }
- ~QueryCount()
- {
- }
- SharedLockGuarded<QueryCountRecords> records;
- QueryCountFilter filter;
- bool enabled{false};
-};
-
-extern QueryCount g_qcount;
-
-struct ClientState
-{
- ClientState(const ComboAddress& local_, bool isTCP_, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_): cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort)
- {
- }
-
- stat_t queries{0};
- stat_t nonCompliantQueries{0};
- mutable stat_t responses{0};
- mutable stat_t tcpDiedReadingQuery{0};
- mutable stat_t tcpDiedSendingResponse{0};
- mutable stat_t tcpGaveUp{0};
- mutable stat_t tcpClientTimeouts{0};
- mutable stat_t tcpDownstreamTimeouts{0};
- /* current number of connections to this frontend */
- mutable stat_t tcpCurrentConnections{0};
- /* maximum number of concurrent connections to this frontend reached */
- mutable stat_t tcpMaxConcurrentConnections{0};
- stat_t tlsNewSessions{0}; // A new TLS session has been negotiated, no resumption
- stat_t tlsResumptions{0}; // A TLS session has been resumed, either via session id or via a TLS ticket
- stat_t tlsUnknownTicketKey{0}; // A TLS ticket has been presented but we don't have the associated key (might have expired)
- stat_t tlsInactiveTicketKey{0}; // A TLS ticket has been successfully resumed but the key is no longer active, we should issue a new one
- stat_t tls10queries{0}; // valid DNS queries received via TLSv1.0
- stat_t tls11queries{0}; // valid DNS queries received via TLSv1.1
- stat_t tls12queries{0}; // valid DNS queries received via TLSv1.2
- stat_t tls13queries{0}; // valid DNS queries received via TLSv1.3
- stat_t tlsUnknownqueries{0}; // valid DNS queries received via unknown TLS version
- pdns::stat_t_trait<double> tcpAvgQueriesPerConnection{0.0};
- /* in ms */
- pdns::stat_t_trait<double> tcpAvgConnectionDuration{0.0};
- std::set<int> cpus;
- std::string interface;
- ComboAddress local;
- std::vector<std::pair<ComboAddress, int>> d_additionalAddresses;
- std::shared_ptr<DNSCryptContext> dnscryptCtx{nullptr};
- std::shared_ptr<TLSFrontend> tlsFrontend{nullptr};
- std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
- std::shared_ptr<BPFFilter> d_filter{nullptr};
- size_t d_maxInFlightQueriesPerConn{1};
- size_t d_tcpConcurrentConnectionsLimit{0};
- int udpFD{-1};
- int tcpFD{-1};
- int tcpListenQueueSize{SOMAXCONN};
- int fastOpenQueueSize{0};
- bool muted{false};
- bool tcp;
- bool reuseport;
- bool ready{false};
-
- int getSocket() const
- {
- return udpFD != -1 ? udpFD : tcpFD;
- }
-
- bool isUDP() const
- {
- return udpFD != -1;
- }
-
- bool isTCP() const
- {
- return udpFD == -1;
- }
-
- bool isDoH() const
- {
- return dohFrontend != nullptr;
- }
-
- bool hasTLS() const
- {
- return tlsFrontend != nullptr || (dohFrontend != nullptr && dohFrontend->isHTTPS());
- }
-
- dnsdist::Protocol getProtocol() const
- {
- if (dnscryptCtx) {
- if (udpFD != -1) {
- return dnsdist::Protocol::DNSCryptUDP;
- }
- return dnsdist::Protocol::DNSCryptTCP;
- }
- if (isDoH()) {
- return dnsdist::Protocol::DoH;
- }
- else if (hasTLS()) {
- return dnsdist::Protocol::DoT;
- }
- else if (udpFD != -1) {
- return dnsdist::Protocol::DoUDP;
- }
- else {
- return dnsdist::Protocol::DoTCP;
- }
- }
-
- std::string getType() const
- {
- std::string result = udpFD != -1 ? "UDP" : "TCP";
-
- if (dohFrontend) {
- if (dohFrontend->isHTTPS()) {
- result += " (DNS over HTTPS)";
- }
- else {
- result += " (DNS over HTTP)";
- }
- }
- else if (tlsFrontend) {
- result += " (DNS over TLS)";
- }
- else if (dnscryptCtx) {
- result += " (DNSCrypt)";
- }
-
- return result;
- }
-
- void detachFilter(int socket)
- {
- if (d_filter) {
- d_filter->removeSocket(socket);
- d_filter = nullptr;
- }
- }
-
- void attachFilter(shared_ptr<BPFFilter> bpf, int socket)
- {
- detachFilter(socket);
-
- bpf->addSocket(socket);
- d_filter = bpf;
- }
-
- void detachFilter()
- {
- if (d_filter) {
- detachFilter(getSocket());
- for (const auto& [addr, socket] : d_additionalAddresses) {
- (void) addr;
- if (socket != -1) {
- detachFilter(socket);
- }
- }
-
- d_filter = nullptr;
- }
- }
-
- void attachFilter(shared_ptr<BPFFilter> bpf)
- {
- detachFilter();
-
- bpf->addSocket(getSocket());
- for (const auto& [addr, socket] : d_additionalAddresses) {
- (void) addr;
- if (socket != -1) {
- bpf->addSocket(socket);
- }
- }
- d_filter = bpf;
- }
-
- void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
- {
- tcpAvgQueriesPerConnection = (99.0 * tcpAvgQueriesPerConnection / 100.0) + (nbQueries / 100.0);
- tcpAvgConnectionDuration = (99.0 * tcpAvgConnectionDuration / 100.0) + (durationMs / 100.0);
- }
-};
-
-struct CrossProtocolQuery;
-
-struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
-{
- DownstreamState(const DownstreamState&) = delete;
- DownstreamState(DownstreamState&&) = delete;
- DownstreamState& operator=(const DownstreamState&) = delete;
- DownstreamState& operator=(DownstreamState&&) = delete;
-
- typedef std::function<std::tuple<DNSName, uint16_t, uint16_t>(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t;
- enum class Availability : uint8_t { Up, Down, Auto, Lazy };
- enum class LazyHealthCheckMode : uint8_t { TimeoutOnly, TimeoutOrServFail };
-
- struct Config
- {
- Config()
- {
- }
- Config(const ComboAddress& remote_): remote(remote_)
- {
- }
-
- TLSContextParameters d_tlsParams;
- set<string> pools;
- std::set<int> d_cpus;
- checkfunc_t checkFunction;
- std::optional<boost::uuids::uuid> id;
- DNSName checkName{"a.root-servers.net."};
- ComboAddress remote;
- ComboAddress sourceAddr;
- std::string sourceItfName;
- std::string d_tlsSubjectName;
- std::string d_dohPath;
- std::string name;
- std::string nameWithAddr;
- size_t d_numberOfSockets{1};
- size_t d_maxInFlightQueriesPerConn{1};
- size_t d_tcpConcurrentConnectionsLimit{0};
- int order{1};
- int d_weight{1};
- int tcpConnectTimeout{5};
- int tcpRecvTimeout{30};
- int tcpSendTimeout{30};
- int d_qpsLimit{0};
- unsigned int checkInterval{1};
- unsigned int sourceItf{0};
- QType checkType{QType::A};
- uint16_t checkClass{QClass::IN};
- uint16_t d_retries{5};
- uint16_t xpfRRCode{0};
- uint16_t checkTimeout{1000}; /* in milliseconds */
- uint16_t d_lazyHealthCheckSampleSize{100};
- uint16_t d_lazyHealthCheckMinSampleCount{1};
- uint16_t d_lazyHealthCheckFailedInterval{30};
- uint16_t d_lazyHealthCheckMaxBackOff{3600};
- uint8_t d_lazyHealthCheckThreshold{20};
- LazyHealthCheckMode d_lazyHealthCheckMode{LazyHealthCheckMode::TimeoutOrServFail};
- uint8_t maxCheckFailures{1};
- uint8_t minRiseSuccesses{1};
- Availability availability{Availability::Auto};
- bool d_tlsSubjectIsAddr{false};
- bool mustResolve{false};
- bool useECS{false};
- bool useProxyProtocol{false};
- bool setCD{false};
- bool disableZeroScope{false};
- bool tcpFastOpen{false};
- bool ipBindAddrNoPort{true};
- bool reconnectOnUp{false};
- bool d_tcpCheck{false};
- bool d_tcpOnly{false};
- bool d_addXForwardedHeaders{false}; // for DoH backends
- bool d_lazyHealthCheckUseExponentialBackOff{false};
- bool d_upgradeToLazyHealthChecks{false};
- };
-
- DownstreamState(DownstreamState::Config&& config, std::shared_ptr<TLSCtx> tlsCtx, bool connect);
- DownstreamState(const ComboAddress& remote): DownstreamState(DownstreamState::Config(remote), nullptr, false)
- {
- }
-
- ~DownstreamState();
-
- Config d_config;
- stat_t sendErrors{0};
- stat_t outstanding{0};
- stat_t reuseds{0};
- stat_t queries{0};
- stat_t responses{0};
- stat_t nonCompliantResponses{0};
- struct {
- stat_t sendErrors{0};
- stat_t reuseds{0};
- stat_t queries{0};
- } prev;
- stat_t tcpDiedSendingQuery{0};
- stat_t tcpDiedReadingResponse{0};
- stat_t tcpGaveUp{0};
- stat_t tcpReadTimeouts{0};
- stat_t tcpWriteTimeouts{0};
- stat_t tcpConnectTimeouts{0};
- /* current number of connections to this backend */
- stat_t tcpCurrentConnections{0};
- /* maximum number of concurrent connections to this backend reached */
- stat_t tcpMaxConcurrentConnections{0};
- /* number of times we had to enforce the maximum concurrent connections limit */
- stat_t tcpTooManyConcurrentConnections{0};
- stat_t tcpReusedConnections{0};
- stat_t tcpNewConnections{0};
- stat_t tlsResumptions{0};
- pdns::stat_t_trait<double> tcpAvgQueriesPerConnection{0.0};
- /* in ms */
- pdns::stat_t_trait<double> tcpAvgConnectionDuration{0.0};
- pdns::stat_t_trait<double> queryLoad{0.0};
- pdns::stat_t_trait<double> dropRate{0.0};
-
- SharedLockGuarded<std::vector<unsigned int>> hashes;
- LockGuarded<std::unique_ptr<FDMultiplexer>> mplexer{nullptr};
-private:
- LockGuarded<std::map<uint16_t, IDState>> d_idStatesMap;
- vector<IDState> idStates;
-
- struct LazyHealthCheckStats
- {
- boost::circular_buffer<bool> d_lastResults;
- time_t d_nextCheck{0};
- enum class LazyStatus: uint8_t { Healthy = 0, PotentialFailure, Failed };
- LazyStatus d_status{LazyStatus::Healthy};
- };
- LockGuarded<LazyHealthCheckStats> d_lazyHealthCheckStats;
-
-public:
- std::shared_ptr<TLSCtx> d_tlsCtx{nullptr};
- std::vector<int> sockets;
- StopWatch sw;
- QPSLimiter qps;
- std::atomic<uint64_t> idOffset{0};
- size_t socketsOffset{0};
- double latencyUsec{0.0};
- double latencyUsecTCP{0.0};
- unsigned int d_nextCheck{0};
- uint16_t currentCheckFailures{0};
- uint8_t consecutiveSuccessfulChecks{0};
- std::atomic<bool> hashesComputed{false};
- std::atomic<bool> connected{false};
- bool upStatus{false};
-
-private:
- void connectUDPSockets();
-
- std::thread tid;
- std::mutex connectLock;
- std::atomic_flag threadStarted;
- bool d_stopped{false};
-public:
-
- void start();
-
- bool isUp() const
- {
- if (d_config.availability == Availability::Down) {
- return false;
- }
- else if (d_config.availability == Availability::Up) {
- return true;
- }
- return upStatus;
- }
-
- void setUp() {
- d_config.availability = Availability::Up;
- }
-
- void setUpStatus(bool newStatus)
- {
- upStatus = newStatus;
- if (!upStatus) {
- latencyUsec = 0.0;
- latencyUsecTCP = 0.0;
- }
- }
- void setDown()
- {
- d_config.availability = Availability::Down;
- latencyUsec = 0.0;
- latencyUsecTCP = 0.0;
- }
- void setAuto() {
- d_config.availability = Availability::Auto;
- }
- void setLazyAuto() {
- d_config.availability = Availability::Lazy;
- d_lazyHealthCheckStats.lock()->d_lastResults.set_capacity(d_config.d_lazyHealthCheckSampleSize);
- }
- bool healthCheckRequired(std::optional<time_t> currentTime = std::nullopt);
-
- const string& getName() const {
- return d_config.name;
- }
- const string& getNameWithAddr() const {
- return d_config.nameWithAddr;
- }
- void setName(const std::string& newName)
- {
- d_config.name = newName;
- d_config.nameWithAddr = newName.empty() ? d_config.remote.toStringWithPort() : (d_config.name + " (" + d_config.remote.toStringWithPort()+ ")");
- }
-
- string getStatus() const
- {
- string status;
- if (d_config.availability == DownstreamState::Availability::Up) {
- status = "UP";
- }
- else if (d_config.availability == DownstreamState::Availability::Down) {
- status = "DOWN";
- }
- else {
- status = (upStatus ? "up" : "down");
- }
- return status;
- }
-
- bool reconnect();
- void hash();
- void setId(const boost::uuids::uuid& newId);
- void setWeight(int newWeight);
- void stop();
- bool isStopped() const
- {
- return d_stopped;
- }
- const boost::uuids::uuid& getID() const
- {
- return *d_config.id;
- }
-
- void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
- {
- tcpAvgQueriesPerConnection = (99.0 * tcpAvgQueriesPerConnection / 100.0) + (nbQueries / 100.0);
- tcpAvgConnectionDuration = (99.0 * tcpAvgConnectionDuration / 100.0) + (durationMs / 100.0);
- }
-
- void updateTCPLatency(double udiff)
- {
- latencyUsecTCP = (127.0 * latencyUsecTCP / 128.0) + udiff / 128.0;
- }
-
- void incQueriesCount()
- {
- ++queries;
- qps.addHit();
- }
-
- void incCurrentConnectionsCount();
-
- bool doHealthcheckOverTCP() const
- {
- return d_config.d_tcpOnly || d_config.d_tcpCheck || d_tlsCtx != nullptr;
- }
-
- bool isTCPOnly() const
- {
- return d_config.d_tcpOnly || d_tlsCtx != nullptr;
- }
-
- bool isDoH() const
- {
- return !d_config.d_dohPath.empty();
- }
-
- bool passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq);
- int pickSocketForSending();
- void pickSocketsReadyForReceiving(std::vector<int>& ready);
- void handleUDPTimeouts();
- void reportTimeoutOrError();
- void reportResponse(uint8_t rcode);
- void submitHealthCheckResult(bool initial, bool newState);
- time_t getNextLazyHealthCheck();
- uint16_t saveState(InternalQueryState&&);
- void restoreState(uint16_t id, InternalQueryState&&);
- std::optional<InternalQueryState> getState(uint16_t id);
-
- dnsdist::Protocol getProtocol() const
- {
- if (isDoH()) {
- return dnsdist::Protocol::DoH;
- }
- if (d_tlsCtx != nullptr) {
- return dnsdist::Protocol::DoT;
- }
- if (isTCPOnly()) {
- return dnsdist::Protocol::DoTCP;
- }
- return dnsdist::Protocol::DoUDP;
- }
-
- double getRelevantLatencyUsec() const
- {
- if (isTCPOnly()) {
- return latencyUsecTCP;
- }
- return latencyUsec;
- }
-
- static int s_udpTimeout;
- static bool s_randomizeSockets;
- static bool s_randomizeIDs;
-private:
- void handleUDPTimeout(IDState& ids);
- void updateNextLazyHealthCheck(LazyHealthCheckStats& stats, bool checkScheduled, std::optional<time_t> currentTime = std::nullopt);
-};
-using servers_t = vector<std::shared_ptr<DownstreamState>>;
-
-void responderThread(std::shared_ptr<DownstreamState> state);
-extern LockGuarded<LuaContext> g_lua;
-extern std::string g_outputBuffer; // locking for this is ok, as locked by g_luamutex
-
-class DNSRule
-{
-public:
- virtual ~DNSRule ()
- {
- }
- virtual bool matches(const DNSQuestion* dq) const =0;
- virtual string toString() const = 0;
- mutable stat_t d_matches{0};
-};
-
-struct ServerPool
-{
- ServerPool(): d_servers(std::make_shared<const ServerPolicy::NumberedServerVector>())
- {
- }
-
- ~ServerPool()
- {
- }
-
- const std::shared_ptr<DNSDistPacketCache> getCache() const { return packetCache; };
-
- bool getECS() const
- {
- return d_useECS;
- }
-
- void setECS(bool useECS)
- {
- d_useECS = useECS;
- }
-
- std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
- std::shared_ptr<ServerPolicy> policy{nullptr};
-
- size_t poolLoad();
- size_t countServers(bool upOnly);
- const std::shared_ptr<const ServerPolicy::NumberedServerVector> getServers();
- void addServer(shared_ptr<DownstreamState>& server);
- void removeServer(shared_ptr<DownstreamState>& server);
-
-private:
- SharedLockGuarded<std::shared_ptr<const ServerPolicy::NumberedServerVector>> d_servers;
- bool d_useECS{false};
-};
-
-enum ednsHeaderFlags {
- EDNS_HEADER_FLAG_NONE = 0,
- EDNS_HEADER_FLAG_DO = 32768
-};
-
-struct DNSDistRuleAction
-{
- std::shared_ptr<DNSRule> d_rule;
- std::shared_ptr<DNSAction> d_action;
- std::string d_name;
- boost::uuids::uuid d_id;
- uint64_t d_creationOrder;
-};
-
-struct DNSDistResponseRuleAction
-{
- std::shared_ptr<DNSRule> d_rule;
- std::shared_ptr<DNSResponseAction> d_action;
- std::string d_name;
- boost::uuids::uuid d_id;
- uint64_t d_creationOrder;
-};
-
-extern GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
-extern DNSAction::Action g_dynBlockAction;
-
-extern GlobalStateHolder<ServerPolicy> g_policy;
-extern GlobalStateHolder<servers_t> g_dstates;
-extern GlobalStateHolder<pools_t> g_pools;
-extern GlobalStateHolder<vector<DNSDistRuleAction> > g_ruleactions;
-extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_respruleactions;
-extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cachehitrespruleactions;
-extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_selfansweredrespruleactions;
-extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cacheInsertedRespRuleActions;
-extern GlobalStateHolder<NetmaskGroup> g_ACL;
-
-extern ComboAddress g_serverControl; // not changed during runtime
-
-extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
-extern std::vector<shared_ptr<DOHFrontend>> g_dohlocals;
-extern std::vector<std::unique_ptr<ClientState>> g_frontends;
-extern bool g_truncateTC;
-extern bool g_fixupCase;
-extern int g_tcpRecvTimeout;
-extern int g_tcpSendTimeout;
-extern uint16_t g_maxOutstanding;
-extern std::atomic<bool> g_configurationDone;
-extern boost::optional<uint64_t> g_maxTCPClientThreads;
-extern uint64_t g_maxTCPQueuedConnections;
-extern size_t g_maxTCPQueriesPerConn;
-extern size_t g_maxTCPConnectionDuration;
-extern size_t g_tcpInternalPipeBufferSize;
-extern pdns::stat16_t g_cacheCleaningDelay;
-extern pdns::stat16_t g_cacheCleaningPercentage;
-extern uint32_t g_staleCacheEntriesTTL;
-extern bool g_apiReadWrite;
-extern std::string g_apiConfigDirectory;
-extern bool g_servFailOnNoPolicy;
-extern size_t g_udpVectorSize;
-extern bool g_allowEmptyResponse;
-extern uint32_t g_socketUDPSendBuffer;
-extern uint32_t g_socketUDPRecvBuffer;
-
-extern shared_ptr<BPFFilter> g_defaultBPFFilter;
-extern std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
-
-struct LocalHolders
-{
- LocalHolders(): acl(g_ACL.getLocal()), policy(g_policy.getLocal()), ruleactions(g_ruleactions.getLocal()), cacheHitRespRuleactions(g_cachehitrespruleactions.getLocal()), cacheInsertedRespRuleActions(g_cacheInsertedRespRuleActions.getLocal()), selfAnsweredRespRuleactions(g_selfansweredrespruleactions.getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
- {
- }
-
- LocalStateHolder<NetmaskGroup> acl;
- LocalStateHolder<ServerPolicy> policy;
- LocalStateHolder<vector<DNSDistRuleAction> > ruleactions;
- LocalStateHolder<vector<DNSDistResponseRuleAction> > cacheHitRespRuleactions;
- LocalStateHolder<vector<DNSDistResponseRuleAction> > cacheInsertedRespRuleActions;
- LocalStateHolder<vector<DNSDistResponseRuleAction> > selfAnsweredRespRuleactions;
- LocalStateHolder<servers_t> servers;
- LocalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange> > dynNMGBlock;
- LocalStateHolder<SuffixMatchTree<DynBlock> > dynSMTBlock;
- LocalStateHolder<pools_t> pools;
-};
-
-void tcpAcceptorThread(std::vector<ClientState*> states);
-
-#ifdef HAVE_DNS_OVER_HTTPS
-void dohThread(ClientState* cs);
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects
-void setLuaSideEffect(); // set to report a side effect, cancelling all _no_ side effect calls
-bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect
-void resetLuaSideEffect(); // reset to indeterminate state
-
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote, unsigned int& qnameWireLength);
-
-bool checkQueryHeaders(const struct dnsheader* dh, ClientState& cs);
-
-extern std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
-int handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response);
-bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp);
-
-#include "dnsdist-snmp.hh"
-
-extern bool g_snmpEnabled;
-extern bool g_snmpTrapsEnabled;
-extern DNSDistSNMPAgent* g_snmpAgent;
-extern bool g_addEDNSToSelfGeneratedResponses;
-
-extern std::set<std::string> g_capabilitiesToRetain;
-static const uint16_t s_udpIncomingBufferSize{1500}; // don't accept UDP queries larger than this value
-static const size_t s_maxPacketCacheEntrySize{4096}; // don't cache responses larger than this value
-
-enum class ProcessQueryResult : uint8_t { Drop, SendAnswer, PassToBackend, Asynchronous };
-ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
-ProcessQueryResult processQueryAfterRules(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
-bool processResponse(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& respRuleActions, const std::vector<DNSDistResponseRuleAction>& insertedRespRuleActions, DNSResponse& dr, bool muted);
-bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop);
-bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted);
-
-bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& ds, uint16_t queryID, DNSQuestion& dq, PacketBuffer& query, ComboAddress& dest);
-
-ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ss, const int sd, const PacketBuffer& request, bool healthCheck = false);
-bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote);
-void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend);
-void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend);
/missing
/testrunner
/dnsdist
+/fuzz_target_dnsdistcache
/*.pb.cc
/*.pb.h
/dnsdist.service
MAX-ACCESS read-only
STATUS current
DESCRIPTION
- "Number of queries answered in less than 1 ms"
+ "Number of UDP queries answered in less than 1 ms"
::= { stats 14 }
latency110 OBJECT-TYPE
MAX-ACCESS read-only
STATUS current
DESCRIPTION
- "Number of queries answered in 1-10 ms"
+ "Number of UDP queries answered in 1-10 ms"
::= { stats 15 }
latency1050 OBJECT-TYPE
MAX-ACCESS read-only
STATUS current
DESCRIPTION
- "Number of queries answered in 10-50 ms"
+ "Number of UDP queries answered in 10-50 ms"
::= { stats 16 }
latency50100 OBJECT-TYPE
MAX-ACCESS read-only
STATUS current
DESCRIPTION
- "Number of queries answered in 50-100 ms"
+ "Number of UDP queries answered in 50-100 ms"
::= { stats 17 }
latency1001000 OBJECT-TYPE
MAX-ACCESS read-only
STATUS current
DESCRIPTION
- "Number of queries answered in 100-1000 ms"
+ "Number of UDP queries answered in 100-1000 ms"
::= { stats 18 }
latencySlow OBJECT-TYPE
MAX-ACCESS read-only
STATUS current
DESCRIPTION
- "Number of queries answered in more than 1s"
+ "Number of UDP queries answered in more than 1s"
::= { stats 19 }
latencyAVG100 OBJECT-TYPE
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS=ext/ipcrypt \
+SUBDIRS=ext/arc4random \
+ ext/ipcrypt \
ext/yahttp
CLEANFILES = \
$(AM_V_GEN)$(srcdir)/incfiles $(srcdir) > $@.tmp
@mv $@.tmp $@
-dnsdist-lua-ffi-interface.inc: dnsdist-lua-ffi-interface.h
+dnsdist-lua-ffi-interface.inc: dnsdist-lua-ffi-interface.h dnsdist-lua-inspection-ffi.h
$(AM_V_GEN)echo 'R"FFIContent(' > $@
- @cat $< >> $@
+ @cat $^ >> $@
@echo ')FFIContent"' >> $@
SRC_JS_FILES := $(wildcard src_js/*.js)
MIN_JS_FILES := $(patsubst src_js/%.js,html/js/%.min.js,$(SRC_JS_FILES))
AM_CPPFLAGS += $(LIBSSL_CFLAGS)
endif
+if HAVE_GNUTLS
+AM_CPPFLAGS += $(GNUTLS_CFLAGS)
+endif
+
if HAVE_LIBH2OEVLOOP
AM_CPPFLAGS += $(LIBH2OEVLOOP_CFLAGS)
endif
kqueuemplexer.cc \
portsmplexer.cc \
cdb.cc cdb.hh \
+ standalone_fuzz_target_runner.cc \
ext/lmdb-safe/lmdb-safe.cc ext/lmdb-safe/lmdb-safe.hh \
ext/protozero/include/* \
builder-support/gen-version
if UNIT_TESTS
noinst_PROGRAMS = testrunner
-TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message SRCDIR='$(srcdir)'
+TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message BOOST_TEST_RANDOM=1 SRCDIR='$(srcdir)'
TESTS=testrunner
else
check-local:
burtle.hh \
cachecleaner.hh \
capabilities.cc capabilities.hh \
+ channel.cc channel.hh \
circular_buffer.hh \
connection-management.hh \
+ coverage.cc coverage.hh \
credentials.cc credentials.hh \
dns.cc dns.hh \
dns_random.hh \
dnscrypt.cc dnscrypt.hh \
dnsdist-async.cc dnsdist-async.hh \
dnsdist-backend.cc \
+ dnsdist-backoff.hh \
dnsdist-cache.cc dnsdist-cache.hh \
dnsdist-carbon.cc dnsdist-carbon.hh \
dnsdist-concurrent-connections.hh \
dnsdist-console.cc dnsdist-console.hh \
+ dnsdist-crypto.cc dnsdist-crypto.hh \
dnsdist-discovery.cc dnsdist-discovery.hh \
dnsdist-dnscrypt.cc \
dnsdist-dnsparser.cc dnsdist-dnsparser.hh \
+ dnsdist-doh-common.cc dnsdist-doh-common.hh \
dnsdist-downstream-connection.hh \
dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
dnsdist-ecs.cc dnsdist-ecs.hh \
+ dnsdist-edns.cc dnsdist-edns.hh \
dnsdist-healthchecks.cc dnsdist-healthchecks.hh \
dnsdist-idstate.hh \
dnsdist-internal-queries.cc dnsdist-internal-queries.hh \
dnsdist-lua-bindings.cc \
dnsdist-lua-ffi-interface.h dnsdist-lua-ffi-interface.inc \
dnsdist-lua-ffi.cc dnsdist-lua-ffi.hh \
- dnsdist-lua-inspection-ffi.cc dnsdist-lua-inspection-ffi.hh \
+ dnsdist-lua-hooks.cc dnsdist-lua-hooks.hh \
+ dnsdist-lua-inspection-ffi.cc dnsdist-lua-inspection-ffi.h \
dnsdist-lua-inspection.cc \
dnsdist-lua-network.cc dnsdist-lua-network.hh \
dnsdist-lua-rules.cc \
dnsdist-lua-web.cc \
dnsdist-lua.cc dnsdist-lua.hh \
dnsdist-mac-address.cc dnsdist-mac-address.hh \
- dnsdist-nghttp2.cc dnsdist-nghttp2.hh \
+ dnsdist-metrics.cc dnsdist-metrics.hh \
+ dnsdist-nghttp2-in.hh \
+ dnsdist-nghttp2.hh \
dnsdist-prometheus.hh \
dnsdist-protobuf.cc dnsdist-protobuf.hh \
dnsdist-protocols.cc dnsdist-protocols.hh \
dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \
dnsdist-random.cc dnsdist-random.hh \
+ dnsdist-resolver.cc dnsdist-resolver.hh \
dnsdist-rings.cc dnsdist-rings.hh \
+ dnsdist-rule-chains.cc dnsdist-rule-chains.hh \
dnsdist-rules.cc dnsdist-rules.hh \
dnsdist-secpoll.cc dnsdist-secpoll.hh \
dnsdist-session-cache.cc dnsdist-session-cache.hh \
dnsdist-tcp.cc dnsdist-tcp.hh \
dnsdist-web.cc dnsdist-web.hh \
dnsdist-xpf.cc dnsdist-xpf.hh \
+ dnsdist-xsk.cc dnsdist-xsk.hh \
dnsdist.cc dnsdist.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.hh dnsparser.cc \
dnstap.cc dnstap.hh \
dnswriter.cc dnswriter.hh \
- doh.hh doh.cc \
- dolog.hh \
+ doh.hh \
+ doh3.hh \
+ dolog.cc dolog.hh \
+ doq-common.hh \
+ doq.hh \
ednscookies.cc ednscookies.hh \
+ ednsextendederror.cc ednsextendederror.hh \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc ednssubnet.hh \
ext/json11/json11.cpp \
remote_logger.cc remote_logger.hh \
sholder.hh \
snmp-agent.cc snmp-agent.hh \
- sodcrypto.cc sodcrypto.hh \
sstuff.hh \
stat_t.hh \
statnode.cc statnode.hh \
tcpiohandler.cc tcpiohandler.hh \
threadname.hh threadname.cc \
uuid-utils.hh uuid-utils.cc \
- xpf.cc xpf.hh
+ views.hh \
+ xpf.cc xpf.hh \
+ xsk.cc xsk.hh
testrunner_SOURCES = \
base64.hh \
bpf-filter.cc bpf-filter.hh \
cachecleaner.hh \
+ channel.cc channel.hh \
circular_buffer.hh \
connection-management.hh \
credentials.cc credentials.hh \
dnscrypt.cc dnscrypt.hh \
dnsdist-async.cc dnsdist-async.hh \
dnsdist-backend.cc \
+ dnsdist-backoff.hh \
dnsdist-cache.cc dnsdist-cache.hh \
dnsdist-concurrent-connections.hh \
+ dnsdist-crypto.cc dnsdist-crypto.hh \
dnsdist-dnsparser.cc dnsdist-dnsparser.hh \
+ dnsdist-doh-common.cc dnsdist-doh-common.hh \
dnsdist-downstream-connection.hh \
dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
dnsdist-ecs.cc dnsdist-ecs.hh \
+ dnsdist-edns.cc dnsdist-edns.hh \
dnsdist-idstate.hh \
dnsdist-kvs.cc dnsdist-kvs.hh \
dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
dnsdist-lua-network.cc dnsdist-lua-network.hh \
dnsdist-lua-vars.cc \
dnsdist-mac-address.cc dnsdist-mac-address.hh \
- dnsdist-nghttp2.cc dnsdist-nghttp2.hh \
+ dnsdist-metrics.cc dnsdist-metrics.hh \
+ dnsdist-nghttp2-in.hh \
+ dnsdist-nghttp2.hh \
dnsdist-protocols.cc dnsdist-protocols.hh \
dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \
dnsdist-random.cc dnsdist-random.hh \
+ dnsdist-resolver.cc dnsdist-resolver.hh \
dnsdist-rings.cc dnsdist-rings.hh \
+ dnsdist-rule-chains.cc dnsdist-rule-chains.hh \
dnsdist-rules.cc dnsdist-rules.hh \
dnsdist-session-cache.cc dnsdist-session-cache.hh \
dnsdist-svc.cc dnsdist-svc.hh \
dnsdist-tcp-downstream.cc \
dnsdist-tcp.cc dnsdist-tcp.hh \
dnsdist-xpf.cc dnsdist-xpf.hh \
+ dnsdist-xsk.cc dnsdist-xsk.hh \
dnsdist.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.hh dnsparser.cc \
dnswriter.cc dnswriter.hh \
- dolog.hh \
+ dolog.cc dolog.hh \
ednscookies.cc ednscookies.hh \
+ ednsextendederror.cc ednsextendederror.hh \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc ednssubnet.hh \
ext/luawrapper/include/LuaContext.hpp \
proxy-protocol.cc proxy-protocol.hh \
qtype.cc qtype.hh \
sholder.hh \
- sodcrypto.cc \
sstuff.hh \
stat_t.hh \
statnode.cc statnode.hh \
svc-records.cc svc-records.hh \
test-base64_cc.cc \
+ test-channel.cc \
test-connectionmanagement_hh.cc \
test-credentials_cc.cc \
test-delaypipe_hh.cc \
test-dnsdist_cc.cc \
test-dnsdistasync.cc \
test-dnsdistbackend_cc.cc \
+ test-dnsdistbackoff.cc \
test-dnsdistdynblocks_hh.cc \
+ test-dnsdistedns.cc \
test-dnsdistkvs_cc.cc \
test-dnsdistlbpolicies_cc.cc \
test-dnsdistluanetwork.cc \
- test-dnsdistnghttp2_cc.cc \
+ test-dnsdistnghttp2_common.hh \
test-dnsdistpacketcache_cc.cc \
test-dnsdistrings_cc.cc \
test-dnsdistrules_cc.cc \
testrunner.cc \
threadname.hh threadname.cc \
uuid-utils.hh uuid-utils.cc \
- xpf.cc xpf.hh
+ xpf.cc xpf.hh \
+ xsk.cc xsk.hh
dnsdist_LDFLAGS = \
$(AM_LDFLAGS) \
$(SYSTEMD_LIBS) \
$(NET_SNMP_LIBS) \
$(LIBCAP_LIBS) \
- $(IPCRYPT_LIBS)
+ $(IPCRYPT_LIBS) \
+ $(ARC4RANDOM_LIBS)
testrunner_LDFLAGS = \
$(AM_LDFLAGS) \
$(LIBSODIUM_LIBS) \
$(LUA_LIBS) \
$(RT_LIBS) \
- $(LIBCAP_LIBS)
+ $(LIBCAP_LIBS) \
+ $(ARC4RANDOM_LIBS)
if HAVE_CDB
dnsdist_LDADD += $(CDB_LDFLAGS) $(CDB_LIBS)
dnsdist_LDADD += $(LIBSSL_LIBS)
endif
+if HAVE_XSK
+dnsdist_LDADD += -lbpf
+dnsdist_LDADD += -lxdp
+testrunner_LDADD += -lbpf
+testrunner_LDADD += -lxdp
+endif
+
if HAVE_LIBCRYPTO
dnsdist_LDADD += $(LIBCRYPTO_LDFLAGS) $(LIBCRYPTO_LIBS)
testrunner_LDADD += $(LIBCRYPTO_LDFLAGS) $(LIBCRYPTO_LIBS)
if HAVE_DNS_OVER_HTTPS
-if HAVE_LIBH2OEVLOOP
-dnsdist_LDADD += $(LIBH2OEVLOOP_LIBS)
+if HAVE_GNUTLS
+dnsdist_LDADD += -lgnutls
endif
+if HAVE_LIBH2OEVLOOP
+dnsdist_SOURCES += doh.cc
+dnsdist_LDADD += $(LIBH2OEVLOOP_LIBS)
endif
if HAVE_NGHTTP2
+dnsdist_SOURCES += dnsdist-nghttp2-in.cc
+dnsdist_SOURCES += dnsdist-nghttp2.cc
+testrunner_SOURCES += dnsdist-nghttp2-in.cc
+testrunner_SOURCES += dnsdist-nghttp2.cc
+testrunner_SOURCES += test-dnsdistnghttp2-in_cc.cc \
+ test-dnsdistnghttp2_cc.cc
dnsdist_LDADD += $(NGHTTP2_LDFLAGS) $(NGHTTP2_LIBS)
testrunner_LDADD += $(NGHTTP2_LDFLAGS) $(NGHTTP2_LIBS)
endif
+endif
+
+if HAVE_DNS_OVER_QUIC
+dnsdist_SOURCES += doq.cc
+endif
+
+if HAVE_DNS_OVER_HTTP3
+dnsdist_SOURCES += doh3.cc
+endif
+
+if HAVE_QUICHE
+AM_CPPFLAGS += $(QUICHE_CFLAGS)
+dnsdist_LDADD += $(QUICHE_LDFLAGS) $(QUICHE_LIBS)
+dnsdist_SOURCES += doq-common.cc
+endif
+
if !HAVE_LUA_HPP
BUILT_SOURCES += lua.hpp
nodist_dnsdist_SOURCES = lua.hpp
portsmplexer.cc
endif
+if FUZZ_TARGETS
+
+LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
+
+standalone_fuzz_target_runner.o: standalone_fuzz_target_runner.cc
+
+fuzz_targets_programs = \
+ fuzz_target_dnsdistcache
+
+if HAVE_XSK
+fuzz_targets_programs += \
+ fuzz_target_xsk
+endif
+
+fuzz_targets: $(ARC4RANDOM_LIBS) $(fuzz_targets_programs)
+
+bin_PROGRAMS += \
+ $(fuzz_targets_programs)
+
+fuzz_targets_libs = \
+ $(LIBCRYPTO_LIBS) \
+ $(ARC4RANDOM_LIBS) \
+ $(LIB_FUZZING_ENGINE)
+
+fuzz_targets_ldflags = \
+ $(AM_LDFLAGS) \
+ $(DYNLINKFLAGS) \
+ $(LIBCRYPTO_LDFLAGS) \
+ $(FUZZING_LDFLAGS)
+
+# we need the mockup runner to be built, but not linked if a real fuzzing engine is used
+fuzz_targets_deps = standalone_fuzz_target_runner.o
+
+fuzz_target_dnsdistcache_SOURCES = \
+ channel.hh channel.cc \
+ dns.cc dns.hh \
+ dnsdist-cache.cc dnsdist-cache.hh \
+ dnsdist-dnsparser.cc dnsdist-dnsparser.hh \
+ dnsdist-ecs.cc dnsdist-ecs.hh \
+ dnsdist-idstate.hh \
+ dnsdist-protocols.cc dnsdist-protocols.hh \
+ dnslabeltext.cc \
+ dnsname.cc dnsname.hh \
+ dnsparser.cc dnsparser.hh \
+ dnswriter.cc dnswriter.hh \
+ doh.hh \
+ ednsoptions.cc ednsoptions.hh \
+ ednssubnet.cc ednssubnet.hh \
+ fuzz_dnsdistcache.cc \
+ iputils.cc iputils.hh \
+ misc.cc misc.hh \
+ packetcache.hh \
+ qtype.cc qtype.hh \
+ svc-records.cc svc-records.hh
+
+fuzz_target_dnsdistcache_DEPENDENCIES = $(fuzz_targets_deps)
+fuzz_target_dnsdistcache_LDFLAGS = $(fuzz_targets_ldflags)
+fuzz_target_dnsdistcache_LDADD = $(fuzz_targets_libs)
+
+if HAVE_XSK
+fuzz_target_xsk_SOURCES = \
+ dnslabeltext.cc \
+ dnsname.cc dnsname.hh \
+ fuzz_xsk.cc \
+ gettime.cc gettime.hh \
+ iputils.cc iputils.hh \
+ misc.cc misc.hh \
+ xsk.cc xsk.hh
+fuzz_target_xsk_DEPENDENCIES = $(fuzz_targets_deps)
+fuzz_target_xsk_LDFLAGS = $(fuzz_targets_ldflags)
+fuzz_target_xsk_LDADD = $(fuzz_targets_libs) -lbpf -lxdp
+endif # HAVE_XSK
+
+endif # FUZZ_TARGETS
+
MANPAGES=dnsdist.1
dist_man_MANS=$(MANPAGES)
--- /dev/null
+../channel.cc
\ No newline at end of file
--- /dev/null
+../channel.hh
\ No newline at end of file
CXXFLAGS="-g -O3 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls -fvisibility=hidden $CXXFLAGS"
PDNS_WITH_LIBSODIUM
+PDNS_WITH_QUICHE
PDNS_CHECK_DNSTAP([auto])
PDNS_CHECK_RAGEL([dnslabeltext.cc], [www.dnsdist.org])
PDNS_WITH_LIBEDIT
BOOST_REQUIRE([1.42])
PDNS_ENABLE_UNIT_TESTS
+PDNS_ENABLE_FUZZ_TARGETS
PDNS_WITH_RE2
DNSDIST_ENABLE_DNSCRYPT
PDNS_WITH_EBPF
+PDNS_WITH_XSK
PDNS_WITH_NET_SNMP
PDNS_WITH_LIBCAP
dnl the *_r functions are in posix so we can use them unconditionally, but the ext/yahttp code is
dnl using the defines.
-AC_CHECK_FUNCS_ONCE([localtime_r gmtime_r getrandom])
+AC_CHECK_FUNCS_ONCE([localtime_r gmtime_r])
+AC_CHECK_FUNCS_ONCE([getrandom getentropy arc4random arc4random_uniform arc4random_buf])
AC_SUBST([YAHTTP_CFLAGS], ['-I$(top_srcdir)/ext/yahttp'])
AC_SUBST([YAHTTP_LIBS], ['$(top_builddir)/ext/yahttp/yahttp/libyahttp.la'])
AC_SUBST([IPCRYPT_CFLAGS], ['-I$(top_srcdir)/ext/ipcrypt'])
AC_SUBST([IPCRYPT_LIBS], ['$(top_builddir)/ext/ipcrypt/libipcrypt.la'])
+AC_SUBST([ARC4RANDOM_LIBS], ['$(top_builddir)/ext/arc4random/libarc4random.la'])
+
+AC_CHECK_HEADERS([sys/random.h])
PDNS_WITH_LUA([mandatory])
AS_IF([test "x$LUAPC" = "xluajit"], [
])
PDNS_CHECK_LUA_HPP
+AM_CONDITIONAL([HAVE_CDB], [false])
AM_CONDITIONAL([HAVE_GNUTLS], [false])
+AM_CONDITIONAL([HAVE_LIBH2OEVLOOP], [false])
AM_CONDITIONAL([HAVE_LIBSSL], [false])
AM_CONDITIONAL([HAVE_LMDB], [false])
-AM_CONDITIONAL([HAVE_CDB], [false])
+AM_CONDITIONAL([HAVE_NGHTTP2], [false])
PDNS_CHECK_LIBCRYPTO
PDNS_ENABLE_DNS_OVER_TLS
DNSDIST_ENABLE_DNS_OVER_HTTPS
+DNSDIST_ENABLE_DNS_OVER_QUIC
+DNSDIST_ENABLE_DNS_OVER_HTTP3
-AS_IF([test "x$enable_dns_over_tls" != "xno" -o "x$enable_dns_over_https" != "xno"], [
+AS_IF([test "x$enable_dns_over_tls" != "xno" -o "x$enable_dns_over_https" != "xno" -o "x$enable_dns_over_quic" != "xno" ], [
PDNS_WITH_LIBSSL
+ AS_IF([test "x$enable_dns_over_tls" != "xno" -o "x$enable_dns_over_https" != "xno"], [
+ PDNS_WITH_GNUTLS
+ ])
])
AS_IF([test "x$enable_dns_over_tls" != "xno"], [
- PDNS_WITH_GNUTLS
-
AS_IF([test "x$HAVE_GNUTLS" != "x1" -a "x$HAVE_LIBSSL" != "x1"], [
AC_MSG_ERROR([DNS over TLS support requested but neither GnuTLS nor OpenSSL are available])
])
])
-PDNS_CHECK_LIBH2OEVLOOP
AS_IF([test "x$enable_dns_over_https" != "xno"], [
- AS_IF([test "x$HAVE_LIBH2OEVLOOP" != "x1"], [
- AC_MSG_ERROR([DNS over HTTPS support requested but libh2o-evloop was not found])
+ PDNS_WITH_NGHTTP2
+ PDNS_WITH_LIBH2OEVLOOP
+
+ AS_IF([test "x$HAVE_LIBH2OEVLOOP" != "x1" -a "x$HAVE_NGHTTP2" != "x1" ], [
+ AC_MSG_ERROR([DNS over HTTPS support requested but neither libh2o-evloop nor nghttp2 was not found])
])
+ AS_IF([test "x$HAVE_GNUTLS" != "x1" -a "x$HAVE_LIBSSL" != "x1"], [
+ AC_MSG_ERROR([DNS over HTTPS support requested but neither GnuTLS nor OpenSSL are available])
+ ])
+])
+
+AS_IF([test "x$enable_dns_over_quic" != "xno"], [
+ AS_IF([test "x$HAVE_QUICHE" != "x1"], [
+ AC_MSG_ERROR([DNS over QUIC support requested but quiche was not found])
+ ])
AS_IF([test "x$HAVE_LIBSSL" != "x1"], [
- AC_MSG_ERROR([DNS over HTTPS support requested but OpenSSL was not found])
+ AC_MSG_ERROR([DNS over QUIC support requested but OpenSSL is not available])
])
])
-PDNS_WITH_NGHTTP2
+AS_IF([test "x$enable_dns_over_http3" != "xno"], [
+ AS_IF([test "x$HAVE_QUICHE" != "x1"], [
+ AC_MSG_ERROR([DNS over HTTP/3 support requested but quiche was not found])
+ ])
+])
DNSDIST_WITH_CDB
PDNS_CHECK_LMDB
PDNS_ENABLE_SANITIZERS
PDNS_ENABLE_LTO
+PDNS_ENABLE_COVERAGE
PDNS_CHECK_PYTHON_VENV
)
AC_CONFIG_FILES([Makefile
+ ext/arc4random/Makefile
ext/yahttp/Makefile
ext/yahttp/yahttp/Makefile
ext/ipcrypt/Makefile])
[AC_MSG_NOTICE([systemd: yes])],
[AC_MSG_NOTICE([systemd: no])]
)
+AS_IF([test x"$BPF_LIBS" != "x" -a x"$XDP_LIBS" != "x"],
+ [AC_MSG_NOTICE([AF_XDP/XSK: yes])],
+ [AC_MSG_NOTICE([AF_XDP/XSK: no])]
+)
AS_IF([test "x$HAVE_IPCIPHER" = "x1"],
[AC_MSG_NOTICE([ipcipher: yes])],
[AC_MSG_NOTICE([ipcipher: no])]
[AC_MSG_NOTICE([dnstap: yes])],
[AC_MSG_NOTICE([dnstap: no])]
)
+AS_IF([test "x$QUICHE_LIBS" != "x"],
+ [AC_MSG_NOTICE([quiche: yes])],
+ [AC_MSG_NOTICE([quiche: no])]
+)
AS_IF([test "x$RE2_LIBS" != "x"],
[AC_MSG_NOTICE([re2: yes])],
[AC_MSG_NOTICE([re2: no])]
[AC_MSG_NOTICE([DNS over HTTPS (DoH): yes])],
[AC_MSG_NOTICE([DNS over HTTPS (DoH): no])]
)
+AS_IF([test "x$enable_dns_over_quic" != "xno"],
+ [AC_MSG_NOTICE([DNS over QUIC (DoQ): yes])],
+ [AC_MSG_NOTICE([DNS over QUIC (DoQ): no])]
+)
+AS_IF([test "x$enable_dns_over_http3" != "xno"],
+ [AC_MSG_NOTICE([DNS over HTTP/3 (DoH3): yes])],
+ [AC_MSG_NOTICE([DNS over HTTP/3 (DoH3): no])]
+)
AS_IF([test "x$enable_dns_over_tls" != "xno"], [
AS_IF([test "x$GNUTLS_LIBS" != "x"],
[AC_MSG_NOTICE([GnuTLS: yes])],
[AC_MSG_NOTICE([OpenSSL: no])]
)]
)
+AS_IF([test "x$LIBH2OEVLOOP_LIBS" != "x"],
+ [AC_MSG_NOTICE([h2o-evloop: yes])],
+ [AC_MSG_NOTICE([h2o-evloop: no])]
+)
AS_IF([test "x$NGHTTP2_LIBS" != "x"],
[AC_MSG_NOTICE([nghttp2: yes])],
[AC_MSG_NOTICE([nghttp2: no])]
--- /dev/null
+../coverage.cc
\ No newline at end of file
--- /dev/null
+../coverage.hh
\ No newline at end of file
namespace dnsdist
{
-AsynchronousHolder::AsynchronousHolder(bool failOpen) :
- d_data(std::make_shared<Data>())
+AsynchronousHolder::Data::Data(bool failOpen) :
+ d_failOpen(failOpen)
{
- d_data->d_failOpen = failOpen;
-
- int fds[2] = {-1, -1};
- if (pipe(fds) < 0) {
- throw std::runtime_error("Error creating the AsynchronousHolder pipe: " + stringerror());
- }
-
- for (size_t idx = 0; idx < (sizeof(fds) / sizeof(*fds)); idx++) {
- if (!setNonBlocking(fds[idx])) {
- int err = errno;
- close(fds[0]);
- close(fds[1]);
- throw std::runtime_error("Error setting the AsynchronousHolder pipe non-blocking: " + stringerror(err));
- }
- }
-
- d_data->d_notifyPipe = FDWrapper(fds[1]);
- d_data->d_watchPipe = FDWrapper(fds[0]);
+ auto [notifier, waiter] = pdns::channel::createNotificationQueue(true);
+ // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer): how I am supposed to do that?
+ d_waiter = std::move(waiter);
+ // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer): how I am supposed to do that?
+ d_notifier = std::move(notifier);
+}
+AsynchronousHolder::AsynchronousHolder(bool failOpen) :
+ d_data(std::make_shared<Data>(failOpen))
+{
std::thread main([data = this->d_data] { mainThread(data); });
main.detach();
}
bool AsynchronousHolder::notify() const
{
- const char data = 0;
- bool failed = false;
- do {
- auto written = write(d_data->d_notifyPipe.getHandle(), &data, sizeof(data));
- if (written == 0) {
- break;
- }
- if (written > 0 && static_cast<size_t>(written) == sizeof(data)) {
- return true;
- }
- if (errno != EINTR) {
- failed = true;
- }
- } while (!failed);
-
- return false;
+ return d_data->d_notifier.notify();
}
-bool AsynchronousHolder::wait(const AsynchronousHolder::Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs)
+bool AsynchronousHolder::wait(AsynchronousHolder::Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs)
{
readyFDs.clear();
mplexer.getAvailableFDs(readyFDs, atMostMs);
- if (readyFDs.size() == 0) {
+ if (readyFDs.empty()) {
/* timeout */
return true;
}
- while (true) {
- /* we might have been notified several times, let's read
- as much as possible before returning */
- char dummy = 0;
- auto got = read(data.d_watchPipe.getHandle(), &dummy, sizeof(dummy));
- if (got == 0) {
- break;
- }
- if (got > 0 && static_cast<size_t>(got) != sizeof(dummy)) {
- continue;
- }
- if (got == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
- break;
- }
- }
-
+ data.d_waiter.clear();
return false;
}
notify();
}
+// NOLINTNEXTLINE(performance-unnecessary-value-param): this is a long-lived thread, and we want to make sure the reference count of the shared pointer has been increased
void AsynchronousHolder::mainThread(std::shared_ptr<Data> data)
{
setThreadName("dnsdist/async");
- struct timeval now;
+ struct timeval now
+ {
+ };
std::list<std::pair<uint16_t, std::unique_ptr<CrossProtocolQuery>>> expiredEvents;
auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(1));
- mplexer->addReadFD(data->d_watchPipe.getHandle(), [](int, FDMultiplexer::funcparam_t&) {});
+ mplexer->addReadFD(data->d_waiter.getDescriptor(), [](int, FDMultiplexer::funcparam_t&) {});
std::vector<int> readyFDs;
while (true) {
}
else {
auto remainingUsec = uSec(next - now);
- timeout = std::round(remainingUsec / 1000.0);
+ timeout = static_cast<int>(std::round(static_cast<double>(remainingUsec) / 1000.0));
if (timeout == 0 && remainingUsec > 0) {
/* if we have less than 1 ms, let's wait at least 1 ms */
timeout = 1;
vinfolog("Asynchronous query %d has expired at %d.%d, notifying the sender", queryID, now.tv_sec, now.tv_usec);
auto sender = query->getTCPQuerySender();
if (sender) {
- sender->notifyIOError(std::move(query->query.d_idstate), now);
+ TCPResponse tresponse(std::move(query->query));
+ sender->notifyIOError(now, std::move(tresponse));
}
}
else {
{
/* no need to notify, worst case the thread wakes up for nothing because this was the next TTD */
auto content = d_data->d_content.lock();
- auto it = content->find(std::tie(queryID, asyncID));
- if (it == content->end()) {
- struct timeval now;
+ auto contentIt = content->find(std::tie(queryID, asyncID));
+ if (contentIt == content->end()) {
+ struct timeval now
+ {
+ };
gettimeofday(&now, nullptr);
vinfolog("Asynchronous object %d not found at %d.%d", queryID, now.tv_sec, now.tv_usec);
return nullptr;
}
- auto result = std::move(it->d_query);
- content->erase(it);
+ auto result = std::move(contentIt->d_query);
+ content->erase(contentIt);
return result;
}
void AsynchronousHolder::pickupExpired(content_t& content, const struct timeval& now, std::list<std::pair<uint16_t, std::unique_ptr<CrossProtocolQuery>>>& events)
{
auto& idx = content.get<TTDTag>();
- for (auto it = idx.begin(); it != idx.end() && it->d_ttd < now;) {
- events.emplace_back(it->d_queryID, std::move(it->d_query));
- it = idx.erase(it);
+ for (auto contentIt = idx.begin(); contentIt != idx.end() && contentIt->d_ttd < now;) {
+ events.emplace_back(contentIt->d_queryID, std::move(contentIt->d_query));
+ contentIt = idx.erase(contentIt);
}
}
{
try {
auto& ids = response->query.d_idstate;
- DNSResponse dr = response->getDR();
+ DNSResponse dnsResponse = response->getDR();
LocalHolders holders;
- auto result = processResponseAfterRules(response->query.d_buffer, *holders.cacheInsertedRespRuleActions, dr, ids.cs->muted);
+ auto result = processResponseAfterRules(response->query.d_buffer, *holders.cacheInsertedRespRuleActions, dnsResponse, ids.cs->muted);
if (!result) {
/* easy */
return true;
auto sender = response->getTCPQuerySender();
if (sender) {
- struct timeval now;
+ struct timeval now
+ {
+ };
gettimeofday(&now, nullptr);
TCPResponse resp(std::move(response->query.d_buffer), std::move(response->query.d_idstate), nullptr, response->downstream);
return resumeResponse(std::move(query));
}
- auto& ids = query->query.d_idstate;
- DNSQuestion dq = query->getDQ();
+ DNSQuestion dnsQuestion = query->getDQ();
LocalHolders holders;
- auto result = processQueryAfterRules(dq, holders, query->downstream);
+ auto result = processQueryAfterRules(dnsQuestion, holders, query->downstream);
if (result == ProcessQueryResult::Drop) {
/* easy */
return true;
}
- else if (result == ProcessQueryResult::PassToBackend) {
+ if (result == ProcessQueryResult::PassToBackend) {
if (query->downstream == nullptr) {
return false;
}
#ifdef HAVE_DNS_OVER_HTTPS
- if (dq.ids.du != nullptr) {
- dq.ids.du->downstream = query->downstream;
+ if (dnsQuestion.ids.du != nullptr) {
+ dnsQuestion.ids.du->downstream = query->downstream;
}
#endif
- if (query->downstream->isTCPOnly() || !(dq.getProtocol().isUDP() || dq.getProtocol() == dnsdist::Protocol::DoH)) {
+ if (query->downstream->isTCPOnly() || !(dnsQuestion.getProtocol().isUDP() || dnsQuestion.getProtocol() == dnsdist::Protocol::DoH)) {
query->downstream->passCrossProtocolQuery(std::move(query));
return true;
}
- auto queryID = dq.getHeader()->id;
+ auto queryID = dnsQuestion.getHeader()->id;
/* at this point 'du', if it is not nullptr, is owned by the DoHCrossProtocolQuery
which will stop existing when we return, so we need to increment the reference count
*/
- return assignOutgoingUDPQueryToBackend(query->downstream, queryID, dq, query->query.d_buffer, ids.origDest);
+ return assignOutgoingUDPQueryToBackend(query->downstream, queryID, dnsQuestion, query->query.d_buffer);
}
- else if (result == ProcessQueryResult::SendAnswer) {
+ if (result == ProcessQueryResult::SendAnswer) {
auto sender = query->getTCPQuerySender();
if (!sender) {
return false;
}
- struct timeval now;
+ struct timeval now
+ {
+ };
gettimeofday(&now, nullptr);
TCPResponse response(std::move(query->query.d_buffer), std::move(query->query.d_idstate), nullptr, query->downstream);
return false;
}
}
- else if (result == ProcessQueryResult::Asynchronous) {
+ if (result == ProcessQueryResult::Asynchronous) {
/* nope */
errlog("processQueryAfterRules returned 'asynchronous' while trying to resume an already asynchronous query");
return false;
return false;
}
-bool suspendQuery(DNSQuestion& dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
+bool suspendQuery(DNSQuestion& dnsQuestion, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
{
if (!g_asyncHolder) {
return false;
}
- struct timeval now;
+ struct timeval now
+ {
+ };
gettimeofday(&now, nullptr);
struct timeval ttd = now;
ttd.tv_sec += timeoutMs / 1000;
- ttd.tv_usec += (timeoutMs % 1000) * 1000;
+ ttd.tv_usec += static_cast<decltype(ttd.tv_usec)>((timeoutMs % 1000) * 1000);
normalizeTV(ttd);
vinfolog("Suspending asynchronous query %d at %d.%d until %d.%d", queryID, now.tv_sec, now.tv_usec, ttd.tv_sec, ttd.tv_usec);
- auto query = getInternalQueryFromDQ(dq, false);
+ auto query = getInternalQueryFromDQ(dnsQuestion, false);
g_asyncHolder->push(asyncID, queryID, ttd, std::move(query));
return true;
}
-bool suspendResponse(DNSResponse& dr, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
+bool suspendResponse(DNSResponse& dnsResponse, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
{
if (!g_asyncHolder) {
return false;
}
- struct timeval now;
+ struct timeval now
+ {
+ };
gettimeofday(&now, nullptr);
struct timeval ttd = now;
ttd.tv_sec += timeoutMs / 1000;
- ttd.tv_usec += (timeoutMs % 1000) * 1000;
+ ttd.tv_usec += static_cast<decltype(ttd.tv_usec)>((timeoutMs % 1000) * 1000);
normalizeTV(ttd);
vinfolog("Suspending asynchronous response %d at %d.%d until %d.%d", queryID, now.tv_sec, now.tv_usec, ttd.tv_sec, ttd.tv_usec);
- auto query = getInternalQueryFromDQ(dr, true);
+ auto query = getInternalQueryFromDQ(dnsResponse, true);
query->d_isResponse = true;
- query->downstream = dr.d_downstream;
+ query->downstream = dnsResponse.d_downstream;
g_asyncHolder->push(asyncID, queryID, ttd, std::move(query));
return true;
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/key_extractors.hpp>
+#include "channel.hh"
#include "dnsdist-tcp.hh"
namespace dnsdist
struct Data
{
+ Data(bool failOpen);
+ Data(const Data&) = delete;
+ Data(Data&&) = delete;
+ Data& operator=(const Data&) = delete;
+ Data& operator=(Data&&) = delete;
+ ~Data() = default;
+
LockGuarded<content_t> d_content;
- FDWrapper d_notifyPipe;
- FDWrapper d_watchPipe;
+ pdns::channel::Notifier d_notifier;
+ pdns::channel::Waiter d_waiter;
bool d_failOpen{true};
bool d_done{false};
};
std::shared_ptr<Data> d_data{nullptr};
static void mainThread(std::shared_ptr<Data> data);
- static bool wait(const Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs);
+ static bool wait(Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs);
bool notify() const;
};
-bool suspendQuery(DNSQuestion& dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs);
-bool suspendResponse(DNSResponse& dr, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs);
+bool suspendQuery(DNSQuestion& dnsQuestion, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs);
+bool suspendResponse(DNSResponse& dnsResponse, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs);
bool queueQueryResumptionEvent(std::unique_ptr<CrossProtocolQuery>&& query);
bool resumeQuery(std::unique_ptr<CrossProtocolQuery>&& query);
void handleQueuedAsynchronousEvents();
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-
+#include "config.h"
#include "dnsdist.hh"
+#include "dnsdist-backoff.hh"
+#include "dnsdist-metrics.hh"
#include "dnsdist-nghttp2.hh"
#include "dnsdist-random.hh"
#include "dnsdist-rings.hh"
#include "dnsdist-tcp.hh"
+#include "dnsdist-xsk.hh"
#include "dolog.hh"
+#include "xsk.hh"
bool DownstreamState::passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq)
{
- if (d_config.d_dohPath.empty()) {
- return g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq));
- }
- else {
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ if (!d_config.d_dohPath.empty()) {
return g_dohClientThreads && g_dohClientThreads->passCrossProtocolQueryToThread(std::move(cpq));
}
+#endif
+ return g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq));
+}
+
+#ifdef HAVE_XSK
+void DownstreamState::addXSKDestination(int fd)
+{
+ auto socklen = d_config.remote.getSocklen();
+ ComboAddress local;
+ if (getsockname(fd, reinterpret_cast<sockaddr*>(&local), &socklen)) {
+ return;
+ }
+
+ {
+ auto addresses = d_socketSourceAddresses.write_lock();
+ addresses->push_back(local);
+ }
+ dnsdist::xsk::addDestinationAddress(local);
+ for (size_t idx = 0; idx < d_xskSockets.size(); idx++) {
+ d_xskSockets.at(idx)->addWorkerRoute(d_xskInfos.at(idx), local);
+ }
+}
+
+void DownstreamState::removeXSKDestination(int fd)
+{
+ auto socklen = d_config.remote.getSocklen();
+ ComboAddress local;
+ if (getsockname(fd, reinterpret_cast<sockaddr*>(&local), &socklen)) {
+ return;
+ }
+
+ dnsdist::xsk::removeDestinationAddress(local);
+ for (auto& xskSocket : d_xskSockets) {
+ xskSocket->removeWorkerRoute(local);
+ }
}
+#endif /* HAVE_XSK */
-bool DownstreamState::reconnect()
+bool DownstreamState::reconnect(bool initialAttempt)
{
std::unique_lock<std::mutex> tl(connectLock, std::try_to_lock);
if (!tl.owns_lock() || isStopped()) {
}
connected = false;
+#ifdef HAVE_XSK
+ if (!d_xskInfos.empty()) {
+ auto addresses = d_socketSourceAddresses.write_lock();
+ addresses->clear();
+ }
+#endif /* HAVE_XSK */
+
for (auto& fd : sockets) {
if (fd != -1) {
if (sockets.size() > 1) {
(*mplexer.lock())->removeReadFD(fd);
}
+#ifdef HAVE_XSK
+ if (!d_xskInfos.empty()) {
+ removeXSKDestination(fd);
+ }
+#endif /* HAVE_XSK */
/* shutdown() is needed to wake up recv() in the responderThread */
shutdown(fd, SHUT_RDWR);
close(fd);
#endif
if (!IsAnyAddress(d_config.sourceAddr)) {
- SSetsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 1);
#ifdef IP_BIND_ADDRESS_NO_PORT
if (d_config.ipBindAddrNoPort) {
SSetsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
if (sockets.size() > 1) {
(*mplexer.lock())->addReadFD(fd, [](int, boost::any) {});
}
+#ifdef HAVE_XSK
+ if (!d_xskInfos.empty()) {
+ addXSKDestination(fd);
+ }
+#endif /* HAVE_XSK */
connected = true;
}
catch (const std::runtime_error& error) {
- infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what());
+ if (initialAttempt || g_verbose) {
+ infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what());
+ }
connected = false;
break;
}
/* if at least one (re-)connection failed, close all sockets */
if (!connected) {
+#ifdef HAVE_XSK
+ if (!d_xskInfos.empty()) {
+ auto addresses = d_socketSourceAddresses.write_lock();
+ addresses->clear();
+ }
+#endif /* HAVE_XSK */
for (auto& fd : sockets) {
if (fd != -1) {
+#ifdef HAVE_XSK
+ if (!d_xskInfos.empty()) {
+ removeXSKDestination(fd);
+ }
+#endif /* HAVE_XSK */
if (sockets.size() > 1) {
try {
(*mplexer.lock())->removeReadFD(fd);
}
}
+ if (connected) {
+ tl.unlock();
+ d_connectedWait.notify_all();
+ if (!initialAttempt) {
+ /* we need to be careful not to start this
+ thread too soon, as the creation should only
+ happen after the configuration has been parsed */
+ start();
+ }
+ }
+
return connected;
}
+void DownstreamState::waitUntilConnected()
+{
+ if (d_stopped) {
+ return;
+ }
+ if (connected) {
+ return;
+ }
+ {
+ std::unique_lock<std::mutex> lock(connectLock);
+ d_connectedWait.wait(lock, [this]{
+ return connected.load();
+ });
+ }
+}
+
void DownstreamState::stop()
{
if (d_stopped) {
void DownstreamState::start()
{
if (connected && !threadStarted.test_and_set()) {
- tid = std::thread(responderThread, shared_from_this());
+#ifdef HAVE_XSK
+ for (auto& xskInfo : d_xskInfos) {
+ auto xskResponderThread = std::thread(dnsdist::xsk::XskResponderThread, shared_from_this(), xskInfo);
+ if (!d_config.d_cpus.empty()) {
+ mapThreadToCPUList(xskResponderThread.native_handle(), d_config.d_cpus);
+ }
+ xskResponderThread.detach();
+ }
+#endif /* HAVE_XSK */
+ auto tid = std::thread(responderThread, shared_from_this());
if (!d_config.d_cpus.empty()) {
mapThreadToCPUList(tid.native_handle(), d_config.d_cpus);
}
-
tid.detach();
}
}
fd = -1;
}
- reconnect();
+ reconnect(true);
}
DownstreamState::~DownstreamState()
{
ids.age = 0;
ids.inUse = false;
- handleDOHTimeout(std::move(ids.internal.du));
+ DOHUnitInterface::handleTimeout(std::move(ids.internal.du));
++reuseds;
--outstanding;
- ++g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
+ ++dnsdist::metrics::g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
vinfolog("Had a downstream timeout from %s (%s) for query for %s|%s from %s",
d_config.remote.toStringWithPort(), getName(),
ids.internal.qname.toLogString(), QType(ids.internal.qtype).toString(), ids.internal.origRemote.toStringWithPort());
auto oldDU = std::move(it->second.internal.du);
++reuseds;
- ++g_stats.downstreamTimeouts;
- handleDOHTimeout(std::move(oldDU));
+ ++dnsdist::metrics::g_stats.downstreamTimeouts;
+ DOHUnitInterface::handleTimeout(std::move(oldDU));
}
else {
++outstanding;
to handle it because it's about to be overwritten. */
auto oldDU = std::move(ids.internal.du);
++reuseds;
- ++g_stats.downstreamTimeouts;
- handleDOHTimeout(std::move(oldDU));
+ ++dnsdist::metrics::g_stats.downstreamTimeouts;
+ DOHUnitInterface::handleTimeout(std::move(oldDU));
}
else {
++outstanding;
if (!inserted) {
/* already used */
++reuseds;
- ++g_stats.downstreamTimeouts;
- handleDOHTimeout(std::move(state.du));
+ ++dnsdist::metrics::g_stats.downstreamTimeouts;
+ DOHUnitInterface::handleTimeout(std::move(state.du));
}
else {
it->second.internal = std::move(state);
if (!guard) {
/* already used */
++reuseds;
- ++g_stats.downstreamTimeouts;
- handleDOHTimeout(std::move(state.du));
+ ++dnsdist::metrics::g_stats.downstreamTimeouts;
+ DOHUnitInterface::handleTimeout(std::move(state.du));
return;
}
if (ids.isInUse()) {
/* already used */
++reuseds;
- ++g_stats.downstreamTimeouts;
- handleDOHTimeout(std::move(state.du));
+ ++dnsdist::metrics::g_stats.downstreamTimeouts;
+ DOHUnitInterface::handleTimeout(std::move(state.du));
return;
}
ids.internal = std::move(state);
lastResults.clear();
vinfolog("Backend %s reached the lazy health-check threshold (%f%% out of %f%%, looking at sample of %d items with %d failures), moving to Potential Failure state", getNameWithAddr(), current, maxFailureRate, totalCount, failures);
stats->d_status = LazyHealthCheckStats::LazyStatus::PotentialFailure;
+ consecutiveSuccessfulChecks = 0;
/* we update the next check time here because the check might time out,
and we do not want to send a second check during that time unless
the timer is actually very short */
}
time_t backOff = d_config.d_lazyHealthCheckMaxBackOff;
- double backOffCoeffTmp = std::pow(2.0, failedTests);
- if (backOffCoeffTmp != HUGE_VAL && static_cast<uint64_t>(backOffCoeffTmp) <= static_cast<uint64_t>(std::numeric_limits<time_t>::max())) {
- time_t backOffCoeff = static_cast<time_t>(backOffCoeffTmp);
- if ((std::numeric_limits<time_t>::max() / d_config.d_lazyHealthCheckFailedInterval) >= backOffCoeff) {
- backOff = d_config.d_lazyHealthCheckFailedInterval * backOffCoeff;
- if (backOff > d_config.d_lazyHealthCheckMaxBackOff || (std::numeric_limits<time_t>::max() - now) <= backOff) {
- backOff = d_config.d_lazyHealthCheckMaxBackOff;
- }
+ const ExponentialBackOffTimer backOffTimer(d_config.d_lazyHealthCheckMaxBackOff);
+ auto backOffCoeffTmp = backOffTimer.get(failedTests - 1);
+ /* backOffCoeffTmp cannot be higher than d_config.d_lazyHealthCheckMaxBackOff */
+ const auto backOffCoeff = static_cast<time_t>(backOffCoeffTmp);
+ if ((std::numeric_limits<time_t>::max() / d_config.d_lazyHealthCheckFailedInterval) >= backOffCoeff) {
+ backOff = d_config.d_lazyHealthCheckFailedInterval * backOffCoeff;
+ if (backOff > d_config.d_lazyHealthCheckMaxBackOff || (std::numeric_limits<time_t>::max() - now) <= backOff) {
+ backOff = d_config.d_lazyHealthCheckMaxBackOff;
}
}
void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
{
+ if (!newResult) {
+ ++d_healthCheckMetrics.d_failures;
+ }
+
if (initial) {
/* if this is the initial health-check, at startup, we do not care
about the minimum number of failed/successful health-checks */
setUpStatus(newResult);
if (newResult == false) {
currentCheckFailures++;
- auto stats = d_lazyHealthCheckStats.lock();
- stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
- updateNextLazyHealthCheck(*stats, false);
+ if (d_config.availability == DownstreamState::Availability::Lazy) {
+ auto stats = d_lazyHealthCheckStats.lock();
+ stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
+ updateNextLazyHealthCheck(*stats, false);
+ }
}
return;
}
if (newResult) {
/* check succeeded */
currentCheckFailures = 0;
+ consecutiveSuccessfulChecks++;
if (!upStatus) {
/* we were previously marked as "down" and had a successful health-check,
let's see if this is enough to move to the "up" state or if we need
more successful health-checks for that */
- consecutiveSuccessfulChecks++;
if (consecutiveSuccessfulChecks < d_config.minRiseSuccesses) {
/* we need more than one successful check to rise
and we didn't reach the threshold yet, let's stay down */
auto stats = d_lazyHealthCheckStats.lock();
vinfolog("Backend %s failed its health-check, moving from Potential failure to Failed", getNameWithAddr());
stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
- currentCheckFailures = 0;
+ currentCheckFailures = 1;
updateNextLazyHealthCheck(*stats, false);
}
}
if (newState && !isTCPOnly() && (!connected || d_config.reconnectOnUp)) {
newState = reconnect();
- start();
}
setUpStatus(newState);
}
}
+#ifdef HAVE_XSK
+[[nodiscard]] ComboAddress DownstreamState::pickSourceAddressForSending()
+{
+ if (!connected) {
+ waitUntilConnected();
+ }
+
+ auto addresses = d_socketSourceAddresses.read_lock();
+ auto numberOfAddresses = addresses->size();
+ if (numberOfAddresses == 0) {
+ throw std::runtime_error("No source address available for sending XSK data to backend " + getNameWithAddr());
+ }
+ size_t idx = dnsdist::getRandomValue(numberOfAddresses);
+ return (*addresses)[idx % numberOfAddresses];
+}
+
+void DownstreamState::registerXsk(std::vector<std::shared_ptr<XskSocket>>& xsks)
+{
+ d_xskSockets = xsks;
+
+ if (d_config.sourceAddr.sin4.sin_family == 0 || (IsAnyAddress(d_config.sourceAddr))) {
+ const auto& ifName = xsks.at(0)->getInterfaceName();
+ auto addresses = getListOfAddressesOfNetworkInterface(ifName);
+ if (addresses.empty()) {
+ throw std::runtime_error("Unable to get source address from interface " + ifName);
+ }
+
+ if (addresses.size() > 1) {
+ warnlog("More than one address configured on interface %s, picking the first one (%s) for XSK. Set the 'source' parameter on 'newServer' if you want to use a different address.", ifName, addresses.at(0).toString());
+ }
+ d_config.sourceAddr = addresses.at(0);
+ }
+ d_config.sourceMACAddr = d_xskSockets.at(0)->getSourceMACAddress();
+
+ for (auto& xsk : d_xskSockets) {
+ auto xskInfo = XskWorker::create();
+ d_xskInfos.push_back(xskInfo);
+ xsk->addWorker(xskInfo);
+ xskInfo->sharedEmptyFrameOffset = xsk->sharedEmptyFrameOffset;
+ }
+ reconnect(false);
+}
+#endif /* HAVE_XSK */
+
size_t ServerPool::countServers(bool upOnly)
{
std::shared_ptr<const ServerPolicy::NumberedServerVector> servers = nullptr;
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "dolog.hh"
-#include "dnsdist.hh"
-#include "dnscrypt.hh"
+#pragma once
-#ifdef HAVE_DNSCRYPT
-int handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response)
+class ExponentialBackOffTimer
{
- query.parsePacket(packet, tcp, now);
-
- if (query.isValid() == false) {
- vinfolog("Dropping DNSCrypt invalid query");
- return false;
- }
-
- if (query.isEncrypted() == false) {
- query.getCertificateResponse(now, response);
-
- return false;
+public:
+ ExponentialBackOffTimer(unsigned int maxBackOff) :
+ d_maxBackOff(maxBackOff)
+ {
}
- if (packet.size() < static_cast<uint16_t>(sizeof(struct dnsheader))) {
- ++g_stats.nonCompliantQueries;
- return false;
+ unsigned int get(size_t consecutiveFailures) const
+ {
+ unsigned int backOff = d_maxBackOff;
+ if (consecutiveFailures <= 31) {
+ backOff = 1U << consecutiveFailures;
+ backOff = std::min(d_maxBackOff, backOff);
+ }
+ return backOff;
}
- return true;
-}
-#endif
+private:
+ const unsigned int d_maxBackOff;
+};
+++ /dev/null
-../dnsdist-cache.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <cinttypes>
+
+#include "dnsdist.hh"
+#include "dolog.hh"
+#include "dnsparser.hh"
+#include "dnsdist-cache.hh"
+#include "dnsdist-ecs.hh"
+#include "ednssubnet.hh"
+#include "packetcache.hh"
+
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters): too cumbersome to change at this point
+DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t maxNegativeTTL, uint32_t staleTTL, bool dontAge, uint32_t shards, bool deferrableInsertLock, bool parseECS) :
+ d_maxEntries(maxEntries), d_shardCount(shards), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_maxNegativeTTL(maxNegativeTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge), d_deferrableInsertLock(deferrableInsertLock), d_parseECS(parseECS)
+{
+ if (d_maxEntries == 0) {
+ throw std::runtime_error("Trying to create a 0-sized packet-cache");
+ }
+
+ d_shards.resize(d_shardCount);
+
+ /* we reserve maxEntries + 1 to avoid rehashing from occurring
+ when we get to maxEntries, as it means a load factor of 1 */
+ for (auto& shard : d_shards) {
+ shard.setSize((maxEntries / d_shardCount) + 1);
+ }
+}
+
+bool DNSDistPacketCache::getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet)
+{
+ uint16_t optRDPosition = 0;
+ size_t remaining = 0;
+
+ int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining);
+
+ if (res == 0) {
+ size_t ecsOptionStartPosition = 0;
+ size_t ecsOptionSize = 0;
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize);
+
+ if (res == 0 && ecsOptionSize > (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
+
+ EDNSSubnetOpts eso;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (getEDNSSubnetOptsFromString(reinterpret_cast<const char*>(&packet.at(optRDPosition + ecsOptionStartPosition + (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))), ecsOptionSize - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), &eso)) {
+ subnet = eso.source;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const
+{
+ if (cachedValue.queryFlags != queryFlags || cachedValue.dnssecOK != dnssecOK || cachedValue.receivedOverUDP != receivedOverUDP || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) {
+ return false;
+ }
+
+ if (d_parseECS && cachedValue.subnet != subnet) {
+ return false;
+ }
+
+ return true;
+}
+
+void DNSDistPacketCache::insertLocked(CacheShard& shard, std::unordered_map<uint32_t, CacheValue>& map, uint32_t key, CacheValue& newValue)
+{
+ /* check again now that we hold the lock to prevent a race */
+ if (map.size() >= (d_maxEntries / d_shardCount)) {
+ return;
+ }
+
+ std::unordered_map<uint32_t, CacheValue>::iterator mapIt;
+ bool result{false};
+ std::tie(mapIt, result) = map.insert({key, newValue});
+
+ if (result) {
+ ++shard.d_entriesCount;
+ return;
+ }
+
+ /* in case of collision, don't override the existing entry
+ except if it has expired */
+ CacheValue& value = mapIt->second;
+ bool wasExpired = value.validity <= newValue.added;
+
+ if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.receivedOverUDP, newValue.dnssecOK, newValue.subnet)) {
+ ++d_insertCollisions;
+ return;
+ }
+
+ /* if the existing entry had a longer TTD, keep it */
+ if (newValue.validity <= value.validity) {
+ return;
+ }
+
+ value = newValue;
+}
+
+void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL)
+{
+ if (response.size() < sizeof(dnsheader) || response.size() > getMaximumEntrySize()) {
+ return;
+ }
+
+ if (qtype == QType::AXFR || qtype == QType::IXFR) {
+ return;
+ }
+
+ uint32_t minTTL{0};
+
+ if (rcode == RCode::ServFail || rcode == RCode::Refused) {
+ minTTL = tempFailureTTL == boost::none ? d_tempFailureTTL : *tempFailureTTL;
+ if (minTTL == 0) {
+ return;
+ }
+ }
+ else {
+ bool seenAuthSOA = false;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ minTTL = getMinTTL(reinterpret_cast<const char*>(response.data()), response.size(), &seenAuthSOA);
+
+ /* no TTL found, we don't want to cache this */
+ if (minTTL == std::numeric_limits<uint32_t>::max()) {
+ return;
+ }
+
+ if (rcode == RCode::NXDomain || (rcode == RCode::NoError && seenAuthSOA)) {
+ minTTL = std::min(minTTL, d_maxNegativeTTL);
+ }
+ else if (minTTL > d_maxTTL) {
+ minTTL = d_maxTTL;
+ }
+
+ if (minTTL < d_minTTL) {
+ ++d_ttlTooShorts;
+ return;
+ }
+ }
+
+ uint32_t shardIndex = getShardIndex(key);
+
+ if (d_shards.at(shardIndex).d_entriesCount >= (d_maxEntries / d_shardCount)) {
+ return;
+ }
+
+ const time_t now = time(nullptr);
+ time_t newValidity = now + minTTL;
+ CacheValue newValue;
+ newValue.qname = qname;
+ newValue.qtype = qtype;
+ newValue.qclass = qclass;
+ newValue.queryFlags = queryFlags;
+ newValue.len = response.size();
+ newValue.validity = newValidity;
+ newValue.added = now;
+ newValue.receivedOverUDP = receivedOverUDP;
+ newValue.dnssecOK = dnssecOK;
+ newValue.value = std::string(response.begin(), response.end());
+ newValue.subnet = subnet;
+
+ auto& shard = d_shards.at(shardIndex);
+
+ if (d_deferrableInsertLock) {
+ auto lock = shard.d_map.try_write_lock();
+
+ if (!lock.owns_lock()) {
+ ++d_deferredInserts;
+ return;
+ }
+ insertLocked(shard, *lock, key, newValue);
+ }
+ else {
+ auto lock = shard.d_map.write_lock();
+
+ insertLocked(shard, *lock, key, newValue);
+ }
+}
+
+bool DNSDistPacketCache::get(DNSQuestion& dnsQuestion, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired, bool skipAging, bool truncatedOK, bool recordMiss)
+{
+ if (dnsQuestion.ids.qtype == QType::AXFR || dnsQuestion.ids.qtype == QType::IXFR) {
+ ++d_misses;
+ return false;
+ }
+
+ const auto& dnsQName = dnsQuestion.ids.qname.getStorage();
+ uint32_t key = getKey(dnsQName, dnsQuestion.ids.qname.wirelength(), dnsQuestion.getData(), receivedOverUDP);
+
+ if (keyOut != nullptr) {
+ *keyOut = key;
+ }
+
+ if (d_parseECS) {
+ getClientSubnet(dnsQuestion.getData(), dnsQuestion.ids.qname.wirelength(), subnet);
+ }
+
+ uint32_t shardIndex = getShardIndex(key);
+ time_t now = time(nullptr);
+ time_t age{0};
+ bool stale = false;
+ auto& response = dnsQuestion.getMutableData();
+ auto& shard = d_shards.at(shardIndex);
+ {
+ auto map = shard.d_map.try_read_lock();
+ if (!map.owns_lock()) {
+ ++d_deferredLookups;
+ return false;
+ }
+
+ auto mapIt = map->find(key);
+ if (mapIt == map->end()) {
+ if (recordMiss) {
+ ++d_misses;
+ }
+ return false;
+ }
+
+ const CacheValue& value = mapIt->second;
+ if (value.validity <= now) {
+ if ((now - value.validity) >= static_cast<time_t>(allowExpired)) {
+ if (recordMiss) {
+ ++d_misses;
+ }
+ return false;
+ }
+ stale = true;
+ }
+
+ if (value.len < sizeof(dnsheader)) {
+ return false;
+ }
+
+ /* check for collision */
+ if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnsQuestion.ids.qname, dnsQuestion.ids.qtype, dnsQuestion.ids.qclass, receivedOverUDP, dnssecOK, subnet)) {
+ ++d_lookupCollisions;
+ return false;
+ }
+
+ if (!truncatedOK) {
+ dnsheader dnsHeader{};
+ memcpy(&dnsHeader, value.value.data(), sizeof(dnsHeader));
+ if (dnsHeader.tc != 0) {
+ return false;
+ }
+ }
+
+ response.resize(value.len);
+ memcpy(&response.at(0), &queryId, sizeof(queryId));
+ memcpy(&response.at(sizeof(queryId)), &value.value.at(sizeof(queryId)), sizeof(dnsheader) - sizeof(queryId));
+
+ if (value.len == sizeof(dnsheader)) {
+ /* DNS header only, our work here is done */
+ ++d_hits;
+ return true;
+ }
+
+ const size_t dnsQNameLen = dnsQName.length();
+ if (value.len < (sizeof(dnsheader) + dnsQNameLen)) {
+ return false;
+ }
+
+ memcpy(&response.at(sizeof(dnsheader)), dnsQName.c_str(), dnsQNameLen);
+ if (value.len > (sizeof(dnsheader) + dnsQNameLen)) {
+ memcpy(&response.at(sizeof(dnsheader) + dnsQNameLen), &value.value.at(sizeof(dnsheader) + dnsQNameLen), value.len - (sizeof(dnsheader) + dnsQNameLen));
+ }
+
+ if (!stale) {
+ age = now - value.added;
+ }
+ else {
+ age = (value.validity - value.added) - d_staleTTL;
+ }
+ }
+
+ if (!d_dontAge && !skipAging) {
+ if (!stale) {
+ // coverity[store_truncates_time_t]
+ dnsheader_aligned dh_aligned(response.data());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ageDNSPacket(reinterpret_cast<char*>(response.data()), response.size(), age, dh_aligned);
+ }
+ else {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ editDNSPacketTTL(reinterpret_cast<char*>(response.data()), response.size(),
+ [staleTTL = d_staleTTL](uint8_t /* section */, uint16_t /* class_ */, uint16_t /* type */, uint32_t /* ttl */) { return staleTTL; });
+ }
+ }
+
+ ++d_hits;
+ return true;
+}
+
+/* Remove expired entries, until the cache has at most
+ upTo entries in it.
+ If the cache has more than one shard, we will try hard
+ to make sure that every shard has free space remaining.
+*/
+size_t DNSDistPacketCache::purgeExpired(size_t upTo, const time_t now)
+{
+ const size_t maxPerShard = upTo / d_shardCount;
+
+ size_t removed = 0;
+
+ ++d_cleanupCount;
+ for (auto& shard : d_shards) {
+ auto map = shard.d_map.write_lock();
+ if (map->size() <= maxPerShard) {
+ continue;
+ }
+
+ size_t toRemove = map->size() - maxPerShard;
+
+ for (auto it = map->begin(); toRemove > 0 && it != map->end();) {
+ const CacheValue& value = it->second;
+
+ if (value.validity <= now) {
+ it = map->erase(it);
+ --toRemove;
+ --shard.d_entriesCount;
+ ++removed;
+ }
+ else {
+ ++it;
+ }
+ }
+ }
+
+ return removed;
+}
+
+/* Remove all entries, keeping only upTo
+ entries in the cache.
+ If the cache has more than one shard, we will try hard
+ to make sure that every shard has free space remaining.
+*/
+size_t DNSDistPacketCache::expunge(size_t upTo)
+{
+ const size_t maxPerShard = upTo / d_shardCount;
+
+ size_t removed = 0;
+
+ for (auto& shard : d_shards) {
+ auto map = shard.d_map.write_lock();
+
+ if (map->size() <= maxPerShard) {
+ continue;
+ }
+
+ size_t toRemove = map->size() - maxPerShard;
+
+ auto beginIt = map->begin();
+ auto endIt = beginIt;
+
+ if (map->size() >= toRemove) {
+ std::advance(endIt, toRemove);
+ map->erase(beginIt, endIt);
+ shard.d_entriesCount -= toRemove;
+ removed += toRemove;
+ }
+ else {
+ removed += map->size();
+ map->clear();
+ shard.d_entriesCount = 0;
+ }
+ }
+
+ return removed;
+}
+
+size_t DNSDistPacketCache::expungeByName(const DNSName& name, uint16_t qtype, bool suffixMatch)
+{
+ size_t removed = 0;
+
+ for (auto& shard : d_shards) {
+ auto map = shard.d_map.write_lock();
+
+ for (auto it = map->begin(); it != map->end();) {
+ const CacheValue& value = it->second;
+
+ if ((value.qname == name || (suffixMatch && value.qname.isPartOf(name))) && (qtype == QType::ANY || qtype == value.qtype)) {
+ it = map->erase(it);
+ --shard.d_entriesCount;
+ ++removed;
+ }
+ else {
+ ++it;
+ }
+ }
+ }
+
+ return removed;
+}
+
+bool DNSDistPacketCache::isFull()
+{
+ return (getSize() >= d_maxEntries);
+}
+
+uint64_t DNSDistPacketCache::getSize()
+{
+ uint64_t count = 0;
+
+ for (auto& shard : d_shards) {
+ count += shard.d_entriesCount;
+ }
+
+ return count;
+}
+
+uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA)
+{
+ return getDNSPacketMinTTL(packet, length, seenNoDataSOA);
+}
+
+uint32_t DNSDistPacketCache::getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP)
+{
+ uint32_t result = 0;
+ /* skip the query ID */
+ if (packet.size() < sizeof(dnsheader)) {
+ throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packet.size()) + ")");
+ }
+
+ result = burtle(&packet.at(2), sizeof(dnsheader) - 2, result);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ result = burtleCI(reinterpret_cast<const unsigned char*>(qname.c_str()), qname.length(), result);
+ if (packet.size() < sizeof(dnsheader) + qnameWireLength) {
+ throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packet.size()) + " < " + std::to_string(sizeof(dnsheader) + qnameWireLength) + ")");
+ }
+ if (packet.size() > ((sizeof(dnsheader) + qnameWireLength))) {
+ if (!d_optionsToSkip.empty()) {
+ /* skip EDNS options if any */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ result = PacketCache::hashAfterQname(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_optionsToSkip);
+ }
+ else {
+ result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), packet.size() - (sizeof(dnsheader) + qnameWireLength), result);
+ }
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ result = burtle(reinterpret_cast<const unsigned char*>(&receivedOverUDP), sizeof(receivedOverUDP), result);
+ return result;
+}
+
+uint32_t DNSDistPacketCache::getShardIndex(uint32_t key) const
+{
+ return key % d_shardCount;
+}
+
+string DNSDistPacketCache::toString()
+{
+ return std::to_string(getSize()) + "/" + std::to_string(d_maxEntries);
+}
+
+uint64_t DNSDistPacketCache::getEntriesCount()
+{
+ return getSize();
+}
+
+uint64_t DNSDistPacketCache::dump(int fileDesc)
+{
+ auto filePtr = pdns::UniqueFilePtr(fdopen(dup(fileDesc), "w"));
+ if (filePtr == nullptr) {
+ return 0;
+ }
+
+ fprintf(filePtr.get(), "; dnsdist's packet cache dump follows\n;\n");
+
+ uint64_t count = 0;
+ time_t now = time(nullptr);
+ for (auto& shard : d_shards) {
+ auto map = shard.d_map.read_lock();
+
+ for (const auto& entry : *map) {
+ const CacheValue& value = entry.second;
+ count++;
+
+ try {
+ uint8_t rcode = 0;
+ if (value.len >= sizeof(dnsheader)) {
+ dnsheader dnsHeader{};
+ memcpy(&dnsHeader, value.value.data(), sizeof(dnsheader));
+ rcode = dnsHeader.rcode;
+ }
+
+ fprintf(filePtr.get(), "%s %" PRId64 " %s ; rcode %" PRIu8 ", key %" PRIu32 ", length %" PRIu16 ", received over UDP %d, added %" PRId64 "\n", value.qname.toString().c_str(), static_cast<int64_t>(value.validity - now), QType(value.qtype).toString().c_str(), rcode, entry.first, value.len, value.receivedOverUDP ? 1 : 0, static_cast<int64_t>(value.added));
+ }
+ catch (...) {
+ fprintf(filePtr.get(), "; error printing '%s'\n", value.qname.empty() ? "EMPTY" : value.qname.toString().c_str());
+ }
+ }
+ }
+
+ return count;
+}
+
+void DNSDistPacketCache::setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip)
+{
+ d_optionsToSkip = optionsToSkip;
+}
+
+std::set<DNSName> DNSDistPacketCache::getDomainsContainingRecords(const ComboAddress& addr)
+{
+ std::set<DNSName> domains;
+
+ for (auto& shard : d_shards) {
+ auto map = shard.d_map.read_lock();
+
+ for (const auto& entry : *map) {
+ const CacheValue& value = entry.second;
+
+ try {
+ if (value.len < sizeof(dnsheader)) {
+ continue;
+ }
+
+ dnsheader dnsHeader{};
+ memcpy(&dnsHeader, value.value.data(), sizeof(dnsheader));
+ if (dnsHeader.rcode != RCode::NoError || (dnsHeader.ancount == 0 && dnsHeader.nscount == 0 && dnsHeader.arcount == 0)) {
+ continue;
+ }
+
+ bool found = false;
+ bool valid = visitDNSPacket(value.value, [addr, &found](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) {
+ if (qtype == QType::A && qclass == QClass::IN && addr.isIPv4() && rdatalength == 4 && rdata != nullptr) {
+ ComboAddress parsed;
+ parsed.sin4.sin_family = AF_INET;
+ memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
+ if (parsed == addr) {
+ found = true;
+ return true;
+ }
+ }
+ else if (qtype == QType::AAAA && qclass == QClass::IN && addr.isIPv6() && rdatalength == 16 && rdata != nullptr) {
+ ComboAddress parsed;
+ parsed.sin6.sin6_family = AF_INET6;
+ memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
+ if (parsed == addr) {
+ found = true;
+ return true;
+ }
+ }
+
+ return false;
+ });
+
+ if (valid && found) {
+ domains.insert(value.qname);
+ }
+ }
+ catch (...) {
+ continue;
+ }
+ }
+ }
+
+ return domains;
+}
+
+std::set<ComboAddress> DNSDistPacketCache::getRecordsForDomain(const DNSName& domain)
+{
+ std::set<ComboAddress> addresses;
+
+ for (auto& shard : d_shards) {
+ auto map = shard.d_map.read_lock();
+
+ for (const auto& entry : *map) {
+ const CacheValue& value = entry.second;
+
+ try {
+ if (value.qname != domain) {
+ continue;
+ }
+
+ dnsheader dnsHeader{};
+ if (value.len < sizeof(dnsheader)) {
+ continue;
+ }
+
+ memcpy(&dnsHeader, value.value.data(), sizeof(dnsheader));
+ if (dnsHeader.rcode != RCode::NoError || (dnsHeader.ancount == 0 && dnsHeader.nscount == 0 && dnsHeader.arcount == 0)) {
+ continue;
+ }
+
+ visitDNSPacket(value.value, [&addresses](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) {
+ if (qtype == QType::A && qclass == QClass::IN && rdatalength == 4 && rdata != nullptr) {
+ ComboAddress parsed;
+ parsed.sin4.sin_family = AF_INET;
+ memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
+ addresses.insert(parsed);
+ }
+ else if (qtype == QType::AAAA && qclass == QClass::IN && rdatalength == 16 && rdata != nullptr) {
+ ComboAddress parsed;
+ parsed.sin6.sin6_family = AF_INET6;
+ memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
+ addresses.insert(parsed);
+ }
+
+ return false;
+ });
+ }
+ catch (...) {
+ continue;
+ }
+ }
+ }
+
+ return addresses;
+}
+
+void DNSDistPacketCache::setMaximumEntrySize(size_t maxSize)
+{
+ d_maximumEntrySize = maxSize;
+}
+++ /dev/null
-../dnsdist-cache.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <atomic>
+#include <unordered_map>
+
+#include "iputils.hh"
+#include "lock.hh"
+#include "noinitvector.hh"
+#include "stat_t.hh"
+#include "ednsoptions.hh"
+
+struct DNSQuestion;
+
+class DNSDistPacketCache : boost::noncopyable
+{
+public:
+ DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL = 86400, uint32_t minTTL = 0, uint32_t tempFailureTTL = 60, uint32_t maxNegativeTTL = 3600, uint32_t staleTTL = 60, bool dontAge = false, uint32_t shards = 1, bool deferrableInsertLock = true, bool parseECS = false);
+
+ void insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL);
+ bool get(DNSQuestion& dnsQuestion, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired = 0, bool skipAging = false, bool truncatedOK = true, bool recordMiss = true);
+ size_t purgeExpired(size_t upTo, const time_t now);
+ size_t expunge(size_t upTo = 0);
+ size_t expungeByName(const DNSName& name, uint16_t qtype = QType::ANY, bool suffixMatch = false);
+ bool isFull();
+ string toString();
+ uint64_t getSize();
+ uint64_t getHits() const { return d_hits.load(); }
+ uint64_t getMisses() const { return d_misses.load(); }
+ uint64_t getDeferredLookups() const { return d_deferredLookups.load(); }
+ uint64_t getDeferredInserts() const { return d_deferredInserts.load(); }
+ uint64_t getLookupCollisions() const { return d_lookupCollisions.load(); }
+ uint64_t getInsertCollisions() const { return d_insertCollisions.load(); }
+ uint64_t getMaxEntries() const { return d_maxEntries; }
+ uint64_t getTTLTooShorts() const { return d_ttlTooShorts.load(); }
+ uint64_t getCleanupCount() const { return d_cleanupCount.load(); }
+ uint64_t getEntriesCount();
+ uint64_t dump(int fileDesc);
+
+ /* get the list of domains (qnames) that contains the given address in an A or AAAA record */
+ std::set<DNSName> getDomainsContainingRecords(const ComboAddress& addr);
+ /* get the list of IP addresses contained in A or AAAA for a given domains (qname) */
+ std::set<ComboAddress> getRecordsForDomain(const DNSName& domain);
+
+ void setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip);
+
+ bool isECSParsingEnabled() const { return d_parseECS; }
+
+ bool keepStaleData() const
+ {
+ return d_keepStaleData;
+ }
+ void setKeepStaleData(bool keep)
+ {
+ d_keepStaleData = keep;
+ }
+
+ void setECSParsingEnabled(bool enabled)
+ {
+ d_parseECS = enabled;
+ }
+
+ void setMaximumEntrySize(size_t maxSize);
+ size_t getMaximumEntrySize() const { return d_maximumEntrySize; }
+
+ uint32_t getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP);
+
+ static uint32_t getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA);
+ static bool getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet);
+
+private:
+ struct CacheValue
+ {
+ time_t getTTD() const { return validity; }
+ std::string value;
+ DNSName qname;
+ boost::optional<Netmask> subnet;
+ uint16_t qtype{0};
+ uint16_t qclass{0};
+ uint16_t queryFlags{0};
+ time_t added{0};
+ time_t validity{0};
+ uint16_t len{0};
+ bool receivedOverUDP{false};
+ bool dnssecOK{false};
+ };
+
+ class CacheShard
+ {
+ public:
+ CacheShard()
+ {
+ }
+ CacheShard(const CacheShard& /* old */)
+ {
+ }
+
+ void setSize(size_t maxSize)
+ {
+ d_map.write_lock()->reserve(maxSize);
+ }
+
+ SharedLockGuarded<std::unordered_map<uint32_t, CacheValue>> d_map;
+ std::atomic<uint64_t> d_entriesCount{0};
+ };
+
+ bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const;
+ uint32_t getShardIndex(uint32_t key) const;
+ void insertLocked(CacheShard& shard, std::unordered_map<uint32_t, CacheValue>& map, uint32_t key, CacheValue& newValue);
+
+ std::vector<CacheShard> d_shards;
+ std::unordered_set<uint16_t> d_optionsToSkip{EDNSOptionCode::COOKIE};
+
+ pdns::stat_t d_deferredLookups{0};
+ pdns::stat_t d_deferredInserts{0};
+ pdns::stat_t d_hits{0};
+ pdns::stat_t d_misses{0};
+ pdns::stat_t d_insertCollisions{0};
+ pdns::stat_t d_lookupCollisions{0};
+ pdns::stat_t d_ttlTooShorts{0};
+ pdns::stat_t d_cleanupCount{0};
+
+ const size_t d_maxEntries;
+ size_t d_maximumEntrySize{4096};
+ const uint32_t d_shardCount;
+ const uint32_t d_maxTTL;
+ const uint32_t d_tempFailureTTL;
+ const uint32_t d_maxNegativeTTL;
+ const uint32_t d_minTTL;
+ const uint32_t d_staleTTL;
+ const bool d_dontAge;
+ const bool d_deferrableInsertLock;
+ bool d_parseECS;
+ bool d_keepStaleData{false};
+};
+++ /dev/null
-../dnsdist-carbon.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dnsdist-carbon.hh"
+#include "dnsdist.hh"
+#include "dnsdist-backoff.hh"
+#include "dnsdist-metrics.hh"
+
+#ifndef DISABLE_CARBON
+#include "dolog.hh"
+#include "sstuff.hh"
+#include "threadname.hh"
+
+namespace dnsdist
+{
+
+LockGuarded<Carbon::Config> Carbon::s_config;
+
+static bool doOneCarbonExport(const Carbon::Endpoint& endpoint)
+{
+ const auto& server = endpoint.server;
+ const std::string& namespace_name = endpoint.namespace_name;
+ const std::string& hostname = endpoint.ourname;
+ const std::string& instance_name = endpoint.instance_name;
+
+ try {
+ Socket carbonSock(server.sin4.sin_family, SOCK_STREAM);
+ carbonSock.setNonBlocking();
+ carbonSock.connect(server); // we do the connect so the attempt happens while we gather stats
+ ostringstream str;
+
+ const time_t now = time(nullptr);
+
+ {
+ auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+ for (const auto& entry : *entries) {
+ str << namespace_name << "." << hostname << "." << instance_name << "." << entry.d_name << ' ';
+ if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+ str << (*val)->load();
+ }
+ else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+ str << (*adval)->load();
+ }
+ else if (const auto& dval = std::get_if<double*>(&entry.d_value)) {
+ str << **dval;
+ }
+ else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
+ str << (*func)(entry.d_name);
+ }
+ str << ' ' << now << "\r\n";
+ }
+ }
+
+ auto states = g_dstates.getLocal();
+ for (const auto& state : *states) {
+ string serverName = state->getName().empty() ? state->d_config.remote.toStringWithPort() : state->getName();
+ boost::replace_all(serverName, ".", "_");
+ string base = namespace_name;
+ base += ".";
+ base += hostname;
+ base += ".";
+ base += instance_name;
+ base += ".servers.";
+ base += serverName;
+ base += ".";
+ str << base << "queries" << ' ' << state->queries.load() << " " << now << "\r\n";
+ str << base << "responses" << ' ' << state->responses.load() << " " << now << "\r\n";
+ str << base << "drops" << ' ' << state->reuseds.load() << " " << now << "\r\n";
+ str << base << "latency" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsec / 1000.0 : 0) << " " << now << "\r\n";
+ str << base << "latencytcp" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsecTCP / 1000.0 : 0) << " " << now << "\r\n";
+ str << base << "senderrors" << ' ' << state->sendErrors.load() << " " << now << "\r\n";
+ str << base << "outstanding" << ' ' << state->outstanding.load() << " " << now << "\r\n";
+ str << base << "tcpdiedsendingquery" << ' ' << state->tcpDiedSendingQuery.load() << " " << now << "\r\n";
+ str << base << "tcpdiedreaddingresponse" << ' ' << state->tcpDiedReadingResponse.load() << " " << now << "\r\n";
+ str << base << "tcpgaveup" << ' ' << state->tcpGaveUp.load() << " " << now << "\r\n";
+ str << base << "tcpreadimeouts" << ' ' << state->tcpReadTimeouts.load() << " " << now << "\r\n";
+ str << base << "tcpwritetimeouts" << ' ' << state->tcpWriteTimeouts.load() << " " << now << "\r\n";
+ str << base << "tcpconnecttimeouts" << ' ' << state->tcpConnectTimeouts.load() << " " << now << "\r\n";
+ str << base << "tcpcurrentconnections" << ' ' << state->tcpCurrentConnections.load() << " " << now << "\r\n";
+ str << base << "tcpmaxconcurrentconnections" << ' ' << state->tcpMaxConcurrentConnections.load() << " " << now << "\r\n";
+ str << base << "tcpnewconnections" << ' ' << state->tcpNewConnections.load() << " " << now << "\r\n";
+ str << base << "tcpreusedconnections" << ' ' << state->tcpReusedConnections.load() << " " << now << "\r\n";
+ str << base << "tlsresumptions" << ' ' << state->tlsResumptions.load() << " " << now << "\r\n";
+ str << base << "tcpavgqueriesperconnection" << ' ' << state->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n";
+ str << base << "tcpavgconnectionduration" << ' ' << state->tcpAvgConnectionDuration.load() << " " << now << "\r\n";
+ str << base << "tcptoomanyconcurrentconnections" << ' ' << state->tcpTooManyConcurrentConnections.load() << " " << now << "\r\n";
+ str << base << "healthcheckfailures" << ' ' << state->d_healthCheckMetrics.d_failures << " " << now << "\r\n";
+ str << base << "healthcheckfailuresparsing" << ' ' << state->d_healthCheckMetrics.d_parseErrors << " " << now << "\r\n";
+ str << base << "healthcheckfailurestimeout" << ' ' << state->d_healthCheckMetrics.d_timeOuts << " " << now << "\r\n";
+ str << base << "healthcheckfailuresnetwork" << ' ' << state->d_healthCheckMetrics.d_networkErrors << " " << now << "\r\n";
+ str << base << "healthcheckfailuresmismatch" << ' ' << state->d_healthCheckMetrics.d_mismatchErrors << " " << now << "\r\n";
+ str << base << "healthcheckfailuresinvalid" << ' ' << state->d_healthCheckMetrics.d_invalidResponseErrors << " " << now << "\r\n";
+ }
+
+ std::map<std::string, uint64_t> frontendDuplicates;
+ for (const auto& front : g_frontends) {
+ if (front->udpFD == -1 && front->tcpFD == -1) {
+ continue;
+ }
+
+ string frontName = front->local.toStringWithPort() + (front->udpFD >= 0 ? "_udp" : "_tcp");
+ boost::replace_all(frontName, ".", "_");
+ auto dupPair = frontendDuplicates.insert({frontName, 1});
+ if (!dupPair.second) {
+ frontName += "_" + std::to_string(dupPair.first->second);
+ ++(dupPair.first->second);
+ }
+
+ string base = namespace_name;
+ base += ".";
+ base += hostname;
+ base += ".";
+ base += instance_name;
+ base += ".frontends.";
+ base += frontName;
+ base += ".";
+ str << base << "queries" << ' ' << front->queries.load() << " " << now << "\r\n";
+ str << base << "responses" << ' ' << front->responses.load() << " " << now << "\r\n";
+ str << base << "tcpdiedreadingquery" << ' ' << front->tcpDiedReadingQuery.load() << " " << now << "\r\n";
+ str << base << "tcpdiedsendingresponse" << ' ' << front->tcpDiedSendingResponse.load() << " " << now << "\r\n";
+ str << base << "tcpgaveup" << ' ' << front->tcpGaveUp.load() << " " << now << "\r\n";
+ str << base << "tcpclienttimeouts" << ' ' << front->tcpClientTimeouts.load() << " " << now << "\r\n";
+ str << base << "tcpdownstreamtimeouts" << ' ' << front->tcpDownstreamTimeouts.load() << " " << now << "\r\n";
+ str << base << "tcpcurrentconnections" << ' ' << front->tcpCurrentConnections.load() << " " << now << "\r\n";
+ str << base << "tcpmaxconcurrentconnections" << ' ' << front->tcpMaxConcurrentConnections.load() << " " << now << "\r\n";
+ str << base << "tcpavgqueriesperconnection" << ' ' << front->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n";
+ str << base << "tcpavgconnectionduration" << ' ' << front->tcpAvgConnectionDuration.load() << " " << now << "\r\n";
+ str << base << "tls10-queries" << ' ' << front->tls10queries.load() << " " << now << "\r\n";
+ str << base << "tls11-queries" << ' ' << front->tls11queries.load() << " " << now << "\r\n";
+ str << base << "tls12-queries" << ' ' << front->tls12queries.load() << " " << now << "\r\n";
+ str << base << "tls13-queries" << ' ' << front->tls13queries.load() << " " << now << "\r\n";
+ str << base << "tls-unknown-queries" << ' ' << front->tlsUnknownqueries.load() << " " << now << "\r\n";
+ str << base << "tlsnewsessions" << ' ' << front->tlsNewSessions.load() << " " << now << "\r\n";
+ str << base << "tlsresumptions" << ' ' << front->tlsResumptions.load() << " " << now << "\r\n";
+ str << base << "tlsunknownticketkeys" << ' ' << front->tlsUnknownTicketKey.load() << " " << now << "\r\n";
+ str << base << "tlsinactiveticketkeys" << ' ' << front->tlsInactiveTicketKey.load() << " " << now << "\r\n";
+
+ const TLSErrorCounters* errorCounters = nullptr;
+ if (front->tlsFrontend != nullptr) {
+ errorCounters = &front->tlsFrontend->d_tlsCounters;
+ }
+ else if (front->dohFrontend != nullptr) {
+ errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters;
+ }
+ if (errorCounters != nullptr) {
+ str << base << "tlsdhkeytoosmall" << ' ' << errorCounters->d_dhKeyTooSmall << " " << now << "\r\n";
+ str << base << "tlsinappropriatefallback" << ' ' << errorCounters->d_inappropriateFallBack << " " << now << "\r\n";
+ str << base << "tlsnosharedcipher" << ' ' << errorCounters->d_noSharedCipher << " " << now << "\r\n";
+ str << base << "tlsunknownciphertype" << ' ' << errorCounters->d_unknownCipherType << " " << now << "\r\n";
+ str << base << "tlsunknownkeyexchangetype" << ' ' << errorCounters->d_unknownKeyExchangeType << " " << now << "\r\n";
+ str << base << "tlsunknownprotocol" << ' ' << errorCounters->d_unknownProtocol << " " << now << "\r\n";
+ str << base << "tlsunsupportedec" << ' ' << errorCounters->d_unsupportedEC << " " << now << "\r\n";
+ str << base << "tlsunsupportedprotocol" << ' ' << errorCounters->d_unsupportedProtocol << " " << now << "\r\n";
+ }
+ }
+
+ auto localPools = g_pools.getLocal();
+ for (const auto& entry : *localPools) {
+ string poolName = entry.first;
+ boost::replace_all(poolName, ".", "_");
+ if (poolName.empty()) {
+ poolName = "_default_";
+ }
+ string base = namespace_name;
+ base += ".";
+ base += hostname;
+ base += ".";
+ base += instance_name;
+ base += ".pools.";
+ base += poolName;
+ base += ".";
+ const std::shared_ptr<ServerPool> pool = entry.second;
+ str << base << "servers"
+ << " " << pool->countServers(false) << " " << now << "\r\n";
+ str << base << "servers-up"
+ << " " << pool->countServers(true) << " " << now << "\r\n";
+ if (pool->packetCache != nullptr) {
+ const auto& cache = pool->packetCache;
+ str << base << "cache-size"
+ << " " << cache->getMaxEntries() << " " << now << "\r\n";
+ str << base << "cache-entries"
+ << " " << cache->getEntriesCount() << " " << now << "\r\n";
+ str << base << "cache-hits"
+ << " " << cache->getHits() << " " << now << "\r\n";
+ str << base << "cache-misses"
+ << " " << cache->getMisses() << " " << now << "\r\n";
+ str << base << "cache-deferred-inserts"
+ << " " << cache->getDeferredInserts() << " " << now << "\r\n";
+ str << base << "cache-deferred-lookups"
+ << " " << cache->getDeferredLookups() << " " << now << "\r\n";
+ str << base << "cache-lookup-collisions"
+ << " " << cache->getLookupCollisions() << " " << now << "\r\n";
+ str << base << "cache-insert-collisions"
+ << " " << cache->getInsertCollisions() << " " << now << "\r\n";
+ str << base << "cache-ttl-too-shorts"
+ << " " << cache->getTTLTooShorts() << " " << now << "\r\n";
+ str << base << "cache-cleanup-count"
+ << " " << cache->getCleanupCount() << " " << now << "\r\n";
+ }
+ }
+
+#ifdef HAVE_DNS_OVER_HTTPS
+ {
+ std::map<std::string, uint64_t> dohFrontendDuplicates;
+ const string base = "dnsdist." + hostname + ".main.doh.";
+ for (const auto& doh : g_dohlocals) {
+ string name = doh->d_tlsContext.d_addr.toStringWithPort();
+ boost::replace_all(name, ".", "_");
+ boost::replace_all(name, ":", "_");
+ boost::replace_all(name, "[", "_");
+ boost::replace_all(name, "]", "_");
+
+ auto dupPair = dohFrontendDuplicates.insert({name, 1});
+ if (!dupPair.second) {
+ name += "_" + std::to_string(dupPair.first->second);
+ ++(dupPair.first->second);
+ }
+
+ const vector<pair<const char*, const pdns::stat_t&>> values{
+ {"http-connects", doh->d_httpconnects},
+ {"http1-queries", doh->d_http1Stats.d_nbQueries},
+ {"http2-queries", doh->d_http2Stats.d_nbQueries},
+ {"http1-200-responses", doh->d_http1Stats.d_nb200Responses},
+ {"http2-200-responses", doh->d_http2Stats.d_nb200Responses},
+ {"http1-400-responses", doh->d_http1Stats.d_nb400Responses},
+ {"http2-400-responses", doh->d_http2Stats.d_nb400Responses},
+ {"http1-403-responses", doh->d_http1Stats.d_nb403Responses},
+ {"http2-403-responses", doh->d_http2Stats.d_nb403Responses},
+ {"http1-500-responses", doh->d_http1Stats.d_nb500Responses},
+ {"http2-500-responses", doh->d_http2Stats.d_nb500Responses},
+ {"http1-502-responses", doh->d_http1Stats.d_nb502Responses},
+ {"http2-502-responses", doh->d_http2Stats.d_nb502Responses},
+ {"http1-other-responses", doh->d_http1Stats.d_nbOtherResponses},
+ {"http2-other-responses", doh->d_http2Stats.d_nbOtherResponses},
+ {"get-queries", doh->d_getqueries},
+ {"post-queries", doh->d_postqueries},
+ {"bad-requests", doh->d_badrequests},
+ {"error-responses", doh->d_errorresponses},
+ {"redirect-responses", doh->d_redirectresponses},
+ {"valid-responses", doh->d_validresponses}};
+
+ for (const auto& item : values) {
+ str << base << name << "." << item.first << " " << item.second << " " << now << "\r\n";
+ }
+ }
+ }
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+ {
+ std::string qname;
+ auto records = g_qcount.records.write_lock();
+ for (const auto& record : *records) {
+ qname = record.first;
+ boost::replace_all(qname, ".", "_");
+ str << "dnsdist.querycount." << qname << ".queries " << record.second << " " << now << "\r\n";
+ }
+ records->clear();
+ }
+
+ const string msg = str.str();
+
+ int ret = waitForRWData(carbonSock.getHandle(), false, 1, 0);
+ if (ret <= 0) {
+ vinfolog("Unable to write data to carbon server on %s: %s", server.toStringWithPort(), (ret < 0 ? stringerror() : "Timeout"));
+ return false;
+ }
+ carbonSock.setBlocking();
+ writen2(carbonSock.getHandle(), msg.c_str(), msg.size());
+ }
+ catch (const std::exception& e) {
+ warnlog("Problem sending carbon data to %s: %s", server.toStringWithPort(), e.what());
+ return false;
+ }
+
+ return true;
+}
+
+static void carbonHandler(Carbon::Endpoint&& endpoint)
+{
+ setThreadName("dnsdist/carbon");
+ const auto intervalUSec = endpoint.interval * 1000 * 1000;
+ /* maximum interval between two attempts is 10 minutes */
+ const ExponentialBackOffTimer backOffTimer(10 * 60);
+
+ try {
+ uint8_t consecutiveFailures = 0;
+ do {
+ DTime dtimer;
+ dtimer.set();
+ if (doOneCarbonExport(endpoint)) {
+ const auto elapsedUSec = dtimer.udiff();
+ if (elapsedUSec < 0 || static_cast<unsigned int>(elapsedUSec) <= intervalUSec) {
+ useconds_t toSleepUSec = intervalUSec - elapsedUSec;
+ usleep(toSleepUSec);
+ }
+ else {
+ vinfolog("Carbon export for %s took longer (%s us) than the configured interval (%d us)", endpoint.server.toStringWithPort(), elapsedUSec, intervalUSec);
+ }
+ consecutiveFailures = 0;
+ }
+ else {
+ const auto backOff = backOffTimer.get(consecutiveFailures);
+ if (consecutiveFailures < std::numeric_limits<decltype(consecutiveFailures)>::max()) {
+ consecutiveFailures++;
+ }
+ vinfolog("Run for %s - %s failed, next attempt in %d", endpoint.server.toStringWithPort(), endpoint.ourname, backOff);
+ std::this_thread::sleep_for(std::chrono::seconds(backOff));
+ }
+ } while (true);
+ }
+ catch (const PDNSException& e) {
+ errlog("Carbon thread for %s died, PDNSException: %s", endpoint.server.toStringWithPort(), e.reason);
+ }
+ catch (...) {
+ errlog("Carbon thread for %s died", endpoint.server.toStringWithPort());
+ }
+}
+
+bool Carbon::addEndpoint(Carbon::Endpoint&& endpoint)
+{
+ if (endpoint.ourname.empty()) {
+ try {
+ endpoint.ourname = getCarbonHostName();
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error(std::string("The 'ourname' setting in 'carbonServer()' has not been set and we are unable to determine the system's hostname: ") + e.what());
+ }
+ }
+
+ auto config = s_config.lock();
+ if (config->d_running) {
+ // we already started the threads, let's just spawn a new one
+ std::thread newHandler(carbonHandler, std::move(endpoint));
+ newHandler.detach();
+ }
+ else {
+ config->d_endpoints.push_back(std::move(endpoint));
+ }
+ return true;
+}
+
+void Carbon::run()
+{
+ auto config = s_config.lock();
+ if (config->d_running) {
+ throw std::runtime_error("The carbon threads are already running");
+ }
+ for (auto& endpoint : config->d_endpoints) {
+ std::thread newHandler(carbonHandler, std::move(endpoint));
+ newHandler.detach();
+ }
+ config->d_endpoints.clear();
+ config->d_running = true;
+}
+
+}
+#endif /* DISABLE_CARBON */
+
+static time_t s_start = time(nullptr);
+
+uint64_t uptimeOfProcess(const std::string& str)
+{
+ return time(nullptr) - s_start;
+}
+++ /dev/null
-../dnsdist-console.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "config.h"
+
+#include <fstream>
+// we need this to get the home directory of the current user
+#include <pwd.h>
+#include <thread>
+
+#ifdef HAVE_LIBEDIT
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+// If this is not undeffed, __attribute__ wil be redefined by /usr/include/readline/rlstdc.h
+#undef __STRICT_ANSI__
+#include <readline/readline.h>
+#include <readline/history.h>
+#else
+#include <editline/readline.h>
+#endif
+#endif /* HAVE_LIBEDIT */
+
+#include "ext/json11/json11.hpp"
+
+#include "connection-management.hh"
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsdist-console.hh"
+#include "dnsdist-crypto.hh"
+#include "threadname.hh"
+
+GlobalStateHolder<NetmaskGroup> g_consoleACL;
+vector<pair<struct timeval, string>> g_confDelta;
+std::string g_consoleKey;
+bool g_logConsoleConnections{true};
+bool g_consoleEnabled{false};
+uint32_t g_consoleOutputMsgMaxSize{10000000};
+
+static ConcurrentConnectionManager s_connManager(100);
+
+class ConsoleConnection
+{
+public:
+ ConsoleConnection(const ComboAddress& client, FDWrapper&& fileDesc) :
+ d_client(client), d_fileDesc(std::move(fileDesc))
+ {
+ if (!s_connManager.registerConnection()) {
+ throw std::runtime_error("Too many concurrent console connections");
+ }
+ }
+ ConsoleConnection(ConsoleConnection&& rhs) noexcept :
+ d_client(rhs.d_client), d_fileDesc(std::move(rhs.d_fileDesc))
+ {
+ }
+
+ ConsoleConnection(const ConsoleConnection&) = delete;
+ ConsoleConnection& operator=(const ConsoleConnection&) = delete;
+ ConsoleConnection& operator=(ConsoleConnection&&) = delete;
+
+ ~ConsoleConnection()
+ {
+ if (d_fileDesc.getHandle() != -1) {
+ s_connManager.releaseConnection();
+ }
+ }
+
+ [[nodiscard]] int getFD() const
+ {
+ return d_fileDesc.getHandle();
+ }
+
+ [[nodiscard]] const ComboAddress& getClient() const
+ {
+ return d_client;
+ }
+
+private:
+ ComboAddress d_client;
+ FDWrapper d_fileDesc;
+};
+
+void setConsoleMaximumConcurrentConnections(size_t max)
+{
+ s_connManager.setMaxConcurrentConnections(max);
+}
+
+// MUST BE CALLED UNDER A LOCK - right now the LuaLock
+static void feedConfigDelta(const std::string& line)
+{
+ if (line.empty()) {
+ return;
+ }
+ timeval now{};
+ gettimeofday(&now, nullptr);
+ g_confDelta.emplace_back(now, line);
+}
+
+#ifdef HAVE_LIBEDIT
+static string historyFile(const bool& ignoreHOME = false)
+{
+ string ret;
+
+ passwd pwd{};
+ passwd* result{nullptr};
+ std::array<char, 16384> buf{};
+ getpwuid_r(geteuid(), &pwd, buf.data(), buf.size(), &result);
+
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): we are not modifying the environment
+ const char* homedir = getenv("HOME");
+ if (result != nullptr) {
+ ret = string(pwd.pw_dir);
+ }
+ if (homedir != nullptr && !ignoreHOME) { // $HOME overrides what the OS tells us
+ ret = string(homedir);
+ }
+ if (ret.empty()) {
+ ret = "."; // CWD if nothing works..
+ }
+ ret.append("/.dnsdist_history");
+ return ret;
+}
+#endif /* HAVE_LIBEDIT */
+
+enum class ConsoleCommandResult : uint8_t
+{
+ Valid = 0,
+ ConnectionClosed,
+ TooLarge
+};
+
+static ConsoleCommandResult getMsgLen32(int fileDesc, uint32_t* len)
+{
+ try {
+ uint32_t raw{0};
+ size_t ret = readn2(fileDesc, &raw, sizeof(raw));
+
+ if (ret != sizeof raw) {
+ return ConsoleCommandResult::ConnectionClosed;
+ }
+
+ *len = ntohl(raw);
+ if (*len > g_consoleOutputMsgMaxSize) {
+ return ConsoleCommandResult::TooLarge;
+ }
+
+ return ConsoleCommandResult::Valid;
+ }
+ catch (...) {
+ return ConsoleCommandResult::ConnectionClosed;
+ }
+}
+
+static bool putMsgLen32(int fileDesc, uint32_t len)
+{
+ try {
+ uint32_t raw = htonl(len);
+ size_t ret = writen2(fileDesc, &raw, sizeof raw);
+ return ret == sizeof raw;
+ }
+ catch (...) {
+ return false;
+ }
+}
+
+static ConsoleCommandResult sendMessageToServer(int fileDesc, const std::string& line, dnsdist::crypto::authenticated::Nonce& readingNonce, dnsdist::crypto::authenticated::Nonce& writingNonce, const bool outputEmptyLine)
+{
+ string msg = dnsdist::crypto::authenticated::encryptSym(line, g_consoleKey, writingNonce);
+ const auto msgLen = msg.length();
+ if (msgLen > std::numeric_limits<uint32_t>::max()) {
+ cerr << "Encrypted message is too long to be sent to the server, " << std::to_string(msgLen) << " > " << std::numeric_limits<uint32_t>::max() << endl;
+ return ConsoleCommandResult::TooLarge;
+ }
+
+ putMsgLen32(fileDesc, static_cast<uint32_t>(msgLen));
+
+ if (!msg.empty()) {
+ writen2(fileDesc, msg);
+ }
+
+ uint32_t len{0};
+ auto commandResult = getMsgLen32(fileDesc, &len);
+ if (commandResult == ConsoleCommandResult::ConnectionClosed) {
+ cout << "Connection closed by the server." << endl;
+ return commandResult;
+ }
+ if (commandResult == ConsoleCommandResult::TooLarge) {
+ cerr << "Received a console message whose length (" << len << ") is exceeding the allowed one (" << g_consoleOutputMsgMaxSize << "), closing that connection" << endl;
+ return commandResult;
+ }
+
+ if (len == 0) {
+ if (outputEmptyLine) {
+ cout << endl;
+ }
+
+ return ConsoleCommandResult::Valid;
+ }
+
+ msg.clear();
+ msg.resize(len);
+ readn2(fileDesc, msg.data(), len);
+ msg = dnsdist::crypto::authenticated::decryptSym(msg, g_consoleKey, readingNonce);
+ cout << msg;
+ cout.flush();
+
+ return ConsoleCommandResult::Valid;
+}
+
+void doClient(ComboAddress server, const std::string& command)
+{
+ if (!dnsdist::crypto::authenticated::isValidKey(g_consoleKey)) {
+ cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl;
+ return;
+ }
+
+ if (g_verbose) {
+ cout << "Connecting to " << server.toStringWithPort() << endl;
+ }
+
+ auto fileDesc = FDWrapper(socket(server.sin4.sin_family, SOCK_STREAM, 0));
+ if (fileDesc.getHandle() < 0) {
+ cerr << "Unable to connect to " << server.toStringWithPort() << endl;
+ return;
+ }
+ SConnect(fileDesc.getHandle(), server);
+ setTCPNoDelay(fileDesc.getHandle());
+ dnsdist::crypto::authenticated::Nonce theirs;
+ dnsdist::crypto::authenticated::Nonce ours;
+ dnsdist::crypto::authenticated::Nonce readingNonce;
+ dnsdist::crypto::authenticated::Nonce writingNonce;
+ ours.init();
+
+ writen2(fileDesc.getHandle(), ours.value.data(), ours.value.size());
+ readn2(fileDesc.getHandle(), theirs.value.data(), theirs.value.size());
+ readingNonce.merge(ours, theirs);
+ writingNonce.merge(theirs, ours);
+
+ /* try sending an empty message, the server should send an empty
+ one back. If it closes the connection instead, we are probably
+ having a key mismatch issue. */
+ auto commandResult = sendMessageToServer(fileDesc.getHandle(), "", readingNonce, writingNonce, false);
+ if (commandResult == ConsoleCommandResult::ConnectionClosed) {
+ cerr << "The server closed the connection right away, likely indicating a key mismatch. Please check your setKey() directive." << endl;
+ return;
+ }
+ if (commandResult == ConsoleCommandResult::TooLarge) {
+ return;
+ }
+
+ if (!command.empty()) {
+ sendMessageToServer(fileDesc.getHandle(), command, readingNonce, writingNonce, false);
+ return;
+ }
+
+#ifdef HAVE_LIBEDIT
+ string histfile = historyFile();
+ {
+ ifstream history(histfile);
+ string line;
+ while (getline(history, line)) {
+ add_history(line.c_str());
+ }
+ }
+ ofstream history(histfile, std::ios_base::app);
+ string lastline;
+ for (;;) {
+ char* sline = readline("> ");
+ rl_bind_key('\t', rl_complete);
+ if (sline == nullptr) {
+ break;
+ }
+
+ string line(sline);
+ if (!line.empty() && line != lastline) {
+ add_history(sline);
+ history << sline << endl;
+ history.flush();
+ }
+ lastline = line;
+ // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory): readline
+ free(sline);
+
+ if (line == "quit") {
+ break;
+ }
+ if (line == "help" || line == "?") {
+ line = "help()";
+ }
+
+ /* no need to send an empty line to the server */
+ if (line.empty()) {
+ continue;
+ }
+
+ commandResult = sendMessageToServer(fileDesc.getHandle(), line, readingNonce, writingNonce, true);
+ if (commandResult != ConsoleCommandResult::Valid) {
+ break;
+ }
+ }
+#else
+ errlog("Client mode requested but libedit support is not available");
+#endif /* HAVE_LIBEDIT */
+}
+
+#ifdef HAVE_LIBEDIT
+static std::optional<std::string> getNextConsoleLine(ofstream& history, std::string& lastline)
+{
+ char* sline = readline("> ");
+ rl_bind_key('\t', rl_complete);
+ if (sline == nullptr) {
+ return std::nullopt;
+ }
+
+ string line(sline);
+ if (!line.empty() && line != lastline) {
+ add_history(sline);
+ history << sline << endl;
+ history.flush();
+ }
+
+ lastline = line;
+ // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory): readline
+ free(sline);
+
+ return line;
+}
+#else /* HAVE_LIBEDIT */
+static std::optional<std::string> getNextConsoleLine()
+{
+ std::string line;
+ if (!std::getline(std::cin, line)) {
+ return std::nullopt;
+ }
+ return line;
+}
+#endif /* HAVE_LIBEDIT */
+
+void doConsole()
+{
+#ifdef HAVE_LIBEDIT
+ string histfile = historyFile(true);
+ {
+ ifstream history(histfile);
+ string line;
+ while (getline(history, line)) {
+ add_history(line.c_str());
+ }
+ }
+ ofstream history(histfile, std::ios_base::app);
+ string lastline;
+#endif /* HAVE_LIBEDIT */
+
+ for (;;) {
+#ifdef HAVE_LIBEDIT
+ auto line = getNextConsoleLine(history, lastline);
+#else /* HAVE_LIBEDIT */
+ auto line = getNextConsoleLine();
+#endif /* HAVE_LIBEDIT */
+ if (!line) {
+ break;
+ }
+
+ if (*line == "quit") {
+ break;
+ }
+ if (*line == "help" || *line == "?") {
+ line = "help()";
+ }
+
+ string response;
+ try {
+ bool withReturn = true;
+ retry:;
+ try {
+ auto lua = g_lua.lock();
+ g_outputBuffer.clear();
+ resetLuaSideEffect();
+ auto ret = lua->executeCode<
+ boost::optional<
+ boost::variant<
+ string,
+ shared_ptr<DownstreamState>,
+ ClientState*,
+ std::unordered_map<string, double>>>>(withReturn ? ("return " + *line) : *line);
+ if (ret) {
+ if (const auto* dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
+ if (*dsValue) {
+ cout << (*dsValue)->getName() << endl;
+ }
+ }
+ else if (const auto* csValue = boost::get<ClientState*>(&*ret)) {
+ if (*csValue != nullptr) {
+ cout << (*csValue)->local.toStringWithPort() << endl;
+ }
+ }
+ else if (const auto* strValue = boost::get<string>(&*ret)) {
+ cout << *strValue << endl;
+ }
+ else if (const auto* mapValue = boost::get<std::unordered_map<string, double>>(&*ret)) {
+ using namespace json11;
+ Json::object obj;
+ for (const auto& value : *mapValue) {
+ obj[value.first] = value.second;
+ }
+ Json out = obj;
+ cout << out.dump() << endl;
+ }
+ }
+ else {
+ cout << g_outputBuffer << std::flush;
+ }
+
+ if (!getLuaNoSideEffect()) {
+ feedConfigDelta(*line);
+ }
+ }
+ catch (const LuaContext::SyntaxErrorException&) {
+ if (withReturn) {
+ withReturn = false;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
+ goto retry;
+ }
+ throw;
+ }
+ }
+ catch (const LuaContext::WrongTypeException& e) {
+ std::cerr << "Command returned an object we can't print: " << std::string(e.what()) << std::endl;
+ // tried to return something we don't understand
+ }
+ catch (const LuaContext::ExecutionErrorException& e) {
+ if (strcmp(e.what(), "invalid key to 'next'") == 0) {
+ std::cerr << "Error parsing parameters, did you forget parameter name?";
+ }
+ else {
+ std::cerr << e.what();
+ }
+
+ try {
+ std::rethrow_if_nested(e);
+
+ std::cerr << std::endl;
+ }
+ catch (const std::exception& ne) {
+ // ne is the exception that was thrown from inside the lambda
+ std::cerr << ": " << ne.what() << std::endl;
+ }
+ catch (const PDNSException& ne) {
+ // ne is the exception that was thrown from inside the lambda
+ std::cerr << ": " << ne.reason << std::endl;
+ }
+ }
+ catch (const std::exception& e) {
+ std::cerr << e.what() << std::endl;
+ }
+ }
+}
+
+#ifndef DISABLE_COMPLETION
+/**** CARGO CULT CODE AHEAD ****/
+const std::vector<ConsoleKeyword> g_consoleKeywords
+{
+ /* keyword, function, parameters, description */
+ {"addACL", true, "netmask", "add to the ACL set who can use this server"},
+ {"addAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "add a rule"},
+ {"addBPFFilterDynBlocks", true, "addresses, dynbpf[[, seconds=10], msg]", "This is the eBPF equivalent of addDynBlocks(), blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter"},
+ {"addCapabilitiesToRetain", true, "capability or list of capabilities", "Linux capabilities to retain after startup, like CAP_BPF"},
+ {"addConsoleACL", true, "netmask", "add a netmask to the console ACL"},
+ {"addDNSCryptBind", true, R"('127.0.0.1:8443", "provider name", "/path/to/resolver.cert", "/path/to/resolver.key", {reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}})", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters"},
+ {"addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables"},
+ {"addDOH3Local", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over HTTP/3 queries on the specified address using the specified certificate and key. The last parameter is a table"},
+ {"addDOQLocal", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over QUIC queries on the specified address using the specified certificate and key. The last parameter is a table"},
+ {"addDynamicBlock", true, "address, message[, action [, seconds [, clientIPMask [, clientIPPortMask]]]]", "block the supplied address with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)"},
+ {"addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)"},
+ {"addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)"},
+ {"addLocal", true, R"(addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}}])", "add `addr` to the list of addresses we listen on"},
+ {"addCacheHitResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache hit response rule"},
+ {"addCacheInsertedResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache inserted response rule"},
+ {"addMaintenanceCallback", true, "callback", "register a function to be called as part of the maintenance hook, every second"},
+ {"addResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a response rule"},
+ {"addSelfAnsweredResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a self-answered response rule"},
+ {"addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table"},
+ {"AllowAction", true, "", "let these packets go through"},
+ {"AllowResponseAction", true, "", "let these packets go through"},
+ {"AllRule", true, "", "matches all traffic"},
+ {"AndRule", true, "list of DNS rules", "matches if all sub-rules matches"},
+ {"benchRule", true, "DNS Rule [, iterations [, suffix]]", "bench the specified DNS rule"},
+ {"carbonServer", true, "serverIP, [ourname], [interval]", "report statistics to serverIP using our hostname, or 'ourname' if provided, every 'interval' seconds"},
+ {"clearConsoleHistory", true, "", "clear the internal (in-memory) history of console commands"},
+ {"clearDynBlocks", true, "", "clear all dynamic blocks"},
+ {"clearQueryCounters", true, "", "clears the query counter buffer"},
+ {"clearRules", true, "", "remove all current rules"},
+ {"controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode"},
+ {"ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action"},
+ {"declareMetric", true, "name, type, description [, prometheusName]", "Declare a custom metric"},
+ {"decMetric", true, "name", "Decrement a custom metric"},
+ {"DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)"},
+ {"DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)"},
+ {"delta", true, "", "shows all commands entered that changed the configuration"},
+ {"DNSSECRule", true, "", "matches queries with the DO bit set"},
+ {"DnstapLogAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this query to a FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSQuestion and a DnstapMessage, that can be used to modify the dnstap message"},
+ {"DnstapLogResponseAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this response to a remote or FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSResponse and a DnstapMessage, that can be used to modify the dnstap message"},
+ {"DropAction", true, "", "drop these packets"},
+ {"DropResponseAction", true, "", "drop these packets"},
+ {"DSTPortRule", true, "port", "matches questions received to the destination port specified"},
+ {"dumpStats", true, "", "print all statistics we gather"},
+ {"dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object"},
+ {"EDNSVersionRule", true, "version", "matches queries with the specified EDNS version"},
+ {"EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present"},
+ {"ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode"},
+ {"ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)"},
+ {"exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds"},
+ {"exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds"},
+ {"exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds"},
+ {"exceedRespByterate", true, "rate, seconds", "get set of addresses that exceeded `rate` bytes/s answers over `seconds` seconds"},
+ {"exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servfails/s over `seconds` seconds"},
+ {"firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit"},
+ {"fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer"},
+ {"generateDNSCryptCertificate", true, R"("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil)", "generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key"},
+ {"generateDNSCryptProviderKeys", true, R"("/path/to/providerPublic.key", "/path/to/providerPrivate.key")", "generate a new provider keypair"},
+ {"getAction", true, "n", "Returns the Action associated with rule n"},
+ {"getBind", true, "n", "returns the listener at index n"},
+ {"getBindCount", true, "", "returns the number of listeners all kinds"},
+ {"getCacheHitResponseRule", true, "selector", "Return the cache-hit response rule corresponding to the selector, if any"},
+ {"getCacheInsertedResponseRule", true, "selector", "Return the cache-inserted response rule corresponding to the selector, if any"},
+ {"getCurrentTime", true, "", "returns the current time"},
+ {"getDynamicBlocks", true, "", "returns a table of the current network-based dynamic blocks"},
+ {"getDynamicBlocksSMT", true, "", "returns a table of the current suffix-based dynamic blocks"},
+ {"getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`"},
+ {"getDNSCryptBindCount", true, "", "returns the number of DNSCrypt listeners"},
+ {"getDOHFrontend", true, "n", "returns the DoH frontend with index n"},
+ {"getDOHFrontendCount", true, "", "returns the number of DoH listeners"},
+ {"getDOH3Frontend", true, "n", "returns the DoH3 frontend with index n"},
+ {"getDOH3FrontendCount", true, "", "returns the number of DoH3 listeners"},
+ {"getDOQFrontend", true, "n", "returns the DoQ frontend with index n"},
+ {"getDOQFrontendCount", true, "", "returns the number of DoQ listeners"},
+ {"getListOfAddressesOfNetworkInterface", true, "itf", "returns the list of addresses configured on a given network interface, as strings"},
+ {"getListOfNetworkInterfaces", true, "", "returns the list of network interfaces present on the system, as strings"},
+ {"getListOfRangesOfNetworkInterface", true, "itf", "returns the list of network ranges configured on a given network interface, as strings"},
+ {"getMACAddress", true, "IP addr", "return the link-level address (MAC) corresponding to the supplied neighbour IP address, if known by the kernel"},
+ {"getMetric", true, "name", "Get the value of a custom metric"},
+ {"getOutgoingTLSSessionCacheSize", true, "", "returns the number of TLS sessions (for outgoing connections) currently cached"},
+ {"getPool", true, "name", "return the pool named `name`, or \"\" for the default pool"},
+ {"getPoolServers", true, "pool", "return servers part of this pool"},
+ {"getPoolNames", true, "", "returns a table with all the pool names"},
+ {"getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided"},
+ {"getResponseRing", true, "", "return the current content of the response ring"},
+ {"getResponseRule", true, "selector", "Return the response rule corresponding to the selector, if any"},
+ {"getRespRing", true, "", "return the qname/rcode content of the response ring"},
+ {"getRule", true, "selector", "Return the rule corresponding to the selector, if any"},
+ {"getSelfAnsweredResponseRule", true, "selector", "Return the self-answered response rule corresponding to the selector, if any"},
+ {"getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string"},
+ {"getServers", true, "", "returns a table with all defined servers"},
+ {"getStatisticsCounters", true, "", "returns a map of statistic counters"},
+ {"getTopCacheHitResponseRules", true, "[top]", "return the `top` cache-hit response rules"},
+ {"getTopCacheInsertedResponseRules", true, "[top]", "return the `top` cache-inserted response rules"},
+ {"getTopResponseRules", true, "[top]", "return the `top` response rules"},
+ {"getTopRules", true, "[top]", "return the `top` rules"},
+ {"getTopSelfAnsweredResponseRules", true, "[top]", "return the `top` self-answered response rules"},
+ {"getTLSContext", true, "n", "returns the TLS context with index n"},
+ {"getTLSFrontend", true, "n", "returns the TLS frontend with index n"},
+ {"getTLSFrontendCount", true, "", "returns the number of DoT listeners"},
+ {"getVerbose", true, "", "get whether log messages at the verbose level will be logged"},
+ {"grepq", true, R"(Netmask|DNS Name|100ms|{"::1", "powerdns.com", "100ms"} [, n] [,options])", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms"},
+ {"hashPassword", true, "password [, workFactor]", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"},
+ {"HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"},
+ {"HTTPPathRegexRule", true, "regex", "matches DoH queries whose HTTP path matches 'regex'"},
+ {"HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"},
+ {"HTTPStatusAction", true, "status, reason, body", "return an HTTP response"},
+ {"inClientStartup", true, "", "returns true during console client parsing of configuration"},
+ {"includeDirectory", true, "path", "include configuration files from `path`"},
+ {"incMetric", true, "name", "Increment a custom metric"},
+ {"KeyValueLookupKeyQName", true, "[wireFormat]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the qname of the query, either in wire format (default) or in plain text if 'wireFormat' is false"},
+ {"KeyValueLookupKeySourceIP", true, "[v4Mask [, v6Mask [, includePort]]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the (possibly bitmasked) source IP of the client in network byte-order."},
+ {"KeyValueLookupKeySuffix", true, "[minLabels [,wireFormat]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return a vector of keys based on the labels of the qname in DNS wire format or plain text"},
+ {"KeyValueLookupKeyTag", true, "tag", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the value of the corresponding tag for this query, if it exists"},
+ {"KeyValueStoreLookupAction", true, "kvs, lookupKey, destinationTag", "does a lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'"},
+ {"KeyValueStoreRangeLookupAction", true, "kvs, lookupKey, destinationTag", "does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'"},
+ {"KeyValueStoreLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store"},
+ {"KeyValueStoreRangeLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store"},
+ {"leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
+#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
+ {"loadTLSEngine", true, "engineName [, defaultString]", "Load the OpenSSL engine named 'engineName', setting the engine default string to 'defaultString' if supplied"},
+#endif
+#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+ {"loadTLSProvider", true, "providerName", "Load the OpenSSL provider named 'providerName'"},
+#endif
+ {"LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not."},
+ {"LogResponseAction", true, "[filename], [append], [buffered]", "Log a line for each response, to the specified file if any, to the console (require verbose) otherwise. The `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not."},
+ {"LuaAction", true, "function", "Invoke a Lua function that accepts a DNSQuestion"},
+ {"LuaFFIAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion"},
+ {"LuaFFIPerThreadAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion, with a per-thread Lua context"},
+ {"LuaFFIPerThreadResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse, with a per-thread Lua context"},
+ {"LuaFFIResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse"},
+ {"LuaFFIRule", true, "function", "Invoke a Lua FFI function that filters DNS questions"},
+ {"LuaResponseAction", true, "function", "Invoke a Lua function that accepts a DNSResponse"},
+ {"LuaRule", true, "function", "Invoke a Lua function that filters DNS questions"},
+#ifdef HAVE_IPCIPHER
+ {"makeIPCipherKey", true, "password", "generates a 16-byte key that can be used to pseudonymize IP addresses with IP cipher"},
+#endif /* HAVE_IPCIPHER */
+ {"makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting"},
+ {"makeRule", true, "rule", "Make a NetmaskGroupRule() or a SuffixMatchNodeRule(), depending on how it is called"},
+ {"MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60 [, scanFraction=10 [, shards=10]]]]]]]", "matches traffic exceeding the qps limit per subnet"},
+ {"MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit"},
+ {"mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+ {"mvCacheHitResponseRuleToTop", true, "", "move the last cache hit response rule to the first position"},
+ {"mvCacheInsertedResponseRule", true, "from, to", "move cache inserted response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+ {"mvCacheInsertedResponseRuleToTop", true, "", "move the last cache inserted response rule to the first position"},
+ {"mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+ {"mvResponseRuleToTop", true, "", "move the last response rule to the first position"},
+ {"mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position"},
+ {"mvRuleToTop", true, "", "move the last rule to the first position"},
+ {"mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+ {"mvSelfAnsweredResponseRuleToTop", true, "", "move the last self-answered response rule to the first position"},
+ {"NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients"},
+ {"newBPFFilter", true, "{ipv4MaxItems=int, ipv4PinnedPath=string, ipv6MaxItems=int, ipv6PinnedPath=string, cidr4MaxItems=int, cidr4PinnedPath=string, cidr6MaxItems=int, cidr6PinnedPath=string, qnamesMaxItems=int, qnamesPinnedPath=string, external=bool}", "Return a new eBPF socket filter with specified options."},
+ {"newCA", true, "address", "Returns a ComboAddress based on `address`"},
+#ifdef HAVE_CDB
+ {"newCDBKVStore", true, "fname, refreshDelay", "Return a new KeyValueStore object associated to the corresponding CDB database"},
+#endif
+ {"newDNSName", true, "name", "make a DNSName based on this .-terminated name"},
+ {"newDNSNameSet", true, "", "returns a new DNSNameSet"},
+ {"newDynBPFFilter", true, "bpf", "Return a new dynamic eBPF filter associated to a given BPF Filter"},
+ {"newFrameStreamTcpLogger", true, "addr [, options]", "create a FrameStream logger object writing to a TCP address (addr should be ip:port), to use with `DnstapLogAction()` and `DnstapLogResponseAction()`"},
+ {"newFrameStreamUnixLogger", true, "socket [, options]", "create a FrameStream logger object writing to a local unix socket, to use with `DnstapLogAction()` and `DnstapLogResponseAction()`"},
+#ifdef HAVE_LMDB
+ {"newLMDBKVStore", true, "fname, dbName [, noLock]", "Return a new KeyValueStore object associated to the corresponding LMDB database"},
+#endif
+ {"newNMG", true, "", "Returns a NetmaskGroup"},
+ {"newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache"},
+ {"newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity"},
+ {"newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`"},
+ {"newRuleAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`"},
+ {"newServer", true, R"({address="ip:port", qps=1000, order=1, weight=10, pool="abuse", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName="a.root-servers.net.", checkType="A", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source="address|interface name|address@interface", sockets=1, reconnectOnUp=false})", "instantiate a server"},
+ {"newServerPolicy", true, "name, function", "create a policy object from a Lua function"},
+ {"newSuffixMatchNode", true, "", "returns a new SuffixMatchNode"},
+ {"newSVCRecordParameters", true, "priority, target, mandatoryParams, alpns, noDefaultAlpn [, port [, ech [, ipv4hints [, ipv6hints [, additionalParameters ]]]]]", "return a new SVCRecordParameters object, to use with SpoofSVCAction"},
+ {"NegativeAndSOAAction", true, "nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options]", "Turn a query into a NXDomain or NoData answer and sets a SOA record in the additional section"},
+ {"NoneAction", true, "", "Does nothing. Subsequent rules are processed after this action"},
+ {"NotRule", true, "selector", "Matches the traffic if the selector rule does not match"},
+ {"OpcodeRule", true, "code", "Matches queries with opcode code. code can be directly specified as an integer, or one of the built-in DNSOpcodes"},
+ {"OrRule", true, "selectors", "Matches the traffic if one or more of the the selectors rules does match"},
+ {"PoolAction", true, "poolname [, stop]", "set the packet into the specified pool"},
+ {"PoolAvailableRule", true, "poolname", "Check whether a pool has any servers available to handle queries"},
+ {"PoolOutstandingRule", true, "poolname, limit", "Check whether a pool has outstanding queries above limit"},
+ {"printDNSCryptProviderFingerprint", true, R"("/path/to/providerPublic.key")", "display the fingerprint of the provided resolver public key"},
+ {"ProbaRule", true, "probability", "Matches queries with a given probability. 1.0 means always"},
+ {"ProxyProtocolValueRule", true, "type [, value]", "matches queries with a specified Proxy Protocol TLV value of that type, optionally matching the content of the option as well"},
+ {"QClassRule", true, "qclass", "Matches queries with the specified qclass. class can be specified as an integer or as one of the built-in DNSClass"},
+ {"QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels"},
+ {"QNameRule", true, "qname", "matches queries with the specified qname"},
+ {"QNameSetRule", true, "set", "Matches if the set contains exact qname"},
+ {"QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes"},
+ {"QPSAction", true, "maxqps", "Drop a packet if it does exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise"},
+ {"QPSPoolAction", true, "maxqps, poolname [, stop]", "Send the packet into the specified pool only if it does not exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise"},
+ {"QTypeRule", true, "qtype", "matches queries with the specified qtype"},
+ {"RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode"},
+ {"RCodeRule", true, "rcode", "matches responses with the specified rcode"},
+ {"RDRule", true, "", "Matches queries with the RD flag set"},
+ {"RecordsCountRule", true, "section, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records in the section section. section can be specified as an integer or as a DNS Packet Sections"},
+ {"RecordsTypeCountRule", true, "section, qtype, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records of type type in the section section"},
+ {"RegexRule", true, "regex", "matches the query name against the supplied regex"},
+ {"registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed"},
+ {"reloadAllCertificates", true, "", "reload all DNSCrypt and TLS certificates, along with their associated keys"},
+ {"RemoteLogAction", true, "RemoteLogger [, alterFunction [, serverID]]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes. `serverID` is the server identifier."},
+ {"RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME [, serverID]]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records. `serverID` is the server identifier."},
+ {"requestTCPStatesDump", true, "", "Request a dump of the TCP states (incoming connections, outgoing connections) during the next scan. Useful for debugging purposes only"},
+ {"rmACL", true, "netmask", "remove netmask from ACL"},
+ {"rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+ {"rmCacheInsertedResponseRule", true, "id", "remove cache inserted response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+ {"rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+ {"rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+ {"rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+ {"rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string"},
+ {"roundrobin", false, "", "Simple round robin over available servers"},
+ {"sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
+ {"setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us"},
+ {"setACLFromFile", true, "file", "replace the ACL set with netmasks in this file"},
+ {"setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS"},
+ {"setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends"},
+ {"setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API"},
+ {"setCacheCleaningDelay", true, "num", "Set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries"},
+ {"setCacheCleaningPercentage", true, "num", "Set the percentage of the cache that the cache cleaning algorithm will try to free by removing expired entries. By default (100), all expired entries are remove"},
+ {"setConsistentHashingBalancingFactor", true, "factor", "Set the balancing factor for bounded-load consistent hashing"},
+ {"setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks"},
+ {"setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections"},
+ {"setConsoleMaximumConcurrentConnections", true, "max", "Set the maximum number of concurrent console connections"},
+ {"setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB"},
+ {"setDefaultBPFFilter", true, "filter", "When used at configuration time, the corresponding BPFFilter will be attached to every bind"},
+ {"setDoHDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle DoH downstream connections"},
+ {"setDoHDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream DoH connection to a backend might stay idle"},
+ {"setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported"},
+ {"setDynBlocksPurgeInterval", true, "sec", "set how often the expired dynamic block entries should be removed"},
+ {"setDropEmptyQueries", true, "drop", "Whether to drop empty queries right away instead of sending a NOTIMP response"},
+ {"setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query"},
+ {"setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries"},
+ {"setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries"},
+ {"setKey", true, "key", "set access key to that key"},
+ {"setLocal", true, R"(addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}}])", "reset the list of addresses we listen on to this address"},
+ {"setMaxCachedDoHConnectionsPerDownstream", true, "max", "Set the maximum number of inactive DoH connections to a backend cached by each worker DoH thread"},
+ {"setMaxCachedTCPConnectionsPerDownstream", true, "max", "Set the maximum number of inactive TCP connections to a backend cached by each worker TCP thread"},
+ {"setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections"},
+ {"setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited"},
+ {"setMaxTCPConnectionsPerClient", true, "n", "set the maximum number of TCP connections per client. 0 means unlimited"},
+ {"setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited"},
+ {"setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)"},
+ {"setMaxUDPOutstanding", true, "n", "set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 65535"},
+ {"setMetric", true, "name, value", "Set the value of a custom metric to the supplied value"},
+ {"setPayloadSizeOnSelfGeneratedAnswers", true, "payloadSize", "set the UDP payload size advertised via EDNS on self-generated responses"},
+ {"setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy"},
+ {"setPoolServerPolicyLua", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'"},
+ {"setPoolServerPolicyLuaFFI", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'"},
+ {"setPoolServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy for this pool to one named 'name' and returned by the Lua FFI code passed in 'code'"},
+ {"setProxyProtocolACL", true, "{netmask, netmask}", "Set the netmasks who are allowed to send Proxy Protocol headers in front of queries/connections"},
+ {"setProxyProtocolApplyACLToProxiedClients", true, "apply", "Whether the general ACL should be applied to the source IP address gathered from a Proxy Protocol header, in addition to being first applied to the source address seen by dnsdist"},
+ {"setProxyProtocolMaximumPayloadSize", true, "max", "Set the maximum size of a Proxy Protocol payload, in bytes"},
+ {"setQueryCount", true, "bool", "set whether queries should be counted"},
+ {"setQueryCountFilter", true, "func", "filter queries that would be counted, where `func` is a function with parameter `dq` which decides whether a query should and how it should be counted"},
+ {"SetReducedTTLResponseAction", true, "percentage", "Reduce the TTL of records in a response to a given percentage"},
+ {"setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking"},
+ {"setRingBuffersOptions", true, "{ lockRetries=int, recordQueries=true, recordResponses=true }", "set ringbuffer options"},
+ {"setRingBuffersSize", true, "n [, numberOfShards]", "set the capacity of the ringbuffers used for live traffic inspection to `n`, and optionally the number of shards to use to `numberOfShards`"},
+ {"setRoundRobinFailOnNoServer", true, "value", "By default the roundrobin load-balancing policy will still try to select a backend even if all backends are currently down. Setting this to true will make the policy fail and return that no server is available instead"},
+ {"setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)"},
+ {"setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds"},
+ {"setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value"},
+ {"setServerPolicy", true, "policy", "set server selection policy to that policy"},
+ {"setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'"},
+ {"setServerPolicyLuaFFI", true, "name, function", "set server selection policy to one named 'name' and provided by the Lua FFI 'function'"},
+ {"setServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy to one named 'name' and returned by the Lua FFI code passed in 'code'"},
+ {"setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query"},
+ {"setStaleCacheEntriesTTL", true, "n", "allows using cache entries expired for at most n seconds when there is no backend available to answer for a query"},
+ {"setStructuredLogging", true, "value [, options]", "set whether log messages should be in structured-logging-like format"},
+ {"setSyslogFacility", true, "facility", "set the syslog logging facility to 'facility'. Defaults to LOG_DAEMON"},
+ {"setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections"},
+ {"setTCPFastOpenKey", true, "string", "TCP Fast Open Key"},
+ {"setTCPDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream TCP connection to a backend might stay idle"},
+ {"setTCPInternalPipeBufferSize", true, "size", "Set the size in bytes of the internal buffer of the pipes used internally to distribute connections to TCP (and DoT) workers threads"},
+ {"setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds"},
+ {"setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds"},
+ {"setUDPMultipleMessagesVectorSize", true, "n", "set the size of the vector passed to recvmmsg() to receive UDP messages. Default to 1 which means that the feature is disabled and recvmsg() is used instead"},
+ {"setUDPSocketBufferSizes", true, "recv, send", "Set the size of the receive (SO_RCVBUF) and send (SO_SNDBUF) buffers for incoming UDP sockets"},
+ {"setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds"},
+ {"setVerbose", true, "bool", "set whether log messages at the verbose level will be logged"},
+ {"setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged"},
+ {"setVerboseLogDestination", true, "destination file", "Set a destination file to write the 'verbose' log messages to, instead of sending them to syslog and/or the standard output"},
+ {"setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders, statsRequireAuthentication}]", "Updates webserver configuration"},
+ {"setWeightedBalancingFactor", true, "factor", "Set the balancing factor for bounded-load weighted policies (whashed, wrandom)"},
+ {"setWHashedPertubation", true, "value", "Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instance"},
+ {"show", true, "string", "outputs `string`"},
+ {"showACL", true, "", "show our ACL set"},
+ {"showBinds", true, "", "show listening addresses (frontends)"},
+ {"showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width"},
+ {"showConsoleACL", true, "", "show our current console ACL set"},
+ {"showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds"},
+ {"showDOHFrontends", true, "", "list all the available DOH frontends"},
+ {"showDOH3Frontends", true, "", "list all the available DOH3 frontends"},
+ {"showDOHResponseCodes", true, "", "show the HTTP response code statistics for the DoH frontends"},
+ {"showDOQFrontends", true, "", "list all the available DOQ frontends"},
+ {"showDynBlocks", true, "", "show dynamic blocks in force"},
+ {"showPools", true, "", "show the available pools"},
+ {"showPoolServerPolicy", true, "pool", "show server selection policy for this pool"},
+ {"showResponseLatency", true, "", "show a plot of the response time latency distribution"},
+ {"showResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined response rules, optionally with their UUIDs and optionally truncated to a given width"},
+ {"showRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined rules, optionally with their UUIDs and optionally truncated to a given width"},
+ {"showSecurityStatus", true, "", "Show the security status"},
+ {"showSelfAnsweredResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined self-answered response rules, optionally with their UUIDs and optionally truncated to a given width"},
+ {"showServerPolicy", true, "", "show name of currently operational server selection policy"},
+ {"showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs"},
+ {"showTCPStats", true, "", "show some statistics regarding TCP"},
+ {"showTLSContexts", true, "", "list all the available TLS contexts"},
+ {"showTLSErrorCounters", true, "", "show metrics about TLS handshake failures"},
+ {"showVersion", true, "", "show the current version"},
+ {"showWebserverConfig", true, "", "Show the current webserver configuration"},
+ {"shutdown", true, "", "shut down `dnsdist`"},
+ {"snmpAgent", true, "enableTraps [, daemonSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `daemonSocket` an optional string specifying how to connect to the daemon agent"},
+ {"SetAdditionalProxyProtocolValueAction", true, "type, value", "Add a Proxy Protocol TLV value of this type"},
+ {"SetDisableECSAction", true, "", "Disable the sending of ECS to the backend. Subsequent rules are processed after this action."},
+ {"SetDisableValidationAction", true, "", "set the CD bit in the question, let it go through"},
+ {"SetECSAction", true, "v4[, v6]", "Set the ECS prefix and prefix length sent to backends to an arbitrary value"},
+ {"SetECSOverrideAction", true, "override", "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action"},
+ {"SetECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action"},
+ {"SetMacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action"},
+ {"SetEDNSOptionAction", true, "option, data", "Add arbitrary EDNS option and data to the query. Subsequent rules are processed after this action"},
+ {"SetExtendedDNSErrorAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to the response corresponding to the current query. Subsequent rules are processed after this action"},
+ {"SetExtendedDNSErrorResponseAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to this response. Subsequent rules are processed after this action"},
+ {"SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through"},
+ {"setOutgoingDoHWorkerThreads", true, "n", "Number of outgoing DoH worker threads"},
+ {"SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'"},
+ {"SetSkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer"},
+ {"SetSkipCacheResponseAction", true, "", "Don’t store this response into the cache"},
+ {"SetTagAction", true, "name, value", "set the tag named 'name' to the given value"},
+ {"SetTagResponseAction", true, "name, value", "set the tag named 'name' to the given value"},
+ {"SetTempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies"},
+ {"SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)"},
+ {"SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
+ {"SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
+ {"SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in"},
+ {"SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value"},
+ {"SpoofRawAction", true, "raw|list of raws [, options]", "Forge a response with the specified record data as raw bytes. If you specify multiple raws (it is assumed they match the query type), all will get spoofed in"},
+ {"SpoofSVCAction", true, "list of svcParams [, options]", "Forge a response with the specified SVC record data"},
+ {"SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched"},
+ {"TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any"},
+ {"TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP"},
+ {"TCPRule", true, "[tcp]", "Matches question received over TCP if tcp is true, over UDP otherwise"},
+ {"TCResponseAction", true, "", "truncate a response"},
+ {"TeeAction", true, "remote [, addECS [, local]]", "send copy of query to remote, optionally adding ECS info, optionally set local address"},
+ {"testCrypto", true, "", "test of the crypto all works"},
+ {"TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"},
+ {"topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer"},
+ {"topCacheHitResponseRules", true, "[top][, vars]", "show `top` cache-hit response rules"},
+ {"topCacheInsertedResponseRules", true, "[top][, vars]", "show `top` cache-inserted response rules"},
+ {"topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer"},
+ {"topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels"},
+ {"topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=NXDomain), as grouped when optionally cut down to 'labels' labels"},
+ {"topResponseRules", true, "[top][, vars]", "show `top` response rules"},
+ {"topRules", true, "[top][, vars]", "show `top` rules"},
+ {"topSelfAnsweredResponseRules", true, "[top][, vars]", "show `top` self-answered response rules"},
+ {"topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels"},
+ {"TrailingDataRule", true, "", "Matches if the query has trailing data"},
+ {"truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891."},
+ {"unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter"},
+ {"webserver", true, "address:port", "launch a webserver with stats on that address"},
+ {"whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter"},
+ {"chashed", false, "", "Consistent hashed ('sticky') distribution over available servers, also based on the server 'weight' parameter"},
+ {"wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter"},
+};
+
+#if defined(HAVE_LIBEDIT)
+extern "C"
+{
+ static char* my_generator(const char* text, int state)
+ {
+ string textStr(text);
+ /* to keep it readable, we try to keep only 4 keywords per line
+ and to start a new line when the first letter changes */
+ static int s_counter = 0;
+ int counter = 0;
+ if (state == 0) {
+ s_counter = 0;
+ }
+
+ for (const auto& keyword : g_consoleKeywords) {
+ if (boost::starts_with(keyword.name, textStr) && counter++ == s_counter) {
+ std::string value(keyword.name);
+ s_counter++;
+ if (keyword.function) {
+ value += "(";
+ if (keyword.parameters.empty()) {
+ value += ")";
+ }
+ }
+ return strdup(value.c_str());
+ }
+ }
+ return nullptr;
+ }
+
+ char** my_completion(const char* text, int start, int end)
+ {
+ char** matches = nullptr;
+ if (start == 0) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): readline
+ matches = rl_completion_matches(const_cast<char*>(text), &my_generator);
+ }
+
+ // skip default filename completion.
+ rl_attempted_completion_over = 1;
+
+ return matches;
+ }
+}
+#endif /* HAVE_LIBEDIT */
+#endif /* DISABLE_COMPLETION */
+
+static void controlClientThread(ConsoleConnection&& conn)
+{
+ try {
+ setThreadName("dnsdist/conscli");
+
+ setTCPNoDelay(conn.getFD());
+
+ dnsdist::crypto::authenticated::Nonce theirs;
+ dnsdist::crypto::authenticated::Nonce ours;
+ dnsdist::crypto::authenticated::Nonce readingNonce;
+ dnsdist::crypto::authenticated::Nonce writingNonce;
+ ours.init();
+ readn2(conn.getFD(), theirs.value.data(), theirs.value.size());
+ writen2(conn.getFD(), ours.value.data(), ours.value.size());
+ readingNonce.merge(ours, theirs);
+ writingNonce.merge(theirs, ours);
+
+ for (;;) {
+ uint32_t len{0};
+ if (getMsgLen32(conn.getFD(), &len) != ConsoleCommandResult::Valid) {
+ break;
+ }
+
+ if (len == 0) {
+ /* just ACK an empty message
+ with an empty response */
+ putMsgLen32(conn.getFD(), 0);
+ continue;
+ }
+
+ std::string line;
+ line.resize(len);
+ readn2(conn.getFD(), line.data(), len);
+
+ line = dnsdist::crypto::authenticated::decryptSym(line, g_consoleKey, readingNonce);
+
+ string response;
+ try {
+ bool withReturn = true;
+ retry:;
+ try {
+ auto lua = g_lua.lock();
+
+ g_outputBuffer.clear();
+ resetLuaSideEffect();
+ auto ret = lua->executeCode<
+ boost::optional<
+ boost::variant<
+ string,
+ shared_ptr<DownstreamState>,
+ ClientState*,
+ std::unordered_map<string, double>>>>(withReturn ? ("return " + line) : line);
+
+ if (ret) {
+ if (const auto* dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
+ if (*dsValue) {
+ response = (*dsValue)->getName() + "\n";
+ }
+ else {
+ response = "";
+ }
+ }
+ else if (const auto* csValue = boost::get<ClientState*>(&*ret)) {
+ if (*csValue != nullptr) {
+ response = (*csValue)->local.toStringWithPort() + "\n";
+ }
+ else {
+ response = "";
+ }
+ }
+ else if (const auto* strValue = boost::get<string>(&*ret)) {
+ response = *strValue + "\n";
+ }
+ else if (const auto* mapValue = boost::get<std::unordered_map<string, double>>(&*ret)) {
+ using namespace json11;
+ Json::object obj;
+ for (const auto& value : *mapValue) {
+ obj[value.first] = value.second;
+ }
+ Json out = obj;
+ response = out.dump() + "\n";
+ }
+ }
+ else {
+ response = g_outputBuffer;
+ }
+ if (!getLuaNoSideEffect()) {
+ feedConfigDelta(line);
+ }
+ }
+ catch (const LuaContext::SyntaxErrorException&) {
+ if (withReturn) {
+ withReturn = false;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
+ goto retry;
+ }
+ throw;
+ }
+ }
+ catch (const LuaContext::WrongTypeException& e) {
+ response = "Command returned an object we can't print: " + std::string(e.what()) + "\n";
+ // tried to return something we don't understand
+ }
+ catch (const LuaContext::ExecutionErrorException& e) {
+ if (strcmp(e.what(), "invalid key to 'next'") == 0) {
+ response = "Error: Parsing function parameters, did you forget parameter name?";
+ }
+ else {
+ response = "Error: " + string(e.what());
+ }
+
+ try {
+ std::rethrow_if_nested(e);
+ }
+ catch (const std::exception& ne) {
+ // ne is the exception that was thrown from inside the lambda
+ response += ": " + string(ne.what());
+ }
+ catch (const PDNSException& ne) {
+ // ne is the exception that was thrown from inside the lambda
+ response += ": " + string(ne.reason);
+ }
+ }
+ catch (const LuaContext::SyntaxErrorException& e) {
+ response = "Error: " + string(e.what()) + ": ";
+ }
+ response = dnsdist::crypto::authenticated::encryptSym(response, g_consoleKey, writingNonce);
+ putMsgLen32(conn.getFD(), response.length());
+ writen2(conn.getFD(), response.c_str(), response.length());
+ }
+ if (g_logConsoleConnections) {
+ infolog("Closed control connection from %s", conn.getClient().toStringWithPort());
+ }
+ }
+ catch (const std::exception& e) {
+ infolog("Got an exception in client connection from %s: %s", conn.getClient().toStringWithPort(), e.what());
+ }
+}
+
+// NOLINTNEXTLINE(performance-unnecessary-value-param): this is thread
+void controlThread(std::shared_ptr<Socket> acceptFD, ComboAddress local)
+{
+ try {
+ setThreadName("dnsdist/control");
+ ComboAddress client;
+ // make sure that the family matches the one from the listening IP,
+ // so that getSocklen() returns the correct size later, otherwise
+ // the first IPv6 console connection might get refused
+ client.sin4.sin_family = local.sin4.sin_family;
+
+ int sock{-1};
+ auto localACL = g_consoleACL.getLocal();
+ infolog("Accepting control connections on %s", local.toStringWithPort());
+
+ while ((sock = SAccept(acceptFD->getHandle(), client)) >= 0) {
+
+ FDWrapper socket(sock);
+ if (!dnsdist::crypto::authenticated::isValidKey(g_consoleKey)) {
+ vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort());
+ continue;
+ }
+
+ if (!localACL->match(client)) {
+ vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort());
+ continue;
+ }
+
+ try {
+ ConsoleConnection conn(client, std::move(socket));
+ if (g_logConsoleConnections) {
+ warnlog("Got control connection from %s", client.toStringWithPort());
+ }
+
+ std::thread clientThread(controlClientThread, std::move(conn));
+ clientThread.detach();
+ }
+ catch (const std::exception& e) {
+ infolog("Control connection died: %s", e.what());
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ errlog("Control thread died: %s", e.what());
+ }
+}
+
+void clearConsoleHistory()
+{
+#ifdef HAVE_LIBEDIT
+ clear_history();
+#endif /* HAVE_LIBEDIT */
+ g_confDelta.clear();
+}
+++ /dev/null
-../dnsdist-console.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "config.h"
+#include "sstuff.hh"
+
+#ifndef DISABLE_COMPLETION
+struct ConsoleKeyword
+{
+ std::string name;
+ bool function;
+ std::string parameters;
+ std::string description;
+ std::string toString() const
+ {
+ std::string res(name);
+ if (function) {
+ res += "(" + parameters + ")";
+ }
+ res += ": ";
+ res += description;
+ return res;
+ }
+};
+extern const std::vector<ConsoleKeyword> g_consoleKeywords;
+extern "C"
+{
+ char** my_completion(const char* text, int start, int end);
+}
+
+#endif /* DISABLE_COMPLETION */
+
+extern GlobalStateHolder<NetmaskGroup> g_consoleACL;
+extern std::string g_consoleKey; // in theory needs locking
+extern bool g_logConsoleConnections;
+extern bool g_consoleEnabled;
+extern uint32_t g_consoleOutputMsgMaxSize;
+
+void doClient(ComboAddress server, const std::string& command);
+void doConsole();
+void controlThread(std::shared_ptr<Socket> acceptFD, ComboAddress local);
+void clearConsoleHistory();
+
+void setConsoleMaximumConcurrentConnections(size_t max);
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <iostream>
+#include <arpa/inet.h>
+
+#include "dnsdist-crypto.hh"
+
+#include "namespaces.hh"
+#include "noinitvector.hh"
+#include "misc.hh"
+#include "base64.hh"
+
+namespace dnsdist::crypto::authenticated
+{
+#ifdef HAVE_LIBSODIUM
+string newKey(bool base64Encoded)
+{
+ std::string key;
+ key.resize(crypto_secretbox_KEYBYTES);
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ randombytes_buf(reinterpret_cast<unsigned char*>(key.data()), key.size());
+
+ if (!base64Encoded) {
+ return key;
+ }
+ return "\"" + Base64Encode(key) + "\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+ return key.size() == crypto_secretbox_KEYBYTES;
+}
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ if (!isValidKey(key)) {
+ throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + " (" + std::to_string(crypto_secretbox_KEYBYTES) + " expected), use setKey() to set a valid key");
+ }
+
+ std::string ciphertext;
+ ciphertext.resize(msg.length() + crypto_secretbox_MACBYTES);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ crypto_secretbox_easy(reinterpret_cast<unsigned char*>(ciphertext.data()),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const unsigned char*>(msg.data()),
+ msg.length(),
+ nonce.value.data(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const unsigned char*>(key.data()));
+
+ if (incrementNonce) {
+ nonce.increment();
+ }
+
+ return ciphertext;
+}
+
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ std::string decrypted;
+
+ if (msg.length() < crypto_secretbox_MACBYTES) {
+ throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
+ }
+
+ if (!isValidKey(key)) {
+ throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
+ }
+
+ decrypted.resize(msg.length() - crypto_secretbox_MACBYTES);
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (crypto_secretbox_open_easy(reinterpret_cast<unsigned char*>(decrypted.data()),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const unsigned char*>(msg.data()),
+ msg.length(),
+ nonce.value.data(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const unsigned char*>(key.data()))
+ != 0) {
+ throw std::runtime_error("Could not decrypt message, please check that the key configured with setKey() is correct");
+ }
+
+ if (incrementNonce) {
+ nonce.increment();
+ }
+
+ return decrypted;
+}
+
+void Nonce::init()
+{
+ randombytes_buf(value.data(), value.size());
+}
+
+#elif defined(HAVE_LIBCRYPTO)
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+static constexpr size_t s_CHACHA20_POLY1305_KEY_SIZE = 32U;
+static constexpr size_t s_POLY1305_BLOCK_SIZE = 16U;
+
+string newKey(bool base64Encoded)
+{
+ std::string key;
+ key.resize(s_CHACHA20_POLY1305_KEY_SIZE);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (RAND_priv_bytes(reinterpret_cast<unsigned char*>(key.data()), key.size()) != 1) {
+ throw std::runtime_error("Could not initialize random number generator for cryptographic functions");
+ }
+ if (!base64Encoded) {
+ return key;
+ }
+ return "\"" + Base64Encode(key) + "\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+ return key.size() == s_CHACHA20_POLY1305_KEY_SIZE;
+}
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ if (!isValidKey(key)) {
+ throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + " (" + std::to_string(s_CHACHA20_POLY1305_KEY_SIZE) + " expected), use setKey() to set a valid key");
+ }
+
+ // Each thread gets its own cipher context
+ static thread_local auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(nullptr, EVP_CIPHER_CTX_free);
+
+ if (!ctx) {
+ ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
+ if (!ctx) {
+ throw std::runtime_error("encryptSym: EVP_CIPHER_CTX_new() could not initialize cipher context");
+ }
+
+ if (EVP_EncryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {
+ throw std::runtime_error("encryptSym: EVP_EncryptInit_ex() could not initialize encryption operation");
+ }
+ }
+
+ std::string ciphertext;
+ /* plus one so we can access the last byte in EncryptFinal which does nothing for this algo */
+ ciphertext.resize(s_POLY1305_BLOCK_SIZE + msg.length() + 1);
+ int outLength{0};
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), nonce.value.data()) != 1) {
+ throw std::runtime_error("encryptSym: EVP_EncryptInit_ex() could not initialize encryption key and IV");
+ }
+
+ if (!msg.empty()) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (EVP_EncryptUpdate(ctx.get(),
+ reinterpret_cast<unsigned char*>(&ciphertext.at(s_POLY1305_BLOCK_SIZE)), &outLength,
+ reinterpret_cast<const unsigned char*>(msg.data()), msg.length())
+ != 1) {
+ throw std::runtime_error("encryptSym: EVP_EncryptUpdate() could not encrypt message");
+ }
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (EVP_EncryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char*>(&ciphertext.at(s_POLY1305_BLOCK_SIZE + outLength)), &outLength) != 1) {
+ throw std::runtime_error("encryptSym: EVP_EncryptFinal_ex() could finalize message encryption");
+ ;
+ }
+
+ /* Get the tag */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, s_POLY1305_BLOCK_SIZE, ciphertext.data()) != 1) {
+ throw std::runtime_error("encryptSym: EVP_CIPHER_CTX_ctrl() could not get tag");
+ }
+
+ if (incrementNonce) {
+ nonce.increment();
+ }
+
+ ciphertext.resize(ciphertext.size() - 1);
+ return ciphertext;
+}
+
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ if (msg.length() < s_POLY1305_BLOCK_SIZE) {
+ throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
+ }
+
+ if (!isValidKey(key)) {
+ throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
+ }
+
+ if (msg.length() == s_POLY1305_BLOCK_SIZE) {
+ if (incrementNonce) {
+ nonce.increment();
+ }
+ return std::string();
+ }
+
+ // Each thread gets its own cipher context
+ static thread_local auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(nullptr, EVP_CIPHER_CTX_free);
+ if (!ctx) {
+ ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
+ if (!ctx) {
+ throw std::runtime_error("decryptSym: EVP_CIPHER_CTX_new() could not initialize cipher context");
+ }
+
+ if (EVP_DecryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {
+ throw std::runtime_error("decryptSym: EVP_DecryptInit_ex() could not initialize decryption operation");
+ }
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), nonce.value.data()) != 1) {
+ throw std::runtime_error("decryptSym: EVP_DecryptInit_ex() could not initialize decryption key and IV");
+ }
+
+ const auto tag = msg.substr(0, s_POLY1305_BLOCK_SIZE);
+ std::string decrypted;
+ /* plus one so we can access the last byte in DecryptFinal, which does nothing */
+ decrypted.resize(msg.length() - s_POLY1305_BLOCK_SIZE + 1);
+ int outLength{0};
+ if (msg.size() > s_POLY1305_BLOCK_SIZE) {
+ if (!EVP_DecryptUpdate(ctx.get(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<unsigned char*>(decrypted.data()), &outLength,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const unsigned char*>(&msg.at(s_POLY1305_BLOCK_SIZE)), msg.size() - s_POLY1305_BLOCK_SIZE)) {
+ throw std::runtime_error("Could not decrypt message (update failed), please check that the key configured with setKey() is correct");
+ }
+ }
+
+ /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): sorry, OpenSSL's API is terrible
+ if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, s_POLY1305_BLOCK_SIZE, const_cast<char*>(tag.data()))) {
+ throw std::runtime_error("Could not decrypt message (invalid tag), please check that the key configured with setKey() is correct");
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (!EVP_DecryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char*>(&decrypted.at(outLength)), &outLength)) {
+ throw std::runtime_error("Could not decrypt message (final failed), please check that the key configured with setKey() is correct");
+ }
+
+ if (incrementNonce) {
+ nonce.increment();
+ }
+
+ decrypted.resize(decrypted.size() - 1);
+ return decrypted;
+}
+
+void Nonce::init()
+{
+ if (RAND_priv_bytes(value.data(), value.size()) != 1) {
+ throw std::runtime_error("Could not initialize random number generator for cryptographic functions");
+ }
+}
+#endif
+
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+void Nonce::merge(const Nonce& lower, const Nonce& higher)
+{
+ constexpr size_t halfSize = std::tuple_size<decltype(value)>{} / 2;
+ memcpy(value.data(), lower.value.data(), halfSize);
+ memcpy(value.data() + halfSize, higher.value.data() + halfSize, halfSize);
+}
+
+void Nonce::increment()
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ auto* ptr = reinterpret_cast<uint32_t*>(value.data());
+ uint32_t count = htonl(*ptr);
+ *ptr = ntohl(++count);
+}
+
+#else
+void Nonce::init()
+{
+}
+
+void Nonce::merge(const Nonce& lower, const Nonce& higher)
+{
+}
+
+void Nonce::increment()
+{
+}
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ return std::string(msg);
+}
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+ return std::string(msg);
+}
+
+string newKey(bool base64Encoded)
+{
+ return "\"plaintext\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+ return true;
+}
+
+#endif
+}
+
+#include <cinttypes>
+
+namespace anonpdns
+{
+static char B64Decode1(char cInChar)
+{
+ // The incoming character will be A-Z, a-z, 0-9, +, /, or =.
+ // The idea is to quickly determine which grouping the
+ // letter belongs to and return the associated value
+ // without having to search the global encoding string
+ // (the value we're looking for would be the resulting
+ // index into that string).
+ //
+ // To do that, we'll play some tricks...
+ unsigned char iIndex = '\0';
+ switch (cInChar) {
+ case '+':
+ iIndex = 62;
+ break;
+
+ case '/':
+ iIndex = 63;
+ break;
+
+ case '=':
+ iIndex = 0;
+ break;
+
+ default:
+ // Must be 'A'-'Z', 'a'-'z', '0'-'9', or an error...
+ //
+ // Numerically, small letters are "greater" in value than
+ // capital letters and numerals (ASCII value), and capital
+ // letters are "greater" than numerals (again, ASCII value),
+ // so we check for numerals first, then capital letters,
+ // and finally small letters.
+ iIndex = '9' - cInChar;
+ if (iIndex > 0x3F) {
+ // Not from '0' to '9'...
+ iIndex = 'Z' - cInChar;
+ if (iIndex > 0x3F) {
+ // Not from 'A' to 'Z'...
+ iIndex = 'z' - cInChar;
+ if (iIndex > 0x3F) {
+ // Invalid character...cannot
+ // decode!
+ iIndex = 0x80; // set the high bit
+ } // if
+ else {
+ // From 'a' to 'z'
+ iIndex = (('z' - iIndex) - 'a') + 26;
+ } // else
+ } // if
+ else {
+ // From 'A' to 'Z'
+ iIndex = ('Z' - iIndex) - 'A';
+ } // else
+ } // if
+ else {
+ // Adjust the index...
+ iIndex = (('9' - iIndex) - '0') + 52;
+ } // else
+ break;
+
+ } // switch
+
+ return static_cast<char>(iIndex);
+}
+
+static inline char B64Encode1(unsigned char input)
+{
+ if (input < 26) {
+ return static_cast<char>('A' + input);
+ }
+ if (input < 52) {
+ return static_cast<char>('a' + (input - 26));
+ }
+ if (input < 62) {
+ return static_cast<char>('0' + (input - 52));
+ }
+ if (input == 62) {
+ return '+';
+ }
+ return '/';
+};
+
+}
+using namespace anonpdns;
+
+template <typename Container>
+int B64Decode(const std::string& strInput, Container& strOutput)
+{
+ // Set up a decoding buffer
+ long cBuf = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ char* pBuf = reinterpret_cast<char*>(&cBuf);
+
+ // Decoding management...
+ int iBitGroup = 0;
+ int iInNum = 0;
+
+ // While there are characters to process...
+ //
+ // We'll decode characters in blocks of 4, as
+ // there are 4 groups of 6 bits in 3 bytes. The
+ // incoming Base64 character is first decoded, and
+ // then it is inserted into the decode buffer
+ // (with any relevant shifting, as required).
+ // Later, after all 3 bytes have been reconstituted,
+ // we assign them to the output string, ultimately
+ // to be returned as the original message.
+ int iInSize = static_cast<int>(strInput.size());
+ unsigned char cChar = '\0';
+ uint8_t pad = 0;
+ while (iInNum < iInSize) {
+ // Fill the decode buffer with 4 groups of 6 bits
+ cBuf = 0; // clear
+ pad = 0;
+ for (iBitGroup = 0; iBitGroup < 4; ++iBitGroup) {
+ if (iInNum < iInSize) {
+ // Decode a character
+ if (strInput.at(iInNum) == '=') {
+ pad++;
+ }
+ while (isspace(strInput.at(iInNum))) {
+ iInNum++;
+ }
+ cChar = B64Decode1(strInput.at(iInNum++));
+
+ } // if
+ else {
+ // Decode a padded zero
+ cChar = '\0';
+ } // else
+
+ // Check for valid decode
+ if (cChar > 0x7F) {
+ return -1;
+ }
+
+ // Adjust the bits
+ switch (iBitGroup) {
+ case 0:
+ // The first group is copied into
+ // the least significant 6 bits of
+ // the decode buffer...these 6 bits
+ // will eventually shift over to be
+ // the most significant bits of the
+ // third byte.
+ cBuf = cBuf | cChar;
+ break;
+
+ default:
+ // For groupings 1-3, simply shift
+ // the bits in the decode buffer over
+ // by 6 and insert the 6 from the
+ // current decode character.
+ cBuf = (cBuf << 6) | cChar;
+ break;
+
+ } // switch
+ } // for
+
+ // Interpret the resulting 3 bytes...note there
+ // may have been padding, so those padded bytes
+ // are actually ignored.
+#if BYTE_ORDER == BIG_ENDIAN
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[sizeof(long) - 3]);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[sizeof(long) - 2]);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[sizeof(long) - 1]);
+#else
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[2]);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[1]);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ strOutput.push_back(pBuf[0]);
+#endif
+ } // while
+ if (pad) {
+ strOutput.resize(strOutput.size() - pad);
+ }
+
+ return 1;
+}
+
+template int B64Decode<std::vector<uint8_t>>(const std::string& strInput, std::vector<uint8_t>& strOutput);
+template int B64Decode<PacketBuffer>(const std::string& strInput, PacketBuffer& strOutput);
+template int B64Decode<std::string>(const std::string& strInput, std::string& strOutput);
+
+/*
+www.kbcafe.com
+Copyright 2001-2002 Randy Charles Morin
+The Encode static method takes an array of 8-bit values and returns a base-64 stream.
+*/
+
+std::string Base64Encode(const std::string& src)
+{
+ std::string retval;
+ if (src.empty()) {
+ return retval;
+ }
+ for (unsigned int i = 0; i < src.size(); i += 3) {
+ unsigned char by1 = 0;
+ unsigned char by2 = 0;
+ unsigned char by3 = 0;
+ by1 = src[i];
+ if (i + 1 < src.size()) {
+ by2 = src[i + 1];
+ };
+ if (i + 2 < src.size()) {
+ by3 = src[i + 2];
+ }
+ unsigned char by4 = 0;
+ unsigned char by5 = 0;
+ unsigned char by6 = 0;
+ unsigned char by7 = 0;
+ by4 = by1 >> 2;
+ by5 = ((by1 & 0x3) << 4) | (by2 >> 4);
+ by6 = ((by2 & 0xf) << 2) | (by3 >> 6);
+ by7 = by3 & 0x3f;
+ retval += B64Encode1(by4);
+ retval += B64Encode1(by5);
+ if (i + 1 < src.size()) {
+ retval += B64Encode1(by6);
+ }
+ else {
+ retval += "=";
+ };
+ if (i + 2 < src.size()) {
+ retval += B64Encode1(by7);
+ }
+ else {
+ retval += "=";
+ };
+ /* if ((i % (76 / 4 * 3)) == 0)
+ {
+ retval += "\r\n";
+ }*/
+ };
+ return retval;
+};
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#include "config.h"
+#include <array>
+#include <string>
+#include <cstdint>
+#include <cstring>
+
+#if defined(HAVE_LIBSODIUM)
+#include <sodium.h>
+#endif
+
+namespace dnsdist::crypto::authenticated
+{
+struct Nonce
+{
+ Nonce() = default;
+ Nonce(const Nonce&) = default;
+ Nonce(Nonce&&) = default;
+ Nonce& operator=(const Nonce&) = default;
+ Nonce& operator=(Nonce&&) = default;
+ ~Nonce() = default;
+
+ void init();
+ void merge(const Nonce& lower, const Nonce& higher);
+ void increment();
+
+#if defined(HAVE_LIBSODIUM)
+ std::array<unsigned char, crypto_secretbox_NONCEBYTES> value{};
+#elif defined(HAVE_LIBCRYPTO)
+ // IV is 96 bits
+ std::array<unsigned char, 12> value{};
+#else
+ std::array<unsigned char, 1> value{};
+#endif
+};
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce = true);
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce = true);
+std::string newKey(bool base64Encoded = true);
+bool isValidKey(const std::string& key);
+
+constexpr size_t getEncryptedSize(size_t plainTextSize)
+{
+#if defined(HAVE_LIBSODIUM)
+ return plainTextSize + crypto_secretbox_MACBYTES;
+#elif defined(HAVE_LIBCRYPTO)
+ return plainTextSize + 16;
+#else
+ return plainTextSize;
+#endif
+}
+}
bool ServiceDiscovery::addUpgradeableServer(std::shared_ptr<DownstreamState>& server, uint32_t interval, std::string poolAfterUpgrade, uint16_t dohSVCKey, bool keepAfterUpgrade)
{
- s_upgradeableBackends.lock()->push_back(std::make_shared<UpgradeableBackend>(UpgradeableBackend{server, poolAfterUpgrade, 0, interval, dohSVCKey, keepAfterUpgrade}));
+ s_upgradeableBackends.lock()->push_back(std::make_shared<UpgradeableBackend>(UpgradeableBackend{server, std::move(poolAfterUpgrade), 0, interval, dohSVCKey, keepAfterUpgrade}));
return true;
}
static bool parseSVCParams(const PacketBuffer& answer, std::map<uint16_t, DesignatedResolvers>& resolvers)
{
std::map<DNSName, std::vector<ComboAddress>> hints;
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(answer.data());
+ const dnsheader_aligned dh(answer.data());
PacketReader pr(std::string_view(reinterpret_cast<const char*>(answer.data()), answer.size()));
uint16_t qdcount = ntohs(dh->qdcount);
uint16_t ancount = ntohs(dh->ancount);
tempConfig.d_subjectName = resolver.target.toStringNoDot();
tempConfig.d_addr.sin4.sin_port = tempConfig.d_port;
- config = tempConfig;
+ config = std::move(tempConfig);
return true;
}
sock.writenWithTimeout(reinterpret_cast<const char*>(packet.data()), packet.size(), backend->d_config.tcpSendTimeout);
+ const struct timeval remainingTime = {.tv_sec = backend->d_config.tcpRecvTimeout, .tv_usec = 0};
uint16_t responseSize = 0;
- auto got = sock.readWithTimeout(reinterpret_cast<char*>(&responseSize), sizeof(responseSize), backend->d_config.tcpRecvTimeout);
- if (got < 0 || static_cast<size_t>(got) != sizeof(responseSize)) {
+ auto got = readn2WithTimeout(sock.getHandle(), &responseSize, sizeof(responseSize), remainingTime);
+ if (got != sizeof(responseSize)) {
if (g_verbose) {
warnlog("Error while waiting for the ADD upgrade response size from backend %s: %d", addr.toStringWithPort(), got);
}
packet.resize(ntohs(responseSize));
- got = sock.readWithTimeout(reinterpret_cast<char*>(packet.data()), packet.size(), backend->d_config.tcpRecvTimeout);
- if (got < 0 || static_cast<size_t>(got) != packet.size()) {
+ got = readn2WithTimeout(sock.getHandle(), packet.data(), packet.size(), remainingTime);
+ if (got != packet.size()) {
if (g_verbose) {
warnlog("Error while waiting for the ADD upgrade response from backend %s: %d", addr.toStringWithPort(), got);
}
+++ /dev/null
-../dnsdist-dnscrypt.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsdist-metrics.hh"
+#include "dnscrypt.hh"
+
+#ifdef HAVE_DNSCRYPT
+bool handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response)
+{
+ query.parsePacket(packet, tcp, now);
+
+ if (!query.isValid()) {
+ vinfolog("Dropping DNSCrypt invalid query");
+ return false;
+ }
+
+ if (!query.isEncrypted()) {
+ query.getCertificateResponse(now, response);
+
+ return false;
+ }
+
+ if (packet.size() < static_cast<uint16_t>(sizeof(struct dnsheader))) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ return false;
+ }
+
+ return true;
+}
+#endif
return true;
}
+namespace PacketMangling
+{
+ bool editDNSHeaderFromPacket(PacketBuffer& packet, const std::function<bool(dnsheader& header)>& editFunction)
+ {
+ if (packet.size() < sizeof(dnsheader)) {
+ throw std::runtime_error("Trying to edit the DNS header of a too small packet");
+ }
+
+ return editDNSHeaderFromRawPacket(packet.data(), editFunction);
+ }
+
+ bool editDNSHeaderFromRawPacket(void* packet, const std::function<bool(dnsheader& header)>& editFunction)
+ {
+ if (dnsheader_aligned::isMemoryAligned(packet)) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ auto* header = reinterpret_cast<dnsheader*>(packet);
+ return editFunction(*header);
+ }
+
+ dnsheader header{};
+ memcpy(&header, packet, sizeof(header));
+ if (!editFunction(header)) {
+ return false;
+ }
+ memcpy(packet, &header, sizeof(header));
+ return true;
+ }
+}
}
* because it could contain pointers that would not be rewritten.
*/
bool changeNameInDNSPacket(PacketBuffer& initialPacket, const DNSName& from, const DNSName& to);
+
+namespace PacketMangling
+{
+ bool editDNSHeaderFromPacket(PacketBuffer& packet, const std::function<bool(dnsheader& header)>& editFunction);
+ bool editDNSHeaderFromRawPacket(void* packet, const std::function<bool(dnsheader& header)>& editFunction);
+}
}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "base64.hh"
+#include "dnsdist-doh-common.hh"
+#include "dnsdist-rules.hh"
+
+#ifdef HAVE_DNS_OVER_HTTPS
+
+HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex) :
+ d_header(toLower(header)), d_regex(regex), d_visual("http[" + header + "] ~ " + regex)
+{
+}
+
+bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
+{
+ if (!dq->ids.du) {
+ return false;
+ }
+
+ const auto& headers = dq->ids.du->getHTTPHeaders();
+ for (const auto& header : headers) {
+ if (header.first == d_header) {
+ return d_regex.match(header.second);
+ }
+ }
+ return false;
+}
+
+string HTTPHeaderRule::toString() const
+{
+ return d_visual;
+}
+
+HTTPPathRule::HTTPPathRule(std::string path) :
+ d_path(std::move(path))
+{
+}
+
+bool HTTPPathRule::matches(const DNSQuestion* dq) const
+{
+ if (!dq->ids.du) {
+ return false;
+ }
+
+ const auto path = dq->ids.du->getHTTPPath();
+ return d_path == path;
+}
+
+string HTTPPathRule::toString() const
+{
+ return "url path == " + d_path;
+}
+
+HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex) :
+ d_regex(regex), d_visual("http path ~ " + regex)
+{
+}
+
+bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
+{
+ if (!dq->ids.du) {
+ return false;
+ }
+
+ return d_regex.match(dq->ids.du->getHTTPPath());
+}
+
+string HTTPPathRegexRule::toString() const
+{
+ return d_visual;
+}
+
+void DOHFrontend::rotateTicketsKey(time_t now)
+{
+ return d_tlsContext.rotateTicketsKey(now);
+}
+
+void DOHFrontend::loadTicketsKeys(const std::string& keyFile)
+{
+ return d_tlsContext.loadTicketsKeys(keyFile);
+}
+
+void DOHFrontend::handleTicketsKeyRotation()
+{
+}
+
+std::string DOHFrontend::getNextTicketsKeyRotation() const
+{
+ return d_tlsContext.getNextTicketsKeyRotation();
+}
+
+size_t DOHFrontend::getTicketsKeysCount()
+{
+ return d_tlsContext.getTicketsKeysCount();
+}
+
+void DOHFrontend::reloadCertificates()
+{
+ d_tlsContext.setupTLS();
+}
+
+void DOHFrontend::setup()
+{
+ if (isHTTPS()) {
+ if (!d_tlsContext.setupTLS()) {
+ throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_tlsContext.d_addr.toStringWithPort());
+ }
+ }
+}
+
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+namespace dnsdist::doh
+{
+std::optional<PacketBuffer> getPayloadFromPath(const std::string_view& path)
+{
+ std::optional<PacketBuffer> result{std::nullopt};
+
+ if (path.size() <= 5) {
+ return result;
+ }
+
+ auto pos = path.find("?dns=");
+ if (pos == string::npos) {
+ pos = path.find("&dns=");
+ }
+
+ if (pos == string::npos) {
+ return result;
+ }
+
+ // need to base64url decode this
+ string sdns;
+ const size_t payloadSize = path.size() - pos - 5;
+ size_t neededPadding = 0;
+ switch (payloadSize % 4) {
+ case 2:
+ neededPadding = 2;
+ break;
+ case 3:
+ neededPadding = 1;
+ break;
+ }
+ sdns.reserve(payloadSize + neededPadding);
+ sdns = path.substr(pos + 5);
+ for (auto& entry : sdns) {
+ switch (entry) {
+ case '-':
+ entry = '+';
+ break;
+ case '_':
+ entry = '/';
+ break;
+ }
+ }
+
+ if (neededPadding != 0) {
+ // re-add padding that may have been missing
+ sdns.append(neededPadding, '=');
+ }
+
+ PacketBuffer decoded;
+ /* rough estimate so we hopefully don't need a new allocation later */
+ /* We reserve at few additional bytes to be able to add EDNS later */
+ const size_t estimate = ((sdns.size() * 3) / 4);
+ decoded.reserve(estimate);
+ if (B64Decode(sdns, decoded) < 0) {
+ return result;
+ }
+
+ result = std::move(decoded);
+ return result;
+}
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <optional>
+#include <unordered_map>
+#include <set>
+#include <string_view>
+
+#include "config.h"
+#include "iputils.hh"
+#include "libssl.hh"
+#include "noinitvector.hh"
+#include "stat_t.hh"
+#include "tcpiohandler.hh"
+
+namespace dnsdist::doh
+{
+std::optional<PacketBuffer> getPayloadFromPath(const std::string_view& path);
+}
+
+struct DOHServerConfig;
+
+class DOHResponseMapEntry
+{
+public:
+ DOHResponseMapEntry(const std::string& regex, uint16_t status, const PacketBuffer& content, const boost::optional<std::unordered_map<std::string, std::string>>& headers) :
+ d_regex(regex), d_customHeaders(headers), d_content(content), d_status(status)
+ {
+ if (status >= 400 && !d_content.empty() && d_content.at(d_content.size() - 1) != 0) {
+ // we need to make sure it's null-terminated
+ d_content.push_back(0);
+ }
+ }
+
+ bool matches(const std::string& path) const
+ {
+ return d_regex.match(path);
+ }
+
+ uint16_t getStatusCode() const
+ {
+ return d_status;
+ }
+
+ const PacketBuffer& getContent() const
+ {
+ return d_content;
+ }
+
+ const boost::optional<std::unordered_map<std::string, std::string>>& getHeaders() const
+ {
+ return d_customHeaders;
+ }
+
+private:
+ Regex d_regex;
+ boost::optional<std::unordered_map<std::string, std::string>> d_customHeaders;
+ PacketBuffer d_content;
+ uint16_t d_status;
+};
+
+struct DOHFrontend
+{
+ DOHFrontend()
+ {
+ }
+ DOHFrontend(std::shared_ptr<TLSCtx> tlsCtx) :
+ d_tlsContext(std::move(tlsCtx))
+ {
+ }
+
+ virtual ~DOHFrontend()
+ {
+ }
+
+ std::shared_ptr<DOHServerConfig> d_dsc{nullptr};
+ std::shared_ptr<std::vector<std::shared_ptr<DOHResponseMapEntry>>> d_responsesMap;
+ TLSFrontend d_tlsContext{TLSFrontend::ALPN::DoH};
+ std::string d_serverTokens{"h2o/dnsdist"};
+ std::unordered_map<std::string, std::string> d_customResponseHeaders;
+ std::string d_library;
+
+ uint32_t d_idleTimeout{30}; // HTTP idle timeout in seconds
+ std::set<std::string, std::less<>> d_urls;
+
+ pdns::stat_t d_httpconnects{0}; // number of TCP/IP connections established
+ pdns::stat_t d_getqueries{0}; // valid DNS queries received via GET
+ pdns::stat_t d_postqueries{0}; // valid DNS queries received via POST
+ pdns::stat_t d_badrequests{0}; // request could not be converted to dns query
+ pdns::stat_t d_errorresponses{0}; // dnsdist set 'error' on response
+ pdns::stat_t d_redirectresponses{0}; // dnsdist set 'redirect' on response
+ pdns::stat_t d_validresponses{0}; // valid responses sent out
+
+ struct HTTPVersionStats
+ {
+ pdns::stat_t d_nbQueries{0}; // valid DNS queries received
+ pdns::stat_t d_nb200Responses{0};
+ pdns::stat_t d_nb400Responses{0};
+ pdns::stat_t d_nb403Responses{0};
+ pdns::stat_t d_nb500Responses{0};
+ pdns::stat_t d_nb502Responses{0};
+ pdns::stat_t d_nbOtherResponses{0};
+ };
+
+ HTTPVersionStats d_http1Stats;
+ HTTPVersionStats d_http2Stats;
+#ifdef __linux__
+ // On Linux this gives us 128k pending queries (default is 8192 queries),
+ // which should be enough to deal with huge spikes
+ uint32_t d_internalPipeBufferSize{1024 * 1024};
+#else
+ uint32_t d_internalPipeBufferSize{0};
+#endif
+ bool d_sendCacheControlHeaders{true};
+ bool d_trustForwardedForHeader{false};
+ bool d_earlyACLDrop{true};
+ /* whether we require tue query path to exactly match one of configured ones,
+ or accept everything below these paths. */
+ bool d_exactPathMatching{true};
+ bool d_keepIncomingHeaders{false};
+
+ time_t getTicketsKeyRotationDelay() const
+ {
+ return d_tlsContext.d_tlsConfig.d_ticketsKeyRotationDelay;
+ }
+
+ bool isHTTPS() const
+ {
+ return !d_tlsContext.d_tlsConfig.d_certKeyPairs.empty();
+ }
+
+#ifndef HAVE_DNS_OVER_HTTPS
+ virtual void setup()
+ {
+ }
+
+ virtual void reloadCertificates()
+ {
+ }
+
+ virtual void rotateTicketsKey(time_t /* now */)
+ {
+ }
+
+ virtual void loadTicketsKeys(const std::string& /* keyFile */)
+ {
+ }
+
+ virtual void handleTicketsKeyRotation()
+ {
+ }
+
+ virtual std::string getNextTicketsKeyRotation()
+ {
+ return std::string();
+ }
+
+ virtual size_t getTicketsKeysCount() const
+ {
+ size_t res = 0;
+ return res;
+ }
+
+#else
+ virtual void setup();
+ virtual void reloadCertificates();
+
+ virtual void rotateTicketsKey(time_t now);
+ virtual void loadTicketsKeys(const std::string& keyFile);
+ virtual void handleTicketsKeyRotation();
+ virtual std::string getNextTicketsKeyRotation() const;
+ virtual size_t getTicketsKeysCount();
+#endif /* HAVE_DNS_OVER_HTTPS */
+};
+
+#include "dnsdist-idstate.hh"
+
+struct DownstreamState;
+
+#ifndef HAVE_DNS_OVER_HTTPS
+struct DOHUnitInterface
+{
+ virtual ~DOHUnitInterface()
+ {
+ }
+ static void handleTimeout(std::unique_ptr<DOHUnitInterface>)
+ {
+ }
+
+ static void handleUDPResponse(std::unique_ptr<DOHUnitInterface>, PacketBuffer&&, InternalQueryState&&, const std::shared_ptr<DownstreamState>&)
+ {
+ }
+};
+#else /* HAVE_DNS_OVER_HTTPS */
+struct DOHUnitInterface
+{
+ virtual ~DOHUnitInterface()
+ {
+ }
+
+ virtual std::string getHTTPPath() const = 0;
+ virtual std::string getHTTPQueryString() const = 0;
+ virtual const std::string& getHTTPHost() const = 0;
+ virtual const std::string& getHTTPScheme() const = 0;
+ virtual const std::unordered_map<std::string, std::string>& getHTTPHeaders() const = 0;
+ virtual void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType = "") = 0;
+ virtual void handleTimeout() = 0;
+ virtual void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>&) = 0;
+
+ static void handleTimeout(std::unique_ptr<DOHUnitInterface> unit)
+ {
+ if (unit) {
+ unit->handleTimeout();
+ unit.release();
+ }
+ }
+
+ static void handleUDPResponse(std::unique_ptr<DOHUnitInterface> unit, PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& ds)
+ {
+ if (unit) {
+ unit->handleUDPResponse(std::move(response), std::move(state), ds);
+ unit.release();
+ }
+ }
+
+ std::shared_ptr<DownstreamState> downstream{nullptr};
+};
+#endif /* HAVE_DNS_OVER_HTTPS */
#include "dnsdist.hh"
#include "dnsdist-dynblocks.hh"
+#include "dnsdist-metrics.hh"
GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop;
#ifndef DISABLE_DYNBLOCKS
-
void DynBlockRulesGroup::apply(const struct timespec& now)
{
counts_t counts;
return;
}
- boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> > blocks;
+ boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>> blocks;
bool updated = false;
for (const auto& entry : counts) {
continue;
}
+ if (d_respCacheMissRatioRule.warningRatioExceeded(counters.responses, counters.cacheMisses)) {
+ handleWarning(blocks, now, requestor, d_respCacheMissRatioRule, updated);
+ continue;
+ }
+
+ if (d_respCacheMissRatioRule.ratioExceeded(counters.responses, counters.cacheMisses)) {
+ addBlock(blocks, now, requestor, d_respCacheMissRatioRule, updated);
+ continue;
+ }
+
for (const auto& pair : d_qtypeRules) {
const auto qtype = pair.first;
g_dynblockNMG.setState(std::move(*blocks));
}
- if (!statNodeRoot.empty()) {
- StatNode::Stat node;
- std::unordered_map<DNSName, std::optional<std::string>> namesToBlock;
- statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
- bool block = false;
- std::optional<std::string> reason;
-
- if (d_smtVisitorFFI) {
- dnsdist_ffi_stat_node_t tmp(*node_, self, children, reason);
- block = d_smtVisitorFFI(&tmp);
- }
- else {
- auto ret = d_smtVisitor(*node_, self, children);
- block = std::get<0>(ret);
- if (block) {
- if (boost::optional<std::string> tmp = std::get<1>(ret)) {
- reason = std::move(*tmp);
- }
- }
- }
-
- if (block) {
- namesToBlock.insert({DNSName(node_->fullname), std::move(reason)});
- }
- },
- node);
-
- if (!namesToBlock.empty()) {
- updated = false;
- SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
- for (auto& [name, reason] : namesToBlock) {
- if (reason) {
- DynBlockRule rule(d_suffixMatchRule);
- rule.d_blockReason = std::move(*reason);
- addOrRefreshBlockSMT(smtBlocks, now, std::move(name), std::move(rule), updated);
+ applySMT(now, statNodeRoot);
+}
+
+void DynBlockRulesGroup::applySMT(const struct timespec& now, StatNode& statNodeRoot)
+{
+ if (statNodeRoot.empty()) {
+ return;
+ }
+
+ bool updated = false;
+ StatNode::Stat node;
+ std::unordered_map<DNSName, SMTBlockParameters> namesToBlock;
+ statNodeRoot.visit([this, &namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
+ bool block = false;
+ SMTBlockParameters blockParameters;
+ if (d_smtVisitorFFI) {
+ dnsdist_ffi_stat_node_t tmp(*node_, self, children, blockParameters);
+ block = d_smtVisitorFFI(&tmp);
+ }
+ else {
+ auto ret = d_smtVisitor(*node_, self, children);
+ block = std::get<0>(ret);
+ if (block) {
+ if (boost::optional<std::string> tmp = std::get<1>(ret)) {
+ blockParameters.d_reason = std::move(*tmp);
}
- else {
- addOrRefreshBlockSMT(smtBlocks, now, std::move(name), d_suffixMatchRule, updated);
+ if (boost::optional<int> tmp = std::get<2>(ret)) {
+ blockParameters.d_action = static_cast<DNSAction::Action>(*tmp);
+ }
+ }
+ }
+ if (block) {
+ namesToBlock.insert({DNSName(node_->fullname), std::move(blockParameters)});
+ }
+ },
+ node);
+
+ if (!namesToBlock.empty()) {
+ updated = false;
+ SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
+ for (auto& [name, parameters] : namesToBlock) {
+ if (parameters.d_reason || parameters.d_action) {
+ DynBlockRule rule(d_suffixMatchRule);
+ if (parameters.d_reason) {
+ rule.d_blockReason = std::move(*parameters.d_reason);
+ }
+ if (parameters.d_action) {
+ rule.d_action = *parameters.d_action;
}
+ addOrRefreshBlockSMT(smtBlocks, now, name, rule, updated);
}
- if (updated) {
- g_dynblockSMT.setState(std::move(smtBlocks));
+ else {
+ addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated);
}
}
+ if (updated) {
+ g_dynblockSMT.setState(std::move(smtBlocks));
+ }
}
}
}
auto ratio = d_rcodeRatioRules.find(response.dh.rcode);
- if (ratio != d_rcodeRatioRules.end() && ratio->second.matches(response.when)) {
- return true;
- }
-
- return false;
+ return ratio != d_rcodeRatioRules.end() && ratio->second.matches(response.when);
}
/* return the actual action that will be taken by that block:
return g_dynBlockAction;
}
-void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+namespace dnsdist::DynamicBlocks
+{
+bool addOrRefreshBlock(NetmaskTree<DynBlock, AddressAndPortRange>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet)
{
- /* network exclusions are address-based only (no port) */
- if (d_excludedSubnets.match(requestor.getNetwork())) {
- /* do not add a block for excluded subnets */
- return;
- }
-
- if (!blocks) {
- blocks = g_dynblockNMG.getCopy();
- }
- struct timespec until = now;
- until.tv_sec += rule.d_blockDuration;
unsigned int count = 0;
- const auto& got = blocks->lookup(requestor);
bool expired = false;
bool wasWarning = false;
bool bpf = false;
+ struct timespec until = now;
+ until.tv_sec += duration;
+
+ DynBlock dblock{reason, until, DNSName(), warning ? DNSAction::Action::NoOp : action};
+ dblock.warning = warning;
- if (got) {
+ const auto& got = blocks.lookup(requestor);
+ if (got != nullptr) {
bpf = got->second.bpf;
if (warning && !got->second.warning) {
/* we have an existing entry which is not a warning,
don't override it */
- return;
+ return false;
}
- else if (!warning && got->second.warning) {
+ if (!warning && got->second.warning) {
wasWarning = true;
}
else {
if (until < got->second.until) {
// had a longer policy
- return;
+ return false;
}
}
}
}
- DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
- db.blocks = count;
- db.warning = warning;
- if (!got || expired || wasWarning) {
- const auto actualAction = getActualAction(db);
- if (g_defaultBPFFilter &&
- ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128)) &&
- (actualAction == DNSAction::Action::Drop || actualAction == DNSAction::Action::Truncate)) {
+ dblock.blocks = count;
+
+ if (got == nullptr || expired || wasWarning) {
+ const auto actualAction = getActualAction(dblock);
+ if (g_defaultBPFFilter && ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128)) && (actualAction == DNSAction::Action::Drop || actualAction == DNSAction::Action::Truncate)) {
try {
BPFFilter::MatchAction bpfAction = actualAction == DNSAction::Action::Drop ? BPFFilter::MatchAction::Drop : BPFFilter::MatchAction::Truncate;
if (g_defaultBPFFilter->supportsMatchAction(bpfAction)) {
}
}
- if (!d_beQuiet) {
- warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+ if (!beQuiet) {
+ warnlog("Inserting %s%sdynamic block for %s for %d seconds: %s", warning ? "(warning) " : "", bpf ? "eBPF " : "", requestor.toString(), duration, reason);
}
}
- db.bpf = bpf;
+ dblock.bpf = bpf;
- blocks->insert(requestor).second = std::move(db);
+ blocks.insert(requestor).second = std::move(dblock);
- updated = true;
+ return true;
}
-void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+bool addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet)
{
- if (d_excludedDomains.check(name)) {
- /* do not add a block for excluded domains */
- return;
- }
-
struct timespec until = now;
- until.tv_sec += rule.d_blockDuration;
+ until.tv_sec += duration;
unsigned int count = 0;
+ DynBlock dblock{reason, until, name.makeLowerCase(), action};
/* be careful, if you try to insert a longer suffix
lookup() might return a shorter one if it is
already in the tree as a final node */
const DynBlock* got = blocks.lookup(name);
- if (got && got->domain != name) {
+ if (got != nullptr && got->domain != name) {
got = nullptr;
}
bool expired = false;
- if (got) {
+ if (got != nullptr) {
if (until < got->until) {
// had a longer policy
- return;
+ return false;
}
if (now < got->until) {
}
}
- DynBlock db{rule.d_blockReason, until, name.makeLowerCase(), rule.d_action};
- db.blocks = count;
+ dblock.blocks = count;
- if (!d_beQuiet && (!got || expired)) {
- warnlog("Inserting dynamic block for %s for %d seconds: %s", name, rule.d_blockDuration, rule.d_blockReason);
+ if (!beQuiet && (got == nullptr || expired)) {
+ warnlog("Inserting dynamic block for %s for %d seconds: %s", name, duration, reason);
+ }
+ blocks.add(name, std::move(dblock));
+ return true;
+}
+}
+
+void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+{
+ /* network exclusions are address-based only (no port) */
+ if (d_excludedSubnets.match(requestor.getNetwork())) {
+ /* do not add a block for excluded subnets */
+ return;
+ }
+
+ if (!blocks) {
+ blocks = g_dynblockNMG.getCopy();
+ }
+
+ updated = dnsdist::DynamicBlocks::addOrRefreshBlock(*blocks, now, requestor, rule.d_blockReason, rule.d_blockDuration, rule.d_action, warning, d_beQuiet);
+ if (updated && d_newBlockHook) {
+ try {
+ d_newBlockHook(dnsdist_ffi_dynamic_block_type_nmt, requestor.toString().c_str(), rule.d_blockReason.c_str(), static_cast<uint8_t>(rule.d_action), rule.d_blockDuration, warning);
+ }
+ catch (const std::exception& exp) {
+ warnlog("Error calling the Lua hook after a dynamic block insertion: %s", exp.what());
+ }
+ }
+}
+
+void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+{
+ if (d_excludedDomains.check(name)) {
+ /* do not add a block for excluded domains */
+ return;
+ }
+
+ updated = dnsdist::DynamicBlocks::addOrRefreshBlockSMT(blocks, now, name, rule.d_blockReason, rule.d_blockDuration, rule.d_action, d_beQuiet);
+ if (updated && d_newBlockHook) {
+ try {
+ d_newBlockHook(dnsdist_ffi_dynamic_block_type_smt, name.toString().c_str(), rule.d_blockReason.c_str(), static_cast<uint8_t>(rule.d_action), rule.d_blockDuration, false);
+ }
+ catch (const std::exception& exp) {
+ warnlog("Error calling the Lua hook after a dynamic block insertion: %s", exp.what());
+ }
}
- blocks.add(name, std::move(db));
- updated = true;
}
void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timespec& now)
}
for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->queryRing.lock();
- for(const auto& c : *rl) {
- if (now < c.when) {
+ auto queryRing = shard->queryRing.lock();
+ for (const auto& ringEntry : *queryRing) {
+ if (now < ringEntry.when) {
continue;
}
- bool qRateMatches = d_queryRateRule.matches(c.when);
- bool typeRuleMatches = checkIfQueryTypeMatches(c);
+ bool qRateMatches = d_queryRateRule.matches(ringEntry.when);
+ bool typeRuleMatches = checkIfQueryTypeMatches(ringEntry);
if (qRateMatches || typeRuleMatches) {
- auto& entry = counts[AddressAndPortRange(c.requestor, c.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
+ auto& entry = counts[AddressAndPortRange(ringEntry.requestor, ringEntry.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
if (qRateMatches) {
++entry.queries;
}
if (typeRuleMatches) {
- ++entry.d_qtypeCounts[c.qtype];
+ ++entry.d_qtypeCounts[ringEntry.qtype];
}
}
}
responseCutOff = d_suffixMatchRule.d_cutOff;
}
+ d_respCacheMissRatioRule.d_cutOff = d_respCacheMissRatioRule.d_minTime = now;
+ d_respCacheMissRatioRule.d_cutOff.tv_sec -= d_respCacheMissRatioRule.d_seconds;
+ if (d_respCacheMissRatioRule.d_cutOff < responseCutOff) {
+ responseCutOff = d_respCacheMissRatioRule.d_cutOff;
+ }
+
for (auto& rule : d_rcodeRules) {
rule.second.d_cutOff = rule.second.d_minTime = now;
rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
}
for (const auto& shard : g_rings.d_shards) {
- auto rl = shard->respRing.lock();
- for(const auto& c : *rl) {
- if (now < c.when) {
+ auto responseRing = shard->respRing.lock();
+ for (const auto& ringEntry : *responseRing) {
+ if (now < ringEntry.when) {
continue;
}
- if (c.when < responseCutOff) {
+ if (ringEntry.when < responseCutOff) {
continue;
}
- auto& entry = counts[AddressAndPortRange(c.requestor, c.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
+ auto& entry = counts[AddressAndPortRange(ringEntry.requestor, ringEntry.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
++entry.responses;
- bool respRateMatches = d_respRateRule.matches(c.when);
- bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when);
- bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
+ bool respRateMatches = d_respRateRule.matches(ringEntry.when);
+ bool suffixMatchRuleMatches = d_suffixMatchRule.matches(ringEntry.when);
+ bool rcodeRuleMatches = checkIfResponseCodeMatches(ringEntry);
+ bool respCacheMissRatioRuleMatches = d_respCacheMissRatioRule.matches(ringEntry.when);
- if (respRateMatches || rcodeRuleMatches) {
- if (respRateMatches) {
- entry.respBytes += c.size;
- }
- if (rcodeRuleMatches) {
- ++entry.d_rcodeCounts[c.dh.rcode];
- }
+ if (respRateMatches) {
+ entry.respBytes += ringEntry.size;
+ }
+ if (rcodeRuleMatches) {
+ ++entry.d_rcodeCounts[ringEntry.dh.rcode];
+ }
+ if (respCacheMissRatioRuleMatches && !ringEntry.isACacheHit()) {
+ ++entry.cacheMisses;
}
if (suffixMatchRuleMatches) {
- bool hit = c.ds.sin4.sin_family == 0;
- if (!hit && c.ds.isIPv4() && c.ds.sin4.sin_addr.s_addr == 0 && c.ds.sin4.sin_port == 0) {
- hit = true;
- }
-
- root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, hit, boost::none);
+ const bool hit = ringEntry.isACacheHit();
+ root.submit(ringEntry.name, ((ringEntry.dh.rcode == 0 && ringEntry.usec == std::numeric_limits<unsigned int>::max()) ? -1 : ringEntry.dh.rcode), ringEntry.size, hit, boost::none);
}
}
}
void DynBlockMaintenance::purgeExpired(const struct timespec& now)
{
+ // we need to increase the dynBlocked counter when removing
+ // eBPF blocks, as otherwise it does not get incremented for these
+ // since the block happens in kernel space.
+ uint64_t bpfBlocked = 0;
{
auto blocks = g_dynblockNMG.getLocal();
std::vector<AddressAndPortRange> toRemove;
if (!(now < entry.second.until)) {
toRemove.push_back(entry.first);
if (g_defaultBPFFilter && entry.second.bpf) {
+ const auto& network = entry.first.getNetwork();
+ try {
+ bpfBlocked += g_defaultBPFFilter->getHits(network);
+ }
+ catch (const std::exception& e) {
+ vinfolog("Error while getting block count before removing eBPF dynamic block for %s: %s", entry.first.toString(), e.what());
+ }
try {
- g_defaultBPFFilter->unblock(entry.first.getNetwork());
+ g_defaultBPFFilter->unblock(network);
}
catch (const std::exception& e) {
vinfolog("Error while removing eBPF dynamic block for %s: %s", entry.first.toString(), e.what());
updated.erase(entry);
}
g_dynblockNMG.setState(std::move(updated));
+ dnsdist::metrics::g_stats.dynBlocked += bpfBlocked;
}
}
topsForReason.pop_front();
}
- topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& a, const std::pair<AddressAndPortRange, unsigned int>& b) {
- return a.second < b.second;
- }),
- newEntry);
+ topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& rhs, const std::pair<AddressAndPortRange, unsigned int>& lhs) {
+ return rhs.second < lhs.second;
+ }),
+ newEntry);
}
}
topsForReason.pop_front();
}
- topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) {
- return a.second < b.second;
- }),
- newEntry);
+ topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& rhs, const std::pair<DNSName, unsigned int>& lhs) {
+ return rhs.second < lhs.second;
+ }),
+ newEntry);
}
});
struct DynBlockEntryStat
{
- size_t sum;
+ size_t sum{0};
unsigned int lastSeenValue{0};
};
}
/* do NMG */
- std::map<std::string, std::map<AddressAndPortRange, DynBlockEntryStat>> nm;
+ std::map<std::string, std::map<AddressAndPortRange, DynBlockEntryStat>> netmasks;
for (const auto& reason : s_metricsData.front().nmgData) {
- auto& reasonStat = nm[reason.first];
+ auto& reasonStat = netmasks[reason.first];
/* prepare the counters by scanning the oldest entry (N+1) */
for (const auto& entry : reason.second) {
continue;
}
- auto& nmgData = snap.nmgData;
+ const auto& nmgData = snap.nmgData;
for (const auto& reason : nmgData) {
- auto& reasonStat = nm[reason.first];
+ auto& reasonStat = netmasks[reason.first];
for (const auto& entry : reason.second) {
auto& stat = reasonStat[entry.first];
if (entry.second < stat.lastSeenValue) {
/* now we need to get the top N entries (for each "reason") based on our counters (sum of the last N entries) */
std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGs;
{
- for (const auto& reason : nm) {
+ for (const auto& reason : netmasks) {
auto& topsForReason = topNMGs[reason.first];
for (const auto& entry : reason.second) {
if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.sum) {
/* Note that this is a gauge, so we need to divide by the number of elapsed seconds */
- auto newEntry = std::pair<AddressAndPortRange, unsigned int>(entry.first, std::round(entry.second.sum / 60.0));
+ auto newEntry = std::pair<AddressAndPortRange, unsigned int>(entry.first, std::round(static_cast<double>(entry.second.sum) / 60.0));
if (topsForReason.size() >= s_topN) {
topsForReason.pop_front();
}
- topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& a, const std::pair<AddressAndPortRange, unsigned int>& b) {
- return a.second < b.second;
- }),
- newEntry);
+ topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& rhs, const std::pair<AddressAndPortRange, unsigned int>& lhs) {
+ return rhs.second < lhs.second;
+ }),
+ newEntry);
}
}
}
continue;
}
- auto& smtData = snap.smtData;
+ const auto& smtData = snap.smtData;
for (const auto& reason : smtData) {
auto& reasonStat = smt[reason.first];
for (const auto& entry : reason.second) {
for (const auto& entry : reason.second) {
if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.sum) {
/* Note that this is a gauge, so we need to divide by the number of elapsed seconds */
- auto newEntry = std::pair<DNSName, unsigned int>(entry.first, std::round(entry.second.sum / 60.0));
+ auto newEntry = std::pair<DNSName, unsigned int>(entry.first, std::round(static_cast<double>(entry.second.sum) / 60.0));
if (topsForReason.size() >= s_topN) {
topsForReason.pop_front();
}
- topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) {
- return a.second < b.second;
- }),
- newEntry);
+ topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& lhs, const std::pair<DNSName, unsigned int>& rhs) {
+ return lhs.second < rhs.second;
+ }),
+ newEntry);
}
}
}
sleepDelay = std::min(sleepDelay, (nextMetricsGeneration - now));
// coverity[store_truncates_time_t]
- sleep(sleepDelay);
+ std::this_thread::sleep_for(std::chrono::seconds(sleepDelay));
try {
now = time(nullptr);
}
if (s_expiredDynBlocksPurgeInterval > 0 && now >= nextExpiredPurge) {
- struct timespec tspec;
+ struct timespec tspec
+ {
+ };
gettime(&tspec);
purgeExpired(tspec);
{
return s_tops.lock()->topSMTsByReason;
}
+
+std::string DynBlockRulesGroup::DynBlockRule::toString() const
+{
+ if (!isEnabled()) {
+ return "";
+ }
+
+ std::stringstream result;
+ if (d_action != DNSAction::Action::None) {
+ result << DNSAction::typeToString(d_action) << " ";
+ }
+ else {
+ result << "Apply the global DynBlock action ";
+ }
+ result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
+
+ return result.str();
+}
+
+bool DynBlockRulesGroup::DynBlockRule::matches(const struct timespec& when)
+{
+ if (!d_enabled) {
+ return false;
+ }
+
+ if (d_seconds > 0 && when < d_cutOff) {
+ return false;
+ }
+
+ if (when < d_minTime) {
+ d_minTime = when;
+ }
+
+ return true;
+}
+
+bool DynBlockRulesGroup::DynBlockRule::rateExceeded(unsigned int count, const struct timespec& now) const
+{
+ if (!d_enabled) {
+ return false;
+ }
+
+ double delta = d_seconds > 0 ? d_seconds : DiffTime(now, d_minTime);
+ double limit = delta * d_rate;
+ return (count > limit);
+}
+
+bool DynBlockRulesGroup::DynBlockRule::warningRateExceeded(unsigned int count, const struct timespec& now) const
+{
+ if (!d_enabled) {
+ return false;
+ }
+
+ if (d_warningRate == 0) {
+ return false;
+ }
+
+ double delta = d_seconds > 0 ? d_seconds : DiffTime(now, d_minTime);
+ double limit = delta * d_warningRate;
+ return (count > limit);
+}
+
+bool DynBlockRulesGroup::DynBlockRatioRule::ratioExceeded(unsigned int total, unsigned int count) const
+{
+ if (!d_enabled) {
+ return false;
+ }
+
+ if (total < d_minimumNumberOfResponses) {
+ return false;
+ }
+
+ double allowed = d_ratio * static_cast<double>(total);
+ return (count > allowed);
+}
+
+bool DynBlockRulesGroup::DynBlockRatioRule::warningRatioExceeded(unsigned int total, unsigned int count) const
+{
+ if (!d_enabled) {
+ return false;
+ }
+
+ if (d_warningRatio == 0.0) {
+ return false;
+ }
+
+ if (total < d_minimumNumberOfResponses) {
+ return false;
+ }
+
+ double allowed = d_warningRatio * static_cast<double>(total);
+ return (count > allowed);
+}
+
+std::string DynBlockRulesGroup::DynBlockRatioRule::toString() const
+{
+ if (!isEnabled()) {
+ return "";
+ }
+
+ std::stringstream result;
+ if (d_action != DNSAction::Action::None) {
+ result << DNSAction::typeToString(d_action) << " ";
+ }
+ else {
+ result << "Apply the global DynBlock action ";
+ }
+ result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
+
+ return result.str();
+}
+
+bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::checkGlobalCacheHitRatio() const
+{
+ auto globalMisses = dnsdist::metrics::g_stats.cacheMisses.load();
+ auto globalHits = dnsdist::metrics::g_stats.cacheHits.load();
+ if (globalMisses == 0 || globalHits == 0) {
+ return false;
+ }
+ double globalCacheHitRatio = static_cast<double>(globalHits) / static_cast<double>(globalHits + globalMisses);
+ return globalCacheHitRatio >= d_minimumGlobalCacheHitRatio;
+}
+
+bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::ratioExceeded(unsigned int total, unsigned int count) const
+{
+ if (!DynBlockRulesGroup::DynBlockRatioRule::ratioExceeded(total, count)) {
+ return false;
+ }
+
+ return checkGlobalCacheHitRatio();
+}
+
+bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::warningRatioExceeded(unsigned int total, unsigned int count) const
+{
+ if (!DynBlockRulesGroup::DynBlockRatioRule::warningRatioExceeded(total, count)) {
+ return false;
+ }
+
+ return checkGlobalCacheHitRatio();
+}
+
+std::string DynBlockRulesGroup::DynBlockCacheMissRatioRule::toString() const
+{
+ if (!isEnabled()) {
+ return "";
+ }
+
+ std::stringstream result;
+ if (d_action != DNSAction::Action::None) {
+ result << DNSAction::typeToString(d_action) << " ";
+ }
+ else {
+ result << "Apply the global DynBlock action ";
+ }
+ result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, with a global cache-hit ratio of at least " << d_minimumGlobalCacheHitRatio << ", reason: '" << d_blockReason << "'";
+
+ return result.str();
+}
+
#endif /* DISABLE_DYNBLOCKS */
+++ /dev/null
-../dnsdist-dynblocks.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#ifndef DISABLE_DYNBLOCKS
+#include <unordered_set>
+
+#include "dolog.hh"
+#include "dnsdist-rings.hh"
+#include "statnode.hh"
+
+extern "C"
+{
+#include "dnsdist-lua-inspection-ffi.h"
+}
+
+// dnsdist_ffi_stat_node_t is a lightuserdata
+template <>
+struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*>
+{
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept
+ {
+ lua_pushlightuserdata(state, ptr);
+ return PushedObject{state, 1};
+ }
+};
+
+using dnsdist_ffi_stat_node_visitor_t = std::function<bool(dnsdist_ffi_stat_node_t*)>;
+
+struct SMTBlockParameters
+{
+ std::optional<std::string> d_reason;
+ std::optional<DNSAction::Action> d_action;
+};
+
+struct dnsdist_ffi_stat_node_t
+{
+ dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, SMTBlockParameters& blockParameters) :
+ node(node_), self(self_), children(children_), d_blockParameters(blockParameters)
+ {
+ }
+
+ const StatNode& node;
+ const StatNode::Stat& self;
+ const StatNode::Stat& children;
+ SMTBlockParameters& d_blockParameters;
+};
+
+using dnsdist_ffi_dynamic_block_inserted_hook = std::function<void(uint8_t type, const char* key, const char* reason, uint8_t action, uint64_t duration, bool warning)>;
+
+class DynBlockRulesGroup
+{
+private:
+ struct Counts
+ {
+ std::map<uint8_t, uint64_t> d_rcodeCounts;
+ std::map<uint16_t, uint64_t> d_qtypeCounts;
+ uint64_t queries{0};
+ uint64_t responses{0};
+ uint64_t respBytes{0};
+ uint64_t cacheMisses{0};
+ };
+
+ struct DynBlockRule
+ {
+ DynBlockRule() = default;
+ DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action) :
+ d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true)
+ {
+ }
+
+ bool matches(const struct timespec& when);
+ bool rateExceeded(unsigned int count, const struct timespec& now) const;
+ bool warningRateExceeded(unsigned int count, const struct timespec& now) const;
+
+ bool isEnabled() const
+ {
+ return d_enabled;
+ }
+
+ std::string toString() const;
+
+ std::string d_blockReason;
+ struct timespec d_cutOff;
+ struct timespec d_minTime;
+ unsigned int d_blockDuration{0};
+ unsigned int d_rate{0};
+ unsigned int d_warningRate{0};
+ unsigned int d_seconds{0};
+ DNSAction::Action d_action{DNSAction::Action::None};
+ bool d_enabled{false};
+ };
+
+ struct DynBlockRatioRule : DynBlockRule
+ {
+ DynBlockRatioRule() = default;
+ DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses) :
+ DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio)
+ {
+ }
+
+ bool ratioExceeded(unsigned int total, unsigned int count) const;
+ bool warningRatioExceeded(unsigned int total, unsigned int count) const;
+ std::string toString() const;
+
+ size_t d_minimumNumberOfResponses{0};
+ double d_ratio{0.0};
+ double d_warningRatio{0.0};
+ };
+
+ struct DynBlockCacheMissRatioRule : public DynBlockRatioRule
+ {
+ DynBlockCacheMissRatioRule() = default;
+ DynBlockCacheMissRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio) :
+ DynBlockRatioRule(blockReason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses), d_minimumGlobalCacheHitRatio(minimumGlobalCacheHitRatio)
+ {
+ }
+
+ bool checkGlobalCacheHitRatio() const;
+ bool ratioExceeded(unsigned int total, unsigned int count) const;
+ bool warningRatioExceeded(unsigned int total, unsigned int count) const;
+ std::string toString() const;
+
+ double d_minimumGlobalCacheHitRatio{0.0};
+ };
+
+ using counts_t = std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash>;
+
+public:
+ DynBlockRulesGroup()
+ {
+ }
+
+ void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
+ {
+ d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+ }
+
+ /* rate is in bytes per second */
+ void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
+ {
+ d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+ }
+
+ void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
+ {
+ auto& entry = d_rcodeRules[rcode];
+ entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+ }
+
+ void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
+ {
+ auto& entry = d_rcodeRatioRules[rcode];
+ entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses);
+ }
+
+ void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
+ {
+ auto& entry = d_qtypeRules[qtype];
+ entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+ }
+
+ void setCacheMissRatio(double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio)
+ {
+ d_respCacheMissRatioRule = DynBlockCacheMissRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses, minimumGlobalCacheHitRatio);
+ }
+
+ using smtVisitor_t = std::function<std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
+
+ void setSuffixMatchRule(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor)
+ {
+ d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
+ d_smtVisitor = std::move(visitor);
+ }
+
+ void setSuffixMatchRuleFFI(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor)
+ {
+ d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
+ d_smtVisitorFFI = std::move(visitor);
+ }
+
+ void setNewBlockHook(const dnsdist_ffi_dynamic_block_inserted_hook& callback)
+ {
+ d_newBlockHook = callback;
+ }
+
+ void setMasks(uint8_t v4, uint8_t v6, uint8_t port)
+ {
+ d_v4Mask = v4;
+ d_v6Mask = v6;
+ d_portMask = port;
+ }
+
+ void apply()
+ {
+ struct timespec now;
+ gettime(&now);
+
+ apply(now);
+ }
+
+ void apply(const struct timespec& now);
+
+ void excludeRange(const Netmask& range)
+ {
+ d_excludedSubnets.addMask(range);
+ }
+
+ void excludeRange(const NetmaskGroup& group)
+ {
+ d_excludedSubnets.addMasks(group, true);
+ }
+
+ void includeRange(const Netmask& range)
+ {
+ d_excludedSubnets.addMask(range, false);
+ }
+
+ void includeRange(const NetmaskGroup& group)
+ {
+ d_excludedSubnets.addMasks(group, false);
+ }
+
+ void removeRange(const Netmask& range)
+ {
+ d_excludedSubnets.deleteMask(range);
+ }
+
+ void removeRange(const NetmaskGroup& group)
+ {
+ d_excludedSubnets.deleteMasks(group);
+ }
+
+ void excludeDomain(const DNSName& domain)
+ {
+ d_excludedDomains.add(domain);
+ }
+
+ std::string toString() const
+ {
+ std::stringstream result;
+
+ result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
+ result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
+ result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl;
+ result << "Response cache-miss ratio rule: " << d_respCacheMissRatioRule.toString() << std::endl;
+ result << "RCode rules: " << std::endl;
+ for (const auto& rule : d_rcodeRules) {
+ result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
+ }
+ for (const auto& rule : d_rcodeRatioRules) {
+ result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
+ }
+ result << "QType rules: " << std::endl;
+ for (const auto& rule : d_qtypeRules) {
+ result << "- " << QType(rule.first).toString() << ": " << rule.second.toString() << std::endl;
+ }
+ result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
+ result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl;
+
+ return result.str();
+ }
+
+ void setQuiet(bool quiet)
+ {
+ d_beQuiet = quiet;
+ }
+
+private:
+ void applySMT(const struct timespec& now, StatNode& statNodeRoot);
+ bool checkIfQueryTypeMatches(const Rings::Query& query);
+ bool checkIfResponseCodeMatches(const Rings::Response& response);
+ void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning);
+ void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
+
+ void addBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
+ {
+ addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
+ }
+
+ void handleWarning(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
+ {
+ addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
+ }
+
+ bool hasQueryRules() const
+ {
+ return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
+ }
+
+ bool hasResponseRules() const
+ {
+ return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty() || d_respCacheMissRatioRule.isEnabled();
+ }
+
+ bool hasSuffixMatchRules() const
+ {
+ return d_suffixMatchRule.isEnabled();
+ }
+
+ bool hasRules() const
+ {
+ return hasQueryRules() || hasResponseRules();
+ }
+
+ void processQueryRules(counts_t& counts, const struct timespec& now);
+ void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
+
+ std::map<uint8_t, DynBlockRule> d_rcodeRules;
+ std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
+ std::map<uint16_t, DynBlockRule> d_qtypeRules;
+ DynBlockRule d_queryRateRule;
+ DynBlockRule d_respRateRule;
+ DynBlockRule d_suffixMatchRule;
+ DynBlockCacheMissRatioRule d_respCacheMissRatioRule;
+ NetmaskGroup d_excludedSubnets;
+ SuffixMatchNode d_excludedDomains;
+ smtVisitor_t d_smtVisitor;
+ dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
+ dnsdist_ffi_dynamic_block_inserted_hook d_newBlockHook;
+ uint8_t d_v6Mask{128};
+ uint8_t d_v4Mask{32};
+ uint8_t d_portMask{0};
+ bool d_beQuiet{false};
+};
+
+class DynBlockMaintenance
+{
+public:
+ static void run();
+
+ /* return the (cached) number of hits per second for the top offenders, averaged over 60s */
+ static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getHitsForTopNetmasks();
+ static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getHitsForTopSuffixes();
+
+ /* get the the top offenders based on the current value of the counters */
+ static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getTopNetmasks(size_t topN);
+ static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes(size_t topN);
+ static void purgeExpired(const struct timespec& now);
+
+ static time_t s_expiredDynBlocksPurgeInterval;
+
+private:
+ static void collectMetrics();
+ static void generateMetrics();
+
+ struct MetricsSnapshot
+ {
+ std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> nmgData;
+ std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> smtData;
+ };
+
+ struct Tops
+ {
+ std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGsByReason;
+ std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> topSMTsByReason;
+ };
+
+ static LockGuarded<Tops> s_tops;
+ /* s_metricsData should only be accessed by the dynamic blocks maintenance thread so it does not need a lock */
+ // need N+1 datapoints to be able to do the diff after a collection point has been reached
+ static std::list<MetricsSnapshot> s_metricsData;
+ static size_t s_topN;
+};
+
+namespace dnsdist::DynamicBlocks
+{
+bool addOrRefreshBlock(NetmaskTree<DynBlock, AddressAndPortRange>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet);
+bool addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet);
+}
+#endif /* DISABLE_DYNBLOCKS */
+++ /dev/null
-../dnsdist-dynbpf.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dnsdist-dynbpf.hh"
+
+bool DynBPFFilter::block(const ComboAddress& addr, const struct timespec& until)
+{
+ bool inserted = false;
+ auto data = d_data.lock();
+
+ if (data->d_excludedSubnets.match(addr)) {
+ /* do not add a block for excluded subnets */
+ return inserted;
+ }
+
+ auto entriesIt = data->d_entries.find(addr);
+ if (entriesIt != data->d_entries.end()) {
+ if (entriesIt->d_until < until) {
+ data->d_entries.replace(entriesIt, BlockEntry(addr, until));
+ }
+ }
+ else {
+ data->d_bpf->block(addr, BPFFilter::MatchAction::Drop);
+ data->d_entries.insert(BlockEntry(addr, until));
+ inserted = true;
+ }
+ return inserted;
+}
+
+void DynBPFFilter::purgeExpired(const struct timespec& now)
+{
+ auto data = d_data.lock();
+
+ using ordered_until = boost::multi_index::nth_index<container_t, 1>::type;
+ ordered_until& orderedUntilIndex = boost::multi_index::get<1>(data->d_entries);
+
+ for (auto orderedUntilIt = orderedUntilIndex.begin(); orderedUntilIt != orderedUntilIndex.end();) {
+ if (orderedUntilIt->d_until < now) {
+ ComboAddress addr = orderedUntilIt->d_addr;
+ orderedUntilIt = orderedUntilIndex.erase(orderedUntilIt);
+ data->d_bpf->unblock(addr);
+ }
+ else {
+ break;
+ }
+ }
+}
+
+std::vector<std::tuple<ComboAddress, uint64_t, struct timespec>> DynBPFFilter::getAddrStats()
+{
+ std::vector<std::tuple<ComboAddress, uint64_t, struct timespec>> result;
+ auto data = d_data.lock();
+
+ if (!data->d_bpf) {
+ return result;
+ }
+
+ const auto& stats = data->d_bpf->getAddrStats();
+ result.reserve(stats.size());
+ for (const auto& stat : stats) {
+ const auto entriesIt = data->d_entries.find(stat.first);
+ if (entriesIt != data->d_entries.end()) {
+ result.emplace_back(stat.first, stat.second, entriesIt->d_until);
+ }
+ }
+ return result;
+}
+++ /dev/null
-../dnsdist-dynbpf.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#include "config.h"
+
+#include "bpf-filter.hh"
+#include "iputils.hh"
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/member.hpp>
+
+class DynBPFFilter
+{
+public:
+ DynBPFFilter(std::shared_ptr<BPFFilter>& bpf)
+ {
+ d_data.lock()->d_bpf = bpf;
+ }
+ ~DynBPFFilter()
+ {
+ }
+ void excludeRange(const Netmask& range)
+ {
+ d_data.lock()->d_excludedSubnets.addMask(range);
+ }
+ void includeRange(const Netmask& range)
+ {
+ d_data.lock()->d_excludedSubnets.addMask(range, false);
+ }
+ /* returns true if the addr wasn't already blocked, false otherwise */
+ bool block(const ComboAddress& addr, const struct timespec& until);
+ void purgeExpired(const struct timespec& now);
+ std::vector<std::tuple<ComboAddress, uint64_t, struct timespec>> getAddrStats();
+
+private:
+ struct BlockEntry
+ {
+ BlockEntry(const ComboAddress& addr, const struct timespec until) :
+ d_addr(addr), d_until(until)
+ {
+ }
+ ComboAddress d_addr;
+ struct timespec d_until;
+ };
+ typedef boost::multi_index_container<BlockEntry,
+ boost::multi_index::indexed_by<
+ boost::multi_index::ordered_unique<boost::multi_index::member<BlockEntry, ComboAddress, &BlockEntry::d_addr>, ComboAddress::addressOnlyLessThan>,
+ boost::multi_index::ordered_non_unique<boost::multi_index::member<BlockEntry, struct timespec, &BlockEntry::d_until>>>>
+ container_t;
+ struct Data
+ {
+ container_t d_entries;
+ std::shared_ptr<BPFFilter> d_bpf{nullptr};
+ NetmaskGroup d_excludedSubnets;
+ };
+ LockGuarded<Data> d_data;
+};
+++ /dev/null
-../dnsdist-ecs.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsparser.hh"
+#include "dnswriter.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+
+/* when we add EDNS to a query, we don't want to advertise
+ a large buffer size */
+size_t g_EdnsUDPPayloadSize = 512;
+static const uint16_t defaultPayloadSizeSelfGenAnswers = 1232;
+static_assert(defaultPayloadSizeSelfGenAnswers < s_udpIncomingBufferSize, "The UDP responder's payload size should be smaller or equal to our incoming buffer size");
+uint16_t g_PayloadSizeSelfGenAnswers{defaultPayloadSizeSelfGenAnswers};
+
+/* draft-ietf-dnsop-edns-client-subnet-04 "11.1. Privacy" */
+uint16_t g_ECSSourcePrefixV4 = 24;
+uint16_t g_ECSSourcePrefixV6 = 56;
+
+bool g_ECSOverride{false};
+bool g_addEDNSToSelfGeneratedResponses{true};
+
+int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent)
+{
+ assert(initialPacket.size() >= sizeof(dnsheader));
+ const dnsheader_aligned dnsHeader(initialPacket.data());
+
+ if (ntohs(dnsHeader->arcount) == 0) {
+ return ENOENT;
+ }
+
+ if (ntohs(dnsHeader->qdcount) == 0) {
+ return ENOENT;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ PacketReader packetReader(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+
+ size_t idx = 0;
+ uint16_t qdcount = ntohs(dnsHeader->qdcount);
+ uint16_t ancount = ntohs(dnsHeader->ancount);
+ uint16_t nscount = ntohs(dnsHeader->nscount);
+ uint16_t arcount = ntohs(dnsHeader->arcount);
+ string blob;
+ dnsrecordheader recordHeader{};
+
+ auto rrname = packetReader.getName();
+ auto rrtype = packetReader.get16BitInt();
+ auto rrclass = packetReader.get16BitInt();
+
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(newContent, rrname, rrtype, rrclass, dnsHeader->opcode);
+ packetWriter.getHeader()->id = dnsHeader->id;
+ packetWriter.getHeader()->qr = dnsHeader->qr;
+ packetWriter.getHeader()->aa = dnsHeader->aa;
+ packetWriter.getHeader()->tc = dnsHeader->tc;
+ packetWriter.getHeader()->rd = dnsHeader->rd;
+ packetWriter.getHeader()->ra = dnsHeader->ra;
+ packetWriter.getHeader()->ad = dnsHeader->ad;
+ packetWriter.getHeader()->cd = dnsHeader->cd;
+ packetWriter.getHeader()->rcode = dnsHeader->rcode;
+
+ /* consume remaining qd if any */
+ if (qdcount > 1) {
+ for (idx = 1; idx < qdcount; idx++) {
+ rrname = packetReader.getName();
+ rrtype = packetReader.get16BitInt();
+ rrclass = packetReader.get16BitInt();
+ (void)rrtype;
+ (void)rrclass;
+ }
+ }
+
+ /* copy AN and NS */
+ for (idx = 0; idx < ancount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ANSWER, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+
+ for (idx = 0; idx < nscount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::AUTHORITY, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+ /* consume AR, looking for OPT */
+ for (idx = 0; idx < arcount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ if (recordHeader.d_type != QType::OPT) {
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ADDITIONAL, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+ else {
+
+ packetReader.skip(recordHeader.d_clen);
+ }
+ }
+ packetWriter.commit();
+
+ return 0;
+}
+
+static bool addOrReplaceEDNSOption(std::vector<std::pair<uint16_t, std::string>>& options, uint16_t optionCode, bool& optionAdded, bool overrideExisting, const string& newOptionContent)
+{
+ for (auto it = options.begin(); it != options.end();) {
+ if (it->first == optionCode) {
+ optionAdded = false;
+
+ if (!overrideExisting) {
+ return false;
+ }
+
+ it = options.erase(it);
+ }
+ else {
+ ++it;
+ }
+ }
+
+ options.emplace_back(optionCode, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)));
+ return true;
+}
+
+bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent)
+{
+ assert(initialPacket.size() >= sizeof(dnsheader));
+ const dnsheader_aligned dnsHeader(initialPacket.data());
+
+ if (ntohs(dnsHeader->qdcount) == 0) {
+ return false;
+ }
+
+ if (ntohs(dnsHeader->ancount) == 0 && ntohs(dnsHeader->nscount) == 0 && ntohs(dnsHeader->arcount) == 0) {
+ throw std::runtime_error("slowRewriteEDNSOptionInQueryWithRecords should not be called for queries that have no records");
+ }
+
+ optionAdded = false;
+ ednsAdded = true;
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ PacketReader packetReader(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+
+ size_t idx = 0;
+ uint16_t qdcount = ntohs(dnsHeader->qdcount);
+ uint16_t ancount = ntohs(dnsHeader->ancount);
+ uint16_t nscount = ntohs(dnsHeader->nscount);
+ uint16_t arcount = ntohs(dnsHeader->arcount);
+ string blob;
+ dnsrecordheader recordHeader{};
+
+ auto rrname = packetReader.getName();
+ auto rrtype = packetReader.get16BitInt();
+ auto rrclass = packetReader.get16BitInt();
+
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(newContent, rrname, rrtype, rrclass, dnsHeader->opcode);
+ packetWriter.getHeader()->id = dnsHeader->id;
+ packetWriter.getHeader()->qr = dnsHeader->qr;
+ packetWriter.getHeader()->aa = dnsHeader->aa;
+ packetWriter.getHeader()->tc = dnsHeader->tc;
+ packetWriter.getHeader()->rd = dnsHeader->rd;
+ packetWriter.getHeader()->ra = dnsHeader->ra;
+ packetWriter.getHeader()->ad = dnsHeader->ad;
+ packetWriter.getHeader()->cd = dnsHeader->cd;
+ packetWriter.getHeader()->rcode = dnsHeader->rcode;
+
+ /* consume remaining qd if any */
+ if (qdcount > 1) {
+ for (idx = 1; idx < qdcount; idx++) {
+ rrname = packetReader.getName();
+ rrtype = packetReader.get16BitInt();
+ rrclass = packetReader.get16BitInt();
+ (void)rrtype;
+ (void)rrclass;
+ }
+ }
+
+ /* copy AN and NS */
+ for (idx = 0; idx < ancount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ANSWER, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+
+ for (idx = 0; idx < nscount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::AUTHORITY, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+
+ /* consume AR, looking for OPT */
+ for (idx = 0; idx < arcount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ if (recordHeader.d_type != QType::OPT) {
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ADDITIONAL, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+ else {
+
+ ednsAdded = false;
+ packetReader.xfrBlob(blob);
+
+ std::vector<std::pair<uint16_t, std::string>> options;
+ getEDNSOptionsFromContent(blob, options);
+
+ /* getDnsrecordheader() has helpfully converted the TTL for us, which we do not want in that case */
+ uint32_t ttl = htonl(recordHeader.d_ttl);
+ EDNS0Record edns0{};
+ static_assert(sizeof(edns0) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
+ memcpy(&edns0, &ttl, sizeof(edns0));
+
+ /* addOrReplaceEDNSOption will set it to false if there is already an existing option */
+ optionAdded = true;
+ addOrReplaceEDNSOption(options, optionToReplace, optionAdded, overrideExisting, newOptionContent);
+ packetWriter.addOpt(recordHeader.d_class, edns0.extRCode, edns0.extFlags, options, edns0.version);
+ }
+ }
+
+ if (ednsAdded) {
+ packetWriter.addOpt(g_EdnsUDPPayloadSize, 0, 0, {{optionToReplace, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))}}, 0);
+ optionAdded = true;
+ }
+
+ packetWriter.commit();
+
+ return true;
+}
+
+static bool slowParseEDNSOptions(const PacketBuffer& packet, EDNSOptionViewMap& options)
+{
+ if (packet.size() < sizeof(dnsheader)) {
+ return false;
+ }
+
+ const dnsheader_aligned dnsHeader(packet.data());
+
+ if (ntohs(dnsHeader->qdcount) == 0) {
+ return false;
+ }
+
+ if (ntohs(dnsHeader->arcount) == 0) {
+ throw std::runtime_error("slowParseEDNSOptions() should not be called for queries that have no EDNS");
+ }
+
+ try {
+ uint64_t numrecords = ntohs(dnsHeader->ancount) + ntohs(dnsHeader->nscount) + ntohs(dnsHeader->arcount);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
+ DNSPacketMangler dpm(const_cast<char*>(reinterpret_cast<const char*>(packet.data())), packet.size());
+ uint64_t index{};
+ for (index = 0; index < ntohs(dnsHeader->qdcount); ++index) {
+ dpm.skipDomainName();
+ /* type and class */
+ dpm.skipBytes(4);
+ }
+
+ for (index = 0; index < numrecords; ++index) {
+ dpm.skipDomainName();
+
+ uint8_t section = index < ntohs(dnsHeader->ancount) ? 1 : (index < (ntohs(dnsHeader->ancount) + ntohs(dnsHeader->nscount)) ? 2 : 3);
+ uint16_t dnstype = dpm.get16BitInt();
+ dpm.get16BitInt();
+ dpm.skipBytes(4); /* TTL */
+
+ if (section == 3 && dnstype == QType::OPT) {
+ uint32_t offset = dpm.getOffset();
+ if (offset >= packet.size()) {
+ return false;
+ }
+ /* if we survive this call, we can parse it safely */
+ dpm.skipRData();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ return getEDNSOptions(reinterpret_cast<const char*>(&packet.at(offset)), packet.size() - offset, options) == 0;
+ }
+ dpm.skipRData();
+ }
+ }
+ catch (...) {
+ return false;
+ }
+
+ return true;
+}
+
+int locateEDNSOptRR(const PacketBuffer& packet, uint16_t* optStart, size_t* optLen, bool* last)
+{
+ assert(optStart != nullptr);
+ assert(optLen != nullptr);
+ assert(last != nullptr);
+ const dnsheader_aligned dnsHeader(packet.data());
+
+ if (ntohs(dnsHeader->arcount) == 0) {
+ return ENOENT;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ PacketReader packetReader(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()));
+
+ size_t idx = 0;
+ DNSName rrname;
+ uint16_t qdcount = ntohs(dnsHeader->qdcount);
+ uint16_t ancount = ntohs(dnsHeader->ancount);
+ uint16_t nscount = ntohs(dnsHeader->nscount);
+ uint16_t arcount = ntohs(dnsHeader->arcount);
+ uint16_t rrtype{};
+ uint16_t rrclass{};
+ dnsrecordheader recordHeader{};
+
+ /* consume qd */
+ for (idx = 0; idx < qdcount; idx++) {
+ rrname = packetReader.getName();
+ rrtype = packetReader.get16BitInt();
+ rrclass = packetReader.get16BitInt();
+ (void)rrtype;
+ (void)rrclass;
+ }
+
+ /* consume AN and NS */
+ for (idx = 0; idx < ancount + nscount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+ packetReader.skip(recordHeader.d_clen);
+ }
+
+ /* consume AR, looking for OPT */
+ for (idx = 0; idx < arcount; idx++) {
+ uint16_t start = packetReader.getPosition();
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ if (recordHeader.d_type == QType::OPT) {
+ *optStart = start;
+ *optLen = (packetReader.getPosition() - start) + recordHeader.d_clen;
+
+ if (packet.size() < (*optStart + *optLen)) {
+ throw std::range_error("Opt record overflow");
+ }
+
+ if (idx == ((size_t)arcount - 1)) {
+ *last = true;
+ }
+ else {
+ *last = false;
+ }
+ return 0;
+ }
+ packetReader.skip(recordHeader.d_clen);
+ }
+
+ return ENOENT;
+}
+
+/* extract the start of the OPT RR in a QUERY packet if any */
+int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t* remaining)
+{
+ assert(optRDPosition != nullptr);
+ assert(remaining != nullptr);
+ const dnsheader_aligned dnsHeader(packet.data());
+
+ if (offset >= packet.size()) {
+ return ENOENT;
+ }
+
+ if (ntohs(dnsHeader->qdcount) != 1 || ntohs(dnsHeader->ancount) != 0 || ntohs(dnsHeader->arcount) != 1 || ntohs(dnsHeader->nscount) != 0) {
+ return ENOENT;
+ }
+
+ size_t pos = sizeof(dnsheader) + offset;
+ pos += DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+
+ if (pos >= packet.size()) {
+ return ENOENT;
+ }
+
+ if ((pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE) >= packet.size()) {
+ return ENOENT;
+ }
+
+ if (packet[pos] != 0) {
+ /* not the root so not an OPT record */
+ return ENOENT;
+ }
+ pos += 1;
+
+ uint16_t qtype = packet.at(pos) * 256 + packet.at(pos + 1);
+ pos += DNS_TYPE_SIZE;
+ pos += DNS_CLASS_SIZE;
+
+ if (qtype != QType::OPT || (packet.size() - pos) < (DNS_TTL_SIZE + DNS_RDLENGTH_SIZE)) {
+ return ENOENT;
+ }
+
+ pos += DNS_TTL_SIZE;
+ *optRDPosition = pos;
+ *remaining = packet.size() - pos;
+
+ return 0;
+}
+
+void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength)
+{
+ Netmask sourceNetmask(source, ECSPrefixLength);
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = sourceNetmask;
+ string payload = makeEDNSSubnetOptsString(ecsOpts);
+ generateEDNSOption(EDNSOptionCode::ECS, payload, res);
+}
+
+bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK)
+{
+ const uint8_t name = 0;
+ dnsrecordheader dnsHeader{};
+ EDNS0Record edns0{};
+ edns0.extRCode = ednsrcode;
+ edns0.version = 0;
+ edns0.extFlags = dnssecOK ? htons(EDNS_HEADER_FLAG_DO) : 0;
+
+ if ((maximumSize - res.size()) < (sizeof(name) + sizeof(dnsHeader) + optRData.length())) {
+ return false;
+ }
+
+ dnsHeader.d_type = htons(QType::OPT);
+ dnsHeader.d_class = htons(udpPayloadSize);
+ static_assert(sizeof(EDNS0Record) == sizeof(dnsHeader.d_ttl), "sizeof(EDNS0Record) must match sizeof(dnsrecordheader.d_ttl)");
+ memcpy(&dnsHeader.d_ttl, &edns0, sizeof edns0);
+ dnsHeader.d_clen = htons(static_cast<uint16_t>(optRData.length()));
+
+ res.reserve(res.size() + sizeof(name) + sizeof(dnsHeader) + optRData.length());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ res.insert(res.end(), reinterpret_cast<const uint8_t*>(&name), reinterpret_cast<const uint8_t*>(&name) + sizeof(name));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ res.insert(res.end(), reinterpret_cast<const uint8_t*>(&dnsHeader), reinterpret_cast<const uint8_t*>(&dnsHeader) + sizeof(dnsHeader));
+ res.insert(res.end(), optRData.begin(), optRData.end());
+
+ return true;
+}
+
+static bool replaceEDNSClientSubnetOption(PacketBuffer& packet, size_t maximumSize, size_t const oldEcsOptionStartPosition, size_t const oldEcsOptionSize, size_t const optRDLenPosition, const string& newECSOption)
+{
+ assert(oldEcsOptionStartPosition < packet.size());
+ assert(optRDLenPosition < packet.size());
+
+ if (newECSOption.size() == oldEcsOptionSize) {
+ /* same size as the existing option */
+ memcpy(&packet.at(oldEcsOptionStartPosition), newECSOption.c_str(), oldEcsOptionSize);
+ }
+ else {
+ /* different size than the existing option */
+ const unsigned int newPacketLen = packet.size() + (newECSOption.length() - oldEcsOptionSize);
+ const size_t beforeOptionLen = oldEcsOptionStartPosition;
+ const size_t dataBehindSize = packet.size() - beforeOptionLen - oldEcsOptionSize;
+
+ /* check that it fits in the existing buffer */
+ if (newPacketLen > packet.size()) {
+ if (newPacketLen > maximumSize) {
+ return false;
+ }
+
+ packet.resize(newPacketLen);
+ }
+
+ /* fix the size of ECS Option RDLen */
+ uint16_t newRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1);
+ newRDLen += (newECSOption.size() - oldEcsOptionSize);
+ packet.at(optRDLenPosition) = newRDLen / 256;
+ packet.at(optRDLenPosition + 1) = newRDLen % 256;
+
+ if (dataBehindSize > 0) {
+ memmove(&packet.at(oldEcsOptionStartPosition), &packet.at(oldEcsOptionStartPosition + oldEcsOptionSize), dataBehindSize);
+ }
+ memcpy(&packet.at(oldEcsOptionStartPosition + dataBehindSize), newECSOption.c_str(), newECSOption.size());
+ packet.resize(newPacketLen);
+ }
+
+ return true;
+}
+
+/* This function looks for an OPT RR, return true if a valid one was found (even if there was no options)
+ and false otherwise. */
+bool parseEDNSOptions(const DNSQuestion& dnsQuestion)
+{
+ const auto dnsHeader = dnsQuestion.getHeader();
+ if (dnsQuestion.ednsOptions != nullptr) {
+ return true;
+ }
+
+ // dnsQuestion.ednsOptions is mutable
+ dnsQuestion.ednsOptions = std::make_unique<EDNSOptionViewMap>();
+
+ if (ntohs(dnsHeader->arcount) == 0) {
+ /* nothing in additional so no EDNS */
+ return false;
+ }
+
+ if (ntohs(dnsHeader->ancount) != 0 || ntohs(dnsHeader->nscount) != 0 || ntohs(dnsHeader->arcount) > 1) {
+ return slowParseEDNSOptions(dnsQuestion.getData(), *dnsQuestion.ednsOptions);
+ }
+
+ size_t remaining = 0;
+ uint16_t optRDPosition{};
+ int res = getEDNSOptionsStart(dnsQuestion.getData(), dnsQuestion.ids.qname.wirelength(), &optRDPosition, &remaining);
+
+ if (res == 0) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ res = getEDNSOptions(reinterpret_cast<const char*>(&dnsQuestion.getData().at(optRDPosition)), remaining, *dnsQuestion.ednsOptions);
+ return (res == 0);
+ }
+
+ return false;
+}
+
+static bool addECSToExistingOPT(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, size_t optRDLenPosition, bool& ecsAdded)
+{
+ /* we need to add one EDNS0 ECS option, fixing the size of EDNS0 RDLENGTH */
+ /* getEDNSOptionsStart has already checked that there is exactly one AR,
+ no NS and no AN */
+ uint16_t oldRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1);
+ if (packet.size() != (optRDLenPosition + sizeof(uint16_t) + oldRDLen)) {
+ /* we are supposed to be the last record, do we have some trailing data to remove? */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(packet.data()), packet.size());
+ packet.resize(realPacketLen);
+ }
+
+ if ((maximumSize - packet.size()) < newECSOption.size()) {
+ return false;
+ }
+
+ uint16_t newRDLen = oldRDLen + newECSOption.size();
+ packet.at(optRDLenPosition) = newRDLen / 256;
+ packet.at(optRDLenPosition + 1) = newRDLen % 256;
+
+ packet.insert(packet.end(), newECSOption.begin(), newECSOption.end());
+ ecsAdded = true;
+
+ return true;
+}
+
+static bool addEDNSWithECS(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, bool& ednsAdded, bool& ecsAdded)
+{
+ if (!generateOptRR(newECSOption, packet, maximumSize, g_EdnsUDPPayloadSize, 0, false)) {
+ return false;
+ }
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+ uint16_t arcount = ntohs(header.arcount);
+ arcount++;
+ header.arcount = htons(arcount);
+ return true;
+ });
+ ednsAdded = true;
+ ecsAdded = true;
+
+ return true;
+}
+
+bool handleEDNSClientSubnet(PacketBuffer& packet, const size_t maximumSize, const size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption)
+{
+ assert(qnameWireLength <= packet.size());
+
+ const dnsheader_aligned dnsHeader(packet.data());
+
+ if (ntohs(dnsHeader->ancount) != 0 || ntohs(dnsHeader->nscount) != 0 || (ntohs(dnsHeader->arcount) != 0 && ntohs(dnsHeader->arcount) != 1)) {
+ PacketBuffer newContent;
+ newContent.reserve(packet.size());
+
+ if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::ECS, ecsAdded, overrideExisting, newECSOption)) {
+ return false;
+ }
+
+ if (newContent.size() > maximumSize) {
+ ednsAdded = false;
+ ecsAdded = false;
+ return false;
+ }
+
+ packet = std::move(newContent);
+ return true;
+ }
+
+ uint16_t optRDPosition = 0;
+ size_t remaining = 0;
+
+ int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining);
+
+ if (res != 0) {
+ /* no EDNS but there might be another record in additional (TSIG?) */
+ /* Careful, this code assumes that ANCOUNT == 0 && NSCOUNT == 0 */
+ size_t minimumPacketSize = sizeof(dnsheader) + qnameWireLength + sizeof(uint16_t) + sizeof(uint16_t);
+ if (packet.size() > minimumPacketSize) {
+ if (ntohs(dnsHeader->arcount) == 0) {
+ /* well now.. */
+ packet.resize(minimumPacketSize);
+ }
+ else {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(packet.data()), packet.size());
+ packet.resize(realPacketLen);
+ }
+ }
+
+ return addEDNSWithECS(packet, maximumSize, newECSOption, ednsAdded, ecsAdded);
+ }
+
+ size_t ecsOptionStartPosition = 0;
+ size_t ecsOptionSize = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize);
+
+ if (res == 0) {
+ /* there is already an ECS value */
+ if (!overrideExisting) {
+ return true;
+ }
+
+ return replaceEDNSClientSubnetOption(packet, maximumSize, optRDPosition + ecsOptionStartPosition, ecsOptionSize, optRDPosition, newECSOption);
+ }
+
+ /* we have an EDNS OPT RR but no existing ECS option */
+ return addECSToExistingOPT(packet, maximumSize, newECSOption, optRDPosition, ecsAdded);
+}
+
+bool handleEDNSClientSubnet(DNSQuestion& dnsQuestion, bool& ednsAdded, bool& ecsAdded)
+{
+ string newECSOption;
+ generateECSOption(dnsQuestion.ecs ? dnsQuestion.ecs->getNetwork() : dnsQuestion.ids.origRemote, newECSOption, dnsQuestion.ecs ? dnsQuestion.ecs->getBits() : dnsQuestion.ecsPrefixLength);
+
+ return handleEDNSClientSubnet(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnsQuestion.ids.qname.wirelength(), ednsAdded, ecsAdded, dnsQuestion.ecsOverride, newECSOption);
+}
+
+static int removeEDNSOptionFromOptions(unsigned char* optionsStart, const uint16_t optionsLen, const uint16_t optionCodeToRemove, uint16_t* newOptionsLen)
+{
+ const pdns::views::UnsignedCharView view(optionsStart, optionsLen);
+ size_t pos = 0;
+ while ((pos + 4) <= view.size()) {
+ size_t optionBeginPos = pos;
+ const uint16_t optionCode = 0x100 * view.at(pos) + view.at(pos + 1);
+ pos += sizeof(optionCode);
+ const uint16_t optionLen = 0x100 * view.at(pos) + view.at(pos + 1);
+ pos += sizeof(optionLen);
+ if ((pos + optionLen) > view.size()) {
+ return EINVAL;
+ }
+ if (optionCode == optionCodeToRemove) {
+ if (pos + optionLen < view.size()) {
+ /* move remaining options over the removed one,
+ if any */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memmove(optionsStart + optionBeginPos, optionsStart + pos + optionLen, optionsLen - (pos + optionLen));
+ }
+ *newOptionsLen = optionsLen - (sizeof(optionCode) + sizeof(optionLen) + optionLen);
+ return 0;
+ }
+ pos += optionLen;
+ }
+ return ENOENT;
+}
+
+int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove)
+{
+ if (*optLen < optRecordMinimumSize) {
+ return EINVAL;
+ }
+ const pdns::views::UnsignedCharView view(optStart, *optLen);
+ /* skip the root label, qtype, qclass and TTL */
+ size_t position = 9;
+ uint16_t rdLen = (0x100 * view.at(position) + view.at(position + 1));
+ position += sizeof(rdLen);
+ if (position + rdLen != view.size()) {
+ return EINVAL;
+ }
+ uint16_t newRdLen = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ int res = removeEDNSOptionFromOptions(reinterpret_cast<unsigned char*>(optStart + position), rdLen, optionCodeToRemove, &newRdLen);
+ if (res != 0) {
+ return res;
+ }
+ *optLen -= (rdLen - newRdLen);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ auto* rdLenPtr = reinterpret_cast<unsigned char*>(optStart + 9);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ rdLenPtr[0] = newRdLen / 0x100;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ rdLenPtr[1] = newRdLen % 0x100;
+ return 0;
+}
+
+bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart, uint16_t* optContentLen)
+{
+ if (optLen < optRecordMinimumSize) {
+ return false;
+ }
+ size_t position = optStart + 9;
+ uint16_t rdLen = (0x100 * static_cast<unsigned char>(packet.at(position)) + static_cast<unsigned char>(packet.at(position + 1)));
+ position += sizeof(rdLen);
+ if (rdLen > (optLen - optRecordMinimumSize)) {
+ return false;
+ }
+
+ size_t rdEnd = position + rdLen;
+ while ((position + 4) <= rdEnd) {
+ const uint16_t optionCode = 0x100 * static_cast<unsigned char>(packet.at(position)) + static_cast<unsigned char>(packet.at(position + 1));
+ position += sizeof(optionCode);
+ const uint16_t optionLen = 0x100 * static_cast<unsigned char>(packet.at(position)) + static_cast<unsigned char>(packet.at(position + 1));
+ position += sizeof(optionLen);
+
+ if ((position + optionLen) > rdEnd) {
+ return false;
+ }
+
+ if (optionCode == optionCodeToFind) {
+ if (optContentStart != nullptr) {
+ *optContentStart = position;
+ }
+
+ if (optContentLen != nullptr) {
+ *optContentLen = optionLen;
+ }
+
+ return true;
+ }
+ position += optionLen;
+ }
+ return false;
+}
+
+int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent)
+{
+ assert(initialPacket.size() >= sizeof(dnsheader));
+ const dnsheader_aligned dnsHeader(initialPacket.data());
+
+ if (ntohs(dnsHeader->arcount) == 0) {
+ return ENOENT;
+ }
+
+ if (ntohs(dnsHeader->qdcount) == 0) {
+ return ENOENT;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ PacketReader packetReader(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+
+ size_t idx = 0;
+ DNSName rrname;
+ uint16_t qdcount = ntohs(dnsHeader->qdcount);
+ uint16_t ancount = ntohs(dnsHeader->ancount);
+ uint16_t nscount = ntohs(dnsHeader->nscount);
+ uint16_t arcount = ntohs(dnsHeader->arcount);
+ uint16_t rrtype = 0;
+ uint16_t rrclass = 0;
+ string blob;
+ dnsrecordheader recordHeader{};
+
+ rrname = packetReader.getName();
+ rrtype = packetReader.get16BitInt();
+ rrclass = packetReader.get16BitInt();
+
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(newContent, rrname, rrtype, rrclass, dnsHeader->opcode);
+ packetWriter.getHeader()->id = dnsHeader->id;
+ packetWriter.getHeader()->qr = dnsHeader->qr;
+ packetWriter.getHeader()->aa = dnsHeader->aa;
+ packetWriter.getHeader()->tc = dnsHeader->tc;
+ packetWriter.getHeader()->rd = dnsHeader->rd;
+ packetWriter.getHeader()->ra = dnsHeader->ra;
+ packetWriter.getHeader()->ad = dnsHeader->ad;
+ packetWriter.getHeader()->cd = dnsHeader->cd;
+ packetWriter.getHeader()->rcode = dnsHeader->rcode;
+
+ /* consume remaining qd if any */
+ if (qdcount > 1) {
+ for (idx = 1; idx < qdcount; idx++) {
+ rrname = packetReader.getName();
+ rrtype = packetReader.get16BitInt();
+ rrclass = packetReader.get16BitInt();
+ (void)rrtype;
+ (void)rrclass;
+ }
+ }
+
+ /* copy AN and NS */
+ for (idx = 0; idx < ancount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ANSWER, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+
+ for (idx = 0; idx < nscount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::AUTHORITY, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+
+ /* consume AR, looking for OPT */
+ for (idx = 0; idx < arcount; idx++) {
+ rrname = packetReader.getName();
+ packetReader.getDnsrecordheader(recordHeader);
+
+ if (recordHeader.d_type != QType::OPT) {
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ADDITIONAL, true);
+ packetReader.xfrBlob(blob);
+ packetWriter.xfrBlob(blob);
+ }
+ else {
+ packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ADDITIONAL, false);
+ packetReader.xfrBlob(blob);
+ uint16_t rdLen = blob.length();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ removeEDNSOptionFromOptions(reinterpret_cast<unsigned char*>(blob.data()), rdLen, optionCodeToSkip, &rdLen);
+ /* xfrBlob(string, size) completely ignores size.. */
+ if (rdLen > 0) {
+ blob.resize((size_t)rdLen);
+ packetWriter.xfrBlob(blob);
+ }
+ else {
+ packetWriter.commit();
+ }
+ }
+ }
+ packetWriter.commit();
+
+ return 0;
+}
+
+bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode)
+{
+ if (!generateOptRR(std::string(), packet, maximumSize, payloadSize, ednsrcode, dnssecOK)) {
+ return false;
+ }
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+ header.arcount = htons(ntohs(header.arcount) + 1);
+ return true;
+ });
+
+ return true;
+}
+
+/*
+ This function keeps the existing header and DNSSECOK bit (if any) but wipes anything else,
+ generating a NXD or NODATA answer with a SOA record in the additional section (or optionally the authority section for a full cacheable NXDOMAIN/NODATA).
+*/
+bool setNegativeAndAdditionalSOA(DNSQuestion& dnsQuestion, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection)
+{
+ auto& packet = dnsQuestion.getMutableData();
+ auto dnsHeader = dnsQuestion.getHeader();
+ if (ntohs(dnsHeader->qdcount) != 1) {
+ return false;
+ }
+
+ size_t queryPartSize = sizeof(dnsheader) + dnsQuestion.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+ if (packet.size() < queryPartSize) {
+ /* something is already wrong, don't build on flawed foundations */
+ return false;
+ }
+
+ uint16_t qtype = htons(QType::SOA);
+ uint16_t qclass = htons(QClass::IN);
+ uint16_t rdLength = mname.wirelength() + rname.wirelength() + sizeof(serial) + sizeof(refresh) + sizeof(retry) + sizeof(expire) + sizeof(minimum);
+ size_t soaSize = zone.wirelength() + sizeof(qtype) + sizeof(qclass) + sizeof(ttl) + sizeof(rdLength) + rdLength;
+ bool hadEDNS = false;
+ bool dnssecOK = false;
+
+ if (g_addEDNSToSelfGeneratedResponses) {
+ uint16_t payloadSize = 0;
+ uint16_t zValue = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &zValue);
+ if (hadEDNS) {
+ dnssecOK = (zValue & EDNS_HEADER_FLAG_DO) != 0;
+ }
+ }
+
+ /* chop off everything after the question */
+ packet.resize(queryPartSize);
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [nxd](dnsheader& header) {
+ if (nxd) {
+ header.rcode = RCode::NXDomain;
+ }
+ else {
+ header.rcode = RCode::NoError;
+ }
+ header.qr = true;
+ header.ancount = 0;
+ header.nscount = 0;
+ header.arcount = 0;
+ return true;
+ });
+
+ rdLength = htons(rdLength);
+ ttl = htonl(ttl);
+ serial = htonl(serial);
+ refresh = htonl(refresh);
+ retry = htonl(retry);
+ expire = htonl(expire);
+ minimum = htonl(minimum);
+
+ std::string soa;
+ soa.reserve(soaSize);
+ soa.append(zone.toDNSString());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&qtype), sizeof(qtype));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&qclass), sizeof(qclass));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&ttl), sizeof(ttl));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&rdLength), sizeof(rdLength));
+ soa.append(mname.toDNSString());
+ soa.append(rname.toDNSString());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&serial), sizeof(serial));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&refresh), sizeof(refresh));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&retry), sizeof(retry));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&expire), sizeof(expire));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ soa.append(reinterpret_cast<const char*>(&minimum), sizeof(minimum));
+
+ if (soa.size() != soaSize) {
+ throw std::runtime_error("Unexpected SOA response size: " + std::to_string(soa.size()) + " vs " + std::to_string(soaSize));
+ }
+
+ packet.insert(packet.end(), soa.begin(), soa.end());
+
+ /* We are populating a response with only the query in place, order of sections is QD,AN,NS,AR
+ NS (authority) is before AR (additional) so we can just decide which section the SOA record is in here
+ and have EDNS added to AR afterwards */
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [soaInAuthoritySection](dnsheader& header) {
+ if (soaInAuthoritySection) {
+ header.nscount = htons(1);
+ }
+ else {
+ header.arcount = htons(1);
+ }
+ return true;
+ });
+
+ if (hadEDNS) {
+ /* now we need to add a new OPT record */
+ return addEDNS(packet, dnsQuestion.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dnsQuestion.ednsRCode);
+ }
+
+ return true;
+}
+
+bool addEDNSToQueryTurnedResponse(DNSQuestion& dnsQuestion)
+{
+ uint16_t optRDPosition{};
+ /* remaining is at least the size of the rdlen + the options if any + the following records if any */
+ size_t remaining = 0;
+
+ auto& packet = dnsQuestion.getMutableData();
+ int res = getEDNSOptionsStart(packet, dnsQuestion.ids.qname.wirelength(), &optRDPosition, &remaining);
+
+ if (res != 0) {
+ /* if the initial query did not have EDNS0, we are done */
+ return true;
+ }
+
+ const size_t existingOptLen = /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2 + remaining;
+ if (existingOptLen >= packet.size()) {
+ /* something is wrong, bail out */
+ return false;
+ }
+
+ const size_t optPosition = (optRDPosition - (/* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2));
+
+ size_t zPosition = optPosition + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE;
+ uint16_t zValue = 0x100 * packet.at(zPosition) + packet.at(zPosition + 1);
+ bool dnssecOK = (zValue & EDNS_HEADER_FLAG_DO) != 0;
+
+ /* remove the existing OPT record, and everything else that follows (any SIG or TSIG would be useless anyway) */
+ packet.resize(packet.size() - existingOptLen);
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+ header.arcount = 0;
+ return true;
+ });
+
+ if (g_addEDNSToSelfGeneratedResponses) {
+ /* now we need to add a new OPT record */
+ return addEDNS(packet, dnsQuestion.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dnsQuestion.ednsRCode);
+ }
+
+ /* otherwise we are just fine */
+ return true;
+}
+
+// goal in life - if you send us a reasonably normal packet, we'll get Z for you, otherwise 0
+int getEDNSZ(const DNSQuestion& dnsQuestion)
+{
+ try {
+ const auto& dnsHeader = dnsQuestion.getHeader();
+ if (ntohs(dnsHeader->qdcount) != 1 || dnsHeader->ancount != 0 || ntohs(dnsHeader->arcount) != 1 || dnsHeader->nscount != 0) {
+ return 0;
+ }
+
+ if (dnsQuestion.getData().size() <= sizeof(dnsheader)) {
+ return 0;
+ }
+
+ size_t pos = sizeof(dnsheader) + dnsQuestion.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+
+ if (dnsQuestion.getData().size() <= (pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE)) {
+ return 0;
+ }
+
+ const auto& packet = dnsQuestion.getData();
+ if (packet.at(pos) != 0) {
+ /* not root, so not a valid OPT record */
+ return 0;
+ }
+
+ pos++;
+
+ uint16_t qtype = packet.at(pos) * 256 + packet.at(pos + 1);
+ pos += DNS_TYPE_SIZE;
+ pos += DNS_CLASS_SIZE;
+
+ if (qtype != QType::OPT || (pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + 1) >= packet.size()) {
+ return 0;
+ }
+
+ return 0x100 * packet.at(pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE) + packet.at(pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + 1);
+ }
+ catch (...) {
+ return 0;
+ }
+}
+
+bool queryHasEDNS(const DNSQuestion& dnsQuestion)
+{
+ uint16_t optRDPosition = 0;
+ size_t ecsRemaining = 0;
+
+ int res = getEDNSOptionsStart(dnsQuestion.getData(), dnsQuestion.ids.qname.wirelength(), &optRDPosition, &ecsRemaining);
+ return res == 0;
+}
+
+bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0)
+{
+ uint16_t optStart = 0;
+ size_t optLen = 0;
+ bool last = false;
+ int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
+ if (res != 0) {
+ // no EDNS OPT RR
+ return false;
+ }
+
+ if (optLen < optRecordMinimumSize) {
+ return false;
+ }
+
+ if (optStart < packet.size() && packet.at(optStart) != 0) {
+ // OPT RR Name != '.'
+ return false;
+ }
+
+ static_assert(sizeof(EDNS0Record) == sizeof(uint32_t), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
+ // copy out 4-byte "ttl" (really the EDNS0 record), after root label (1) + type (2) + class (2).
+ memcpy(&edns0, &packet.at(optStart + 5), sizeof edns0);
+ return true;
+}
+
+bool setEDNSOption(DNSQuestion& dnsQuestion, uint16_t ednsCode, const std::string& ednsData)
+{
+ std::string optRData;
+ generateEDNSOption(ednsCode, ednsData, optRData);
+
+ if (dnsQuestion.getHeader()->arcount != 0) {
+ bool ednsAdded = false;
+ bool optionAdded = false;
+ PacketBuffer newContent;
+ newContent.reserve(dnsQuestion.getData().size());
+
+ if (!slowRewriteEDNSOptionInQueryWithRecords(dnsQuestion.getData(), newContent, ednsAdded, ednsCode, optionAdded, true, optRData)) {
+ return false;
+ }
+
+ if (newContent.size() > dnsQuestion.getMaximumSize()) {
+ return false;
+ }
+
+ dnsQuestion.getMutableData() = std::move(newContent);
+ if (!dnsQuestion.ids.ednsAdded && ednsAdded) {
+ dnsQuestion.ids.ednsAdded = true;
+ }
+
+ return true;
+ }
+
+ auto& data = dnsQuestion.getMutableData();
+ if (generateOptRR(optRData, data, dnsQuestion.getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.arcount = htons(1);
+ return true;
+ });
+ // make sure that any EDNS sent by the backend is removed before forwarding the response to the client
+ dnsQuestion.ids.ednsAdded = true;
+ }
+
+ return true;
+}
+
+namespace dnsdist
+{
+bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer, uint8_t rcode, bool clearAnswers)
+{
+ const auto qnameLength = state.qname.wirelength();
+ if (buffer.size() < sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t)) {
+ return false;
+ }
+
+ EDNS0Record edns0{};
+ bool hadEDNS = false;
+ if (clearAnswers) {
+ hadEDNS = getEDNS0Record(buffer, edns0);
+ }
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [rcode, clearAnswers](dnsheader& header) {
+ header.rcode = rcode;
+ header.ad = false;
+ header.aa = false;
+ header.ra = header.rd;
+ header.qr = true;
+
+ if (clearAnswers) {
+ header.ancount = 0;
+ header.nscount = 0;
+ header.arcount = 0;
+ }
+ return true;
+ });
+
+ if (clearAnswers) {
+ buffer.resize(sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t));
+ if (hadEDNS) {
+ DNSQuestion dnsQuestion(state, buffer);
+ if (!addEDNS(buffer, dnsQuestion.getMaximumSize(), (edns0.extFlags & htons(EDNS_HEADER_FLAG_DO)) != 0, g_PayloadSizeSelfGenAnswers, 0)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+}
+++ /dev/null
-../dnsdist-ecs.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <string>
+
+#include "iputils.hh"
+#include "noinitvector.hh"
+
+struct DNSQuestion;
+
+// root label (1), type (2), class (2), ttl (4) + rdlen (2)
+static const size_t optRecordMinimumSize = 11;
+
+extern size_t g_EdnsUDPPayloadSize;
+extern uint16_t g_PayloadSizeSelfGenAnswers;
+
+int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent);
+bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent);
+int locateEDNSOptRR(const PacketBuffer& packet, uint16_t* optStart, size_t* optLen, bool* last);
+bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK);
+void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength);
+int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove);
+int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent);
+int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t* remaining);
+bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart = nullptr, uint16_t* optContentLen = nullptr);
+bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode);
+bool addEDNSToQueryTurnedResponse(DNSQuestion& dnsQuestion);
+bool setNegativeAndAdditionalSOA(DNSQuestion& dnsQuestion, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection);
+
+bool handleEDNSClientSubnet(DNSQuestion& dnsQuestion, bool& ednsAdded, bool& ecsAdded);
+bool handleEDNSClientSubnet(PacketBuffer& packet, size_t maximumSize, size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption);
+
+bool parseEDNSOptions(const DNSQuestion& dnsQuestion);
+
+int getEDNSZ(const DNSQuestion& dnsQuestion);
+bool queryHasEDNS(const DNSQuestion& dnsQuestion);
+bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0);
+
+bool setEDNSOption(DNSQuestion& dnsQuestion, uint16_t ednsCode, const std::string& data);
+
+struct InternalQueryState;
+namespace dnsdist
+{
+bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer, uint8_t rcode, bool clearAnswers);
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
+#include "ednsoptions.hh"
+#include "ednsextendederror.hh"
+
+namespace dnsdist::edns
+{
+std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSError(const PacketBuffer& packet)
+{
+ uint16_t optStart = 0;
+ size_t optLen = 0;
+ bool last = false;
+
+ int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
+
+ if (res != 0) {
+ return {std::nullopt, std::nullopt};
+ }
+
+ size_t optContentStart = 0;
+ uint16_t optContentLen = 0;
+ uint16_t infoCode{0};
+ std::optional<std::string> extraText{std::nullopt};
+ /* we need at least 2 bytes after the option length (info-code) */
+ if (!isEDNSOptionInOpt(packet, optStart, optLen, EDNSOptionCode::EXTENDEDERROR, &optContentStart, &optContentLen) || optContentLen < sizeof(infoCode)) {
+ return {std::nullopt, std::nullopt};
+ }
+ memcpy(&infoCode, &packet.at(optContentStart), sizeof(infoCode));
+ infoCode = ntohs(infoCode);
+
+ if (optContentLen > sizeof(infoCode)) {
+ extraText = std::string();
+ extraText->resize(optContentLen - sizeof(infoCode));
+ memcpy(extraText->data(), &packet.at(optContentStart + sizeof(infoCode)), optContentLen - sizeof(infoCode));
+ }
+ return {infoCode, std::move(extraText)};
+}
+
+bool addExtendedDNSError(PacketBuffer& packet, size_t maximumPacketSize, uint16_t code, const std::string& extraStatus)
+{
+ uint16_t optStart = 0;
+ size_t optLen = 0;
+ bool last = false;
+
+ int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
+
+ if (res != 0) {
+ /* no EDNS OPT record in the response, something is not right */
+ return false;
+ }
+
+ EDNSExtendedError ede{.infoCode = code, .extraText = extraStatus};
+ auto edeOptionPayload = makeEDNSExtendedErrorOptString(ede);
+ std::string edeOption;
+ generateEDNSOption(EDNSOptionCode::EXTENDEDERROR, edeOptionPayload, edeOption);
+
+ /* we might have one record after the OPT one, we need to rewrite
+ the whole packet because of compression */
+ PacketBuffer newContent;
+ bool ednsAdded = false;
+ bool edeAdded = false;
+ if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::EXTENDEDERROR, edeAdded, true, edeOption)) {
+ return false;
+ }
+
+ if (newContent.size() > maximumPacketSize) {
+ return false;
+ }
+
+ packet = std::move(newContent);
+ return true;
+}
+}
*/
#pragma once
-#include "snmp-agent.hh"
+#include <optional>
+#include <string>
+#include <utility>
-class DNSDistSNMPAgent;
+#include "noinitvector.hh"
-#include "dnsdist.hh"
-
-class DNSDistSNMPAgent: public SNMPAgent
+namespace dnsdist::edns
{
-public:
- DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket);
- bool sendBackendStatusChangeTrap(const DownstreamState&);
- bool sendCustomTrap(const std::string& reason);
- bool sendDNSTrap(const DNSQuestion&, const std::string& reason="");
-};
+std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSError(const PacketBuffer& packet);
+bool addExtendedDNSError(PacketBuffer& packet, size_t maximumPacketSize, uint16_t code, const std::string& extraStatus);
+}
struct HealthCheckData
{
- enum class TCPState : uint8_t { WritingQuery, ReadingResponseSize, ReadingResponse };
+ enum class TCPState : uint8_t
+ {
+ WritingQuery,
+ ReadingResponseSize,
+ ReadingResponse
+ };
- HealthCheckData(FDMultiplexer& mplexer, const std::shared_ptr<DownstreamState>& ds, DNSName&& checkName, uint16_t checkType, uint16_t checkClass, uint16_t queryID): d_ds(ds), d_mplexer(mplexer), d_udpSocket(-1), d_checkName(std::move(checkName)), d_checkType(checkType), d_checkClass(checkClass), d_queryID(queryID)
+ HealthCheckData(FDMultiplexer& mplexer, std::shared_ptr<DownstreamState> downstream, DNSName&& checkName, uint16_t checkType, uint16_t checkClass, uint16_t queryID) :
+ d_ds(std::move(downstream)), d_mplexer(mplexer), d_udpSocket(-1), d_checkName(std::move(checkName)), d_checkType(checkType), d_checkClass(checkClass), d_queryID(queryID)
{
}
PacketBuffer d_buffer;
Socket d_udpSocket;
DNSName d_checkName;
- struct timeval d_ttd{0, 0};
+ struct timeval d_ttd
+ {
+ 0, 0
+ };
size_t d_bufferPos{0};
uint16_t d_checkType;
uint16_t d_checkClass;
static bool handleResponse(std::shared_ptr<HealthCheckData>& data)
{
- auto& ds = data->d_ds;
+ const auto& downstream = data->d_ds;
try {
if (data->d_buffer.size() < sizeof(dnsheader)) {
+ ++data->d_ds->d_healthCheckMetrics.d_parseErrors;
if (g_verboseHealthChecks) {
- infolog("Invalid health check response of size %d from backend %s, expecting at least %d", data->d_buffer.size(), ds->getNameWithAddr(), sizeof(dnsheader));
+ infolog("Invalid health check response of size %d from backend %s, expecting at least %d", data->d_buffer.size(), downstream->getNameWithAddr(), sizeof(dnsheader));
}
return false;
}
- const dnsheader * responseHeader = reinterpret_cast<const dnsheader*>(data->d_buffer.data());
- if (responseHeader->id != data->d_queryID) {
+ dnsheader_aligned responseHeader(data->d_buffer.data());
+ if (responseHeader.get()->id != data->d_queryID) {
+ ++data->d_ds->d_healthCheckMetrics.d_mismatchErrors;
if (g_verboseHealthChecks) {
- infolog("Invalid health check response id %d from backend %s, expecting %d", data->d_queryID, ds->getNameWithAddr(), data->d_queryID);
+ infolog("Invalid health check response id %d from backend %s, expecting %d", responseHeader.get()->id, downstream->getNameWithAddr(), data->d_queryID);
}
return false;
}
- if (!responseHeader->qr) {
+ if (!responseHeader.get()->qr) {
+ ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
if (g_verboseHealthChecks) {
- infolog("Invalid health check response from backend %s, expecting QR to be set", ds->getNameWithAddr());
+ infolog("Invalid health check response from backend %s, expecting QR to be set", downstream->getNameWithAddr());
}
return false;
}
- if (responseHeader->rcode == RCode::ServFail) {
+ if (responseHeader.get()->rcode == RCode::ServFail) {
+ ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
if (g_verboseHealthChecks) {
- infolog("Backend %s responded to health check with ServFail", ds->getNameWithAddr());
+ infolog("Backend %s responded to health check with ServFail", downstream->getNameWithAddr());
}
return false;
}
- if (ds->d_config.mustResolve && (responseHeader->rcode == RCode::NXDomain || responseHeader->rcode == RCode::Refused)) {
+ if (downstream->d_config.mustResolve && (responseHeader.get()->rcode == RCode::NXDomain || responseHeader.get()->rcode == RCode::Refused)) {
+ ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
if (g_verboseHealthChecks) {
- infolog("Backend %s responded to health check with %s while mustResolve is set", ds->getNameWithAddr(), responseHeader->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
+ infolog("Backend %s responded to health check with %s while mustResolve is set", downstream->getNameWithAddr(), responseHeader.get()->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
}
return false;
}
- uint16_t receivedType;
- uint16_t receivedClass;
- DNSName receivedName(reinterpret_cast<const char*>(data->d_buffer.data()), data->d_buffer.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
+ uint16_t receivedType{0};
+ uint16_t receivedClass{0};
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName receivedName(reinterpret_cast<const char*>(data->d_buffer.data()), static_cast<int>(data->d_buffer.size()), sizeof(dnsheader), false, &receivedType, &receivedClass);
if (receivedName != data->d_checkName || receivedType != data->d_checkType || receivedClass != data->d_checkClass) {
+ ++data->d_ds->d_healthCheckMetrics.d_mismatchErrors;
if (g_verboseHealthChecks) {
- infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", ds->getNameWithAddr(), receivedName.toLogString(), data->d_checkName.toLogString(), QType(receivedType).toString(), QType(data->d_checkType).toString(), receivedClass, data->d_checkClass);
+ infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", downstream->getNameWithAddr(), receivedName.toLogString(), data->d_checkName.toLogString(), QType(receivedType).toString(), QType(data->d_checkType).toString(), receivedClass, data->d_checkClass);
}
return false;
}
}
- catch(const std::exception& e)
- {
+ catch (const std::exception& e) {
+ ++data->d_ds->d_healthCheckMetrics.d_parseErrors;
if (g_verboseHealthChecks) {
- infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
+ infolog("Error checking the health of backend %s: %s", downstream->getNameWithAddr(), e.what());
}
return false;
}
- catch(...)
- {
+ catch (...) {
if (g_verboseHealthChecks) {
- infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
+ infolog("Unknown exception while checking the health of backend %s", downstream->getNameWithAddr());
}
return false;
}
class HealthCheckQuerySender : public TCPQuerySender
{
public:
- HealthCheckQuerySender(std::shared_ptr<HealthCheckData>& data): d_data(data)
+ HealthCheckQuerySender(std::shared_ptr<HealthCheckData>& data) :
+ d_data(data)
{
}
+ HealthCheckQuerySender(const HealthCheckQuerySender&) = default;
+ HealthCheckQuerySender(HealthCheckQuerySender&&) = default;
+ HealthCheckQuerySender& operator=(const HealthCheckQuerySender&) = default;
+ HealthCheckQuerySender& operator=(HealthCheckQuerySender&&) = default;
+ ~HealthCheckQuerySender() override = default;
- ~HealthCheckQuerySender()
- {
- }
-
- bool active() const override
+ [[nodiscard]] bool active() const override
{
return true;
}
throw std::runtime_error("Unexpected XFR reponse to a health check query");
}
- void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
+ void notifyIOError(const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
{
+ ++d_data->d_ds->d_healthCheckMetrics.d_networkErrors;
d_data->d_ds->submitHealthCheckResult(d_data->d_initial, false);
}
std::shared_ptr<HealthCheckData> d_data;
};
-static void healthCheckUDPCallback(int fd, FDMultiplexer::funcparam_t& param)
+static void healthCheckUDPCallback(int descriptor, FDMultiplexer::funcparam_t& param)
{
auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
- data->d_mplexer.removeReadFD(fd);
+ ssize_t got = 0;
ComboAddress from;
- from.sin4.sin_family = data->d_ds->d_config.remote.sin4.sin_family;
- auto fromlen = from.getSocklen();
- data->d_buffer.resize(512);
- auto got = recvfrom(data->d_udpSocket.getHandle(), &data->d_buffer.at(0), data->d_buffer.size(), 0, reinterpret_cast<sockaddr *>(&from), &fromlen);
- if (got < 0) {
- if (g_verboseHealthChecks) {
- infolog("Error receiving health check response from %s: %s", data->d_ds->d_config.remote.toStringWithPort(), stringerror());
+ do {
+ from.sin4.sin_family = data->d_ds->d_config.remote.sin4.sin_family;
+ auto fromlen = from.getSocklen();
+ data->d_buffer.resize(512);
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ got = recvfrom(data->d_udpSocket.getHandle(), data->d_buffer.data(), data->d_buffer.size(), 0, reinterpret_cast<sockaddr*>(&from), &fromlen);
+ if (got < 0) {
+ int savederrno = errno;
+ if (savederrno == EINTR) {
+ /* interrupted before any data was available, let's try again */
+ continue;
+ }
+ if (savederrno == EWOULDBLOCK || savederrno == EAGAIN) {
+ /* spurious wake-up, let's return to sleep */
+ return;
+ }
+
+ if (g_verboseHealthChecks) {
+ infolog("Error receiving health check response from %s: %s", data->d_ds->d_config.remote.toStringWithPort(), stringerror(savederrno));
+ }
+ ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
+ data->d_ds->submitHealthCheckResult(data->d_initial, false);
+ data->d_mplexer.removeReadFD(descriptor);
+ return;
}
- data->d_ds->submitHealthCheckResult(data->d_initial, false);
- return;
- }
+ } while (got < 0);
+
+ data->d_buffer.resize(static_cast<size_t>(got));
/* we are using a connected socket but hey.. */
if (from != data->d_ds->d_config.remote) {
if (g_verboseHealthChecks) {
infolog("Invalid health check response received from %s, expecting one from %s", from.toStringWithPort(), data->d_ds->d_config.remote.toStringWithPort());
}
+ ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
data->d_ds->submitHealthCheckResult(data->d_initial, false);
return;
}
+ data->d_mplexer.removeReadFD(descriptor);
data->d_ds->submitHealthCheckResult(data->d_initial, handleResponse(data));
}
-static void healthCheckTCPCallback(int fd, FDMultiplexer::funcparam_t& param)
+static void healthCheckTCPCallback(int descriptor, FDMultiplexer::funcparam_t& param)
{
auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
ioState = data->d_tcpHandler->tryRead(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
if (ioState == IOState::Done) {
data->d_bufferPos = 0;
- uint16_t responseSize;
- memcpy(&responseSize, &data->d_buffer.at(0), sizeof(responseSize));
+ uint16_t responseSize{0};
+ memcpy(&responseSize, data->d_buffer.data(), sizeof(responseSize));
data->d_buffer.resize(ntohs(responseSize));
data->d_tcpState = HealthCheckData::TCPState::ReadingResponse;
}
ioGuard.release();
}
catch (const std::exception& e) {
+ ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
data->d_ds->submitHealthCheckResult(data->d_initial, false);
if (g_verboseHealthChecks) {
infolog("Error checking the health of backend %s: %s", data->d_ds->getNameWithAddr(), e.what());
}
}
-bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, bool initialCheck)
+bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& downstream, bool initialCheck)
{
- try
- {
+ try {
uint16_t queryID = dnsdist::getRandomDNSID();
- DNSName checkName = ds->d_config.checkName;
- uint16_t checkType = ds->d_config.checkType.getCode();
- uint16_t checkClass = ds->d_config.checkClass;
- dnsheader checkHeader;
+ DNSName checkName = downstream->d_config.checkName;
+ uint16_t checkType = downstream->d_config.checkType.getCode();
+ uint16_t checkClass = downstream->d_config.checkClass;
+ dnsheader checkHeader{};
memset(&checkHeader, 0, sizeof(checkHeader));
checkHeader.qdcount = htons(1);
checkHeader.id = queryID;
checkHeader.rd = true;
- if (ds->d_config.setCD) {
+ if (downstream->d_config.setCD) {
checkHeader.cd = true;
}
- if (ds->d_config.checkFunction) {
+ if (downstream->d_config.checkFunction) {
auto lock = g_lua.lock();
- auto ret = ds->d_config.checkFunction(checkName, checkType, checkClass, &checkHeader);
+ auto ret = downstream->d_config.checkFunction(checkName, checkType, checkClass, &checkHeader);
checkName = std::get<0>(ret);
checkType = std::get<1>(ret);
checkClass = std::get<2>(ret);
uint16_t packetSize = packet.size();
std::string proxyProtocolPayload;
size_t proxyProtocolPayloadSize = 0;
- if (ds->d_config.useProxyProtocol) {
+ if (downstream->d_config.useProxyProtocol) {
proxyProtocolPayload = makeLocalProxyHeader();
proxyProtocolPayloadSize = proxyProtocolPayload.size();
- if (!ds->isDoH()) {
+ if (!downstream->isDoH()) {
packet.insert(packet.begin(), proxyProtocolPayload.begin(), proxyProtocolPayload.end());
}
}
- Socket sock(ds->d_config.remote.sin4.sin_family, ds->doHealthcheckOverTCP() ? SOCK_STREAM : SOCK_DGRAM);
+ Socket sock(downstream->d_config.remote.sin4.sin_family, downstream->doHealthcheckOverTCP() ? SOCK_STREAM : SOCK_DGRAM);
sock.setNonBlocking();
#ifdef SO_BINDTODEVICE
- if (!ds->d_config.sourceItfName.empty()) {
- int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, ds->d_config.sourceItfName.c_str(), ds->d_config.sourceItfName.length());
+ if (!downstream->d_config.sourceItfName.empty()) {
+ int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, downstream->d_config.sourceItfName.c_str(), downstream->d_config.sourceItfName.length());
if (res != 0 && g_verboseHealthChecks) {
- infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", ds->getNameWithAddr(), stringerror());
+ infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", downstream->getNameWithAddr(), stringerror());
}
}
#endif
- if (!IsAnyAddress(ds->d_config.sourceAddr)) {
- sock.setReuseAddr();
+ if (!IsAnyAddress(downstream->d_config.sourceAddr)) {
+ if (downstream->doHealthcheckOverTCP()) {
+ sock.setReuseAddr();
+ }
#ifdef IP_BIND_ADDRESS_NO_PORT
- if (ds->d_config.ipBindAddrNoPort) {
+ if (downstream->d_config.ipBindAddrNoPort) {
SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
}
#endif
- sock.bind(ds->d_config.sourceAddr);
+ sock.bind(downstream->d_config.sourceAddr, false);
}
- auto data = std::make_shared<HealthCheckData>(*mplexer, ds, std::move(checkName), checkType, checkClass, queryID);
+ auto data = std::make_shared<HealthCheckData>(*mplexer, downstream, std::move(checkName), checkType, checkClass, queryID);
data->d_initial = initialCheck;
gettimeofday(&data->d_ttd, nullptr);
- data->d_ttd.tv_sec += ds->d_config.checkTimeout / 1000; /* ms to seconds */
- data->d_ttd.tv_usec += (ds->d_config.checkTimeout % 1000) * 1000; /* remaining ms to us */
+ data->d_ttd.tv_sec += static_cast<decltype(data->d_ttd.tv_sec)>(downstream->d_config.checkTimeout / 1000); /* ms to seconds */
+ data->d_ttd.tv_usec += static_cast<decltype(data->d_ttd.tv_usec)>((downstream->d_config.checkTimeout % 1000) * 1000); /* remaining ms to us */
normalizeTV(data->d_ttd);
- if (!ds->doHealthcheckOverTCP()) {
- sock.connect(ds->d_config.remote);
+ if (!downstream->doHealthcheckOverTCP()) {
+ sock.connect(downstream->d_config.remote);
data->d_udpSocket = std::move(sock);
- ssize_t sent = udpClientSendRequestToBackend(ds, data->d_udpSocket.getHandle(), packet, true);
+ ssize_t sent = udpClientSendRequestToBackend(downstream, data->d_udpSocket.getHandle(), packet, true);
if (sent < 0) {
int ret = errno;
if (g_verboseHealthChecks) {
- infolog("Error while sending a health check query to backend %s: %d", ds->getNameWithAddr(), ret);
+ infolog("Error while sending a health check query (ID %d) to backend %s: %d", queryID, downstream->getNameWithAddr(), ret);
}
return false;
}
mplexer->addReadFD(data->d_udpSocket.getHandle(), &healthCheckUDPCallback, data, &data->d_ttd);
}
- else if (ds->isDoH()) {
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ else if (downstream->isDoH()) {
InternalQuery query(std::move(packet), InternalQueryState());
query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
auto sender = std::shared_ptr<TCPQuerySender>(new HealthCheckQuerySender(data));
- if (!sendH2Query(ds, mplexer, sender, std::move(query), true)) {
+ if (!sendH2Query(downstream, mplexer, sender, std::move(query), true)) {
data->d_ds->submitHealthCheckResult(data->d_initial, false);
}
}
+#endif
else {
- time_t now = time(nullptr);
- data->d_tcpHandler = std::make_unique<TCPIOHandler>(ds->d_config.d_tlsSubjectName, ds->d_config.d_tlsSubjectIsAddr, sock.releaseHandle(), timeval{ds->d_config.checkTimeout,0}, ds->d_tlsCtx);
+ data->d_tcpHandler = std::make_unique<TCPIOHandler>(downstream->d_config.d_tlsSubjectName, downstream->d_config.d_tlsSubjectIsAddr, sock.releaseHandle(), timeval{downstream->d_config.checkTimeout, 0}, downstream->d_tlsCtx);
data->d_ioState = std::make_unique<IOStateHandler>(*mplexer, data->d_tcpHandler->getDescriptor());
- if (ds->d_tlsCtx) {
+ if (downstream->d_tlsCtx) {
try {
- auto tlsSession = g_sessionCache.getSession(ds->getID(), now);
+ time_t now = time(nullptr);
+ auto tlsSession = g_sessionCache.getSession(downstream->getID(), now);
if (tlsSession) {
data->d_tcpHandler->setTLSSession(tlsSession);
}
}
catch (const std::exception& e) {
- vinfolog("Unable to restore a TLS session for the DoT healthcheck: %s", e.what());
+ vinfolog("Unable to restore a TLS session for the DoT healthcheck for backend %s: %s", downstream->getNameWithAddr(), e.what());
}
}
- data->d_tcpHandler->tryConnect(ds->d_config.tcpFastOpen, ds->d_config.remote);
+ data->d_tcpHandler->tryConnect(downstream->d_config.tcpFastOpen, downstream->d_config.remote);
- const uint8_t sizeBytes[] = { static_cast<uint8_t>(packetSize / 256), static_cast<uint8_t>(packetSize % 256) };
- packet.insert(packet.begin() + proxyProtocolPayloadSize, sizeBytes, sizeBytes + 2);
+ const std::array<uint8_t, 2> sizeBytes = {static_cast<uint8_t>(packetSize / 256), static_cast<uint8_t>(packetSize % 256)};
+ packet.insert(packet.begin() + static_cast<ssize_t>(proxyProtocolPayloadSize), sizeBytes.begin(), sizeBytes.end());
data->d_buffer = std::move(packet);
auto ioState = data->d_tcpHandler->tryWrite(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
return true;
}
- catch (const std::exception& e)
- {
+ catch (const std::exception& e) {
if (g_verboseHealthChecks) {
- infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
+ infolog("Error checking the health of backend %s: %s", downstream->getNameWithAddr(), e.what());
}
return false;
}
- catch (...)
- {
+ catch (...) {
if (g_verboseHealthChecks) {
- infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
+ infolog("Unknown exception while checking the health of backend %s", downstream->getNameWithAddr());
}
return false;
}
void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
{
while (mplexer.getWatchedFDCount(false) > 0 || mplexer.getWatchedFDCount(true) > 0) {
- struct timeval now;
+ struct timeval now
+ {
+ };
int ret = mplexer.run(&now, 100);
if (ret == -1) {
if (g_verboseHealthChecks) {
}
break;
}
+ if (ret > 0) {
+ /* we got at least one event other than a timeout */
+ continue;
+ }
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
handleH2Timeouts(mplexer, now);
+#endif
auto timeouts = mplexer.getTimeouts(now);
for (const auto& timeout : timeouts) {
auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
try {
+ /* UDP does not have an IO state, H2 is handled separately */
if (data->d_ioState) {
data->d_ioState.reset();
}
mplexer.removeReadFD(timeout.first);
}
if (g_verboseHealthChecks) {
- infolog("Timeout while waiting for the health check response from backend %s", data->d_ds->getNameWithAddr());
+ infolog("Timeout while waiting for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
}
+ ++data->d_ds->d_healthCheckMetrics.d_timeOuts;
data->d_ds->submitHealthCheckResult(initial, false);
}
catch (const std::exception& e) {
+ /* this is not supposed to happen as the file descriptor has to be
+ there for us to reach that code, and the submission code should not throw,
+ but let's provide a nice error message if it ever does. */
if (g_verboseHealthChecks) {
- infolog("Error while dealing with a timeout for the health check response from backend %s: %s", data->d_ds->getNameWithAddr(), e.what());
+ infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s: %s", data->d_queryID, data->d_ds->getNameWithAddr(), e.what());
}
}
catch (...) {
+ /* this is even less likely to happen */
if (g_verboseHealthChecks) {
- infolog("Error while dealing with a timeout for the health check response from backend %s", data->d_ds->getNameWithAddr());
+ infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
}
}
}
}
auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
try {
+ /* UDP does not block while writing, H2 is handled separately */
data->d_ioState.reset();
if (g_verboseHealthChecks) {
- infolog("Timeout while waiting for the health check response from backend %s", data->d_ds->getNameWithAddr());
+ infolog("Timeout while waiting for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
}
+ ++data->d_ds->d_healthCheckMetrics.d_timeOuts;
data->d_ds->submitHealthCheckResult(initial, false);
}
catch (const std::exception& e) {
+ /* this is not supposed to happen as the submission code should not throw,
+ but let's provide a nice error message if it ever does. */
if (g_verboseHealthChecks) {
- infolog("Error while dealing with a timeout for the health check response from backend %s: %s", data->d_ds->getNameWithAddr(), e.what());
+ infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s: %s", data->d_queryID, data->d_ds->getNameWithAddr(), e.what());
}
}
catch (...) {
+ /* this is even less likely to happen */
if (g_verboseHealthChecks) {
- infolog("Error while dealing with a timeout for the health check response from backend %s", data->d_ds->getNameWithAddr());
+ infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
}
}
}
extern bool g_verboseHealthChecks;
-bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, bool initial=false);
-void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial=false);
-
+bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& downstream, bool initial = false);
+void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial = false);
+++ /dev/null
-../dnsdist-idstate.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <unordered_map>
+
+#include "config.h"
+#include "dnscrypt.hh"
+#include "dnsname.hh"
+#include "dnsdist-protocols.hh"
+#include "ednsextendederror.hh"
+#include "gettime.hh"
+#include "iputils.hh"
+#include "noinitvector.hh"
+#include "uuid-utils.hh"
+
+struct ClientState;
+struct DOHUnitInterface;
+struct DOQUnit;
+struct DOH3Unit;
+class DNSCryptQuery;
+class DNSDistPacketCache;
+
+using QTag = std::unordered_map<string, string>;
+using HeadersMap = std::unordered_map<std::string, std::string>;
+
+struct StopWatch
+{
+ StopWatch(bool realTime = false) :
+ d_needRealTime(realTime)
+ {
+ }
+
+ void start()
+ {
+ d_start = getCurrentTime();
+ }
+
+ void set(const struct timespec& from)
+ {
+ d_start = from;
+ }
+
+ double udiff() const
+ {
+ struct timespec now = getCurrentTime();
+ return 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
+ }
+
+ double udiffAndSet()
+ {
+ struct timespec now = getCurrentTime();
+ auto ret = 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
+ d_start = now;
+ return ret;
+ }
+
+ struct timespec getStartTime() const
+ {
+ return d_start;
+ }
+
+ struct timespec d_start
+ {
+ 0, 0
+ };
+
+private:
+ struct timespec getCurrentTime() const
+ {
+ struct timespec now;
+ if (gettime(&now, d_needRealTime) < 0) {
+ unixDie("Getting timestamp");
+ }
+ return now;
+ }
+
+ bool d_needRealTime;
+};
+
+class CrossProtocolContext;
+
+struct InternalQueryState
+{
+ struct ProtoBufData
+ {
+ std::optional<boost::uuids::uuid> uniqueId{std::nullopt}; // 17
+ std::string d_deviceName;
+ std::string d_deviceID;
+ std::string d_requestorID;
+ };
+
+ InternalQueryState()
+ {
+ origDest.sin4.sin_family = 0;
+ }
+
+ InternalQueryState(InternalQueryState&& rhs) = default;
+ InternalQueryState& operator=(InternalQueryState&& rhs) = default;
+
+ InternalQueryState(const InternalQueryState& orig) = delete;
+ InternalQueryState& operator=(const InternalQueryState& orig) = delete;
+
+ bool isXSK() const noexcept
+ {
+#ifdef HAVE_XSK
+ return !xskPacketHeader.empty();
+#else
+ return false;
+#endif /* HAVE_XSK */
+ }
+
+ boost::optional<Netmask> subnet{boost::none}; // 40
+ std::string poolName; // 32
+ ComboAddress origRemote; // 28
+ ComboAddress origDest; // 28
+ ComboAddress hopRemote;
+ ComboAddress hopLocal;
+ DNSName qname; // 24
+#ifdef HAVE_XSK
+ PacketBuffer xskPacketHeader; // 24
+#endif /* HAVE_XSK */
+ StopWatch queryRealTime{true}; // 24
+ std::shared_ptr<DNSDistPacketCache> packetCache{nullptr}; // 16
+ std::unique_ptr<DNSCryptQuery> dnsCryptQuery{nullptr}; // 8
+ std::unique_ptr<QTag> qTag{nullptr}; // 8
+ std::unique_ptr<PacketBuffer> d_packet{nullptr}; // Initial packet, so we can restart the query from the response path if needed // 8
+ std::unique_ptr<ProtoBufData> d_protoBufData{nullptr};
+ std::unique_ptr<EDNSExtendedError> d_extendedError{nullptr};
+ boost::optional<uint32_t> tempFailureTTL{boost::none}; // 8
+ ClientState* cs{nullptr}; // 8
+ std::unique_ptr<DOHUnitInterface> du; // 8
+ size_t d_proxyProtocolPayloadSize{0}; // 8
+ std::unique_ptr<DOQUnit> doqu{nullptr}; // 8
+ std::unique_ptr<DOH3Unit> doh3u{nullptr}; // 8
+ int32_t d_streamID{-1}; // 4
+ uint32_t cacheKey{0}; // 4
+ uint32_t cacheKeyNoECS{0}; // 4
+ // DoH-only */
+ uint32_t cacheKeyUDP{0}; // 4
+ uint32_t ttlCap{0}; // cap the TTL _after_ inserting into the packet cache // 4
+ int backendFD{-1}; // 4
+ int delayMsec{0};
+ uint16_t qtype{0}; // 2
+ uint16_t qclass{0}; // 2
+ // origID is in network-byte order
+ uint16_t origID{0}; // 2
+ uint16_t origFlags{0}; // 2
+ uint16_t cacheFlags{0}; // DNS flags as sent to the backend // 2
+ uint16_t udpPayloadSize{0}; // Max UDP payload size from the query // 2
+ dnsdist::Protocol protocol; // 1
+ bool ednsAdded{false};
+ bool ecsAdded{false};
+ bool skipCache{false};
+ bool dnssecOK{false};
+ bool useZeroScope{false};
+ bool forwardedOverUDP{false};
+ bool selfGenerated{false};
+};
+
+struct IDState
+{
+ IDState()
+ {
+ }
+
+ IDState(const IDState& orig) = delete;
+ IDState(IDState&& rhs) noexcept :
+ internal(std::move(rhs.internal))
+ {
+ inUse.store(rhs.inUse.load());
+ age.store(rhs.age.load());
+ }
+
+ IDState& operator=(IDState&& rhs) noexcept
+ {
+ inUse.store(rhs.inUse.load());
+ age.store(rhs.age.load());
+ internal = std::move(rhs.internal);
+ return *this;
+ }
+
+ bool isInUse() const
+ {
+ return inUse;
+ }
+
+ /* For performance reasons we don't want to use a lock here, but that means
+ we need to be very careful when modifying this value. Modifications happen
+ from:
+ - one of the UDP or DoH 'client' threads receiving a query, selecting a backend
+ then picking one of the states associated to this backend (via the idOffset).
+ Most of the time this state should not be in use and usageIndicator is -1, but we
+ might not yet have received a response for the query previously associated to this
+ state, meaning that we will 'reuse' this state and erase the existing state.
+ If we ever receive a response for this state, it will be discarded. This is
+ mostly fine for UDP except that we still need to be careful in order to miss
+ the 'outstanding' counters, which should only be increased when we are picking
+ an empty state, and not when reusing ;
+ For DoH, though, we have dynamically allocated a DOHUnit object that needs to
+ be freed, as well as internal objects internals to libh2o.
+ - one of the UDP receiver threads receiving a response from a backend, picking
+ the corresponding state and sending the response to the client ;
+ - the 'healthcheck' thread scanning the states to actively discover timeouts,
+ mostly to keep some counters like the 'outstanding' one sane.
+
+ We have two flags:
+ - inUse tells us if there currently is a in-flight query whose state is stored
+ in this state
+ - locked tells us whether someone currently owns the state, so no-one else can touch
+ it
+ */
+ InternalQueryState internal;
+ std::atomic<uint16_t> age{0};
+
+ class StateGuard
+ {
+ public:
+ StateGuard(IDState& ids) :
+ d_ids(ids)
+ {
+ }
+ ~StateGuard()
+ {
+ d_ids.release();
+ }
+ StateGuard(const StateGuard&) = delete;
+ StateGuard(StateGuard&&) = delete;
+ StateGuard& operator=(const StateGuard&) = delete;
+ StateGuard& operator=(StateGuard&&) = delete;
+
+ private:
+ IDState& d_ids;
+ };
+
+ [[nodiscard]] std::optional<StateGuard> acquire()
+ {
+ bool expected = false;
+ if (locked.compare_exchange_strong(expected, true)) {
+ return std::optional<StateGuard>(*this);
+ }
+ return std::nullopt;
+ }
+
+ void release()
+ {
+ locked.store(false);
+ }
+
+ std::atomic<bool> inUse{false}; // 1
+
+private:
+ std::atomic<bool> locked{false}; // 1
+};
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "dnsdist-internal-queries.hh"
+#include "dnsdist-nghttp2-in.hh"
#include "dnsdist-tcp.hh"
#include "doh.hh"
+#include "doq.hh"
std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dq);
namespace dnsdist
{
-std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, bool isResponse)
+std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
{
- auto protocol = dq.getProtocol();
+ auto protocol = dnsQuestion.getProtocol();
if (protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP) {
- return getUDPCrossProtocolQueryFromDQ(dq);
+ return getUDPCrossProtocolQueryFromDQ(dnsQuestion);
}
#ifdef HAVE_DNS_OVER_HTTPS
else if (protocol == dnsdist::Protocol::DoH) {
- return getDoHCrossProtocolQueryFromDQ(dq, isResponse);
+#ifdef HAVE_LIBH2OEVLOOP
+ if (dnsQuestion.ids.cs->dohFrontend->d_library == "h2o") {
+ return getDoHCrossProtocolQueryFromDQ(dnsQuestion, isResponse);
+ }
+#endif /* HAVE_LIBH2OEVLOOP */
+ return getTCPCrossProtocolQueryFromDQ(dnsQuestion);
+ }
+#endif
+#ifdef HAVE_DNS_OVER_QUIC
+ else if (protocol == dnsdist::Protocol::DoQ) {
+ return getDOQCrossProtocolQueryFromDQ(dnsQuestion, isResponse);
+ }
+#endif
+#ifdef HAVE_DNS_OVER_HTTP3
+ else if (protocol == dnsdist::Protocol::DoH3) {
+ return getDOH3CrossProtocolQueryFromDQ(dnsQuestion, isResponse);
}
#endif
else {
- return getTCPCrossProtocolQueryFromDQ(dq);
+ return getTCPCrossProtocolQueryFromDQ(dnsQuestion);
}
}
}
namespace dnsdist
{
-std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, bool isResponse);
+std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse);
}
return false;
}
}
- catch(const std::exception& e) {
- warnlog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
+ catch (const std::exception& e) {
+ vinfolog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
}
return false;
}
return false;
}
}
- catch(const std::exception& e) {
- warnlog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
+ catch (const std::exception& e) {
+ vinfolog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
}
return false;
}
return false;
}
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
vinfolog("Error while looking up a range from LMDB file '%s', database '%s': %s", d_fname, d_dbName, e.what());
}
return false;
d_nextCheck = now + d_refreshDelay;
d_refreshing.clear();
}
- catch(...) {
+ catch (...) {
d_refreshing.clear();
throw;
}
}
}
}
- catch(const std::exception& e) {
- warnlog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname, e.what());
+ catch (const std::exception& e) {
+ vinfolog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname, e.what());
}
return false;
}
return (*cdb)->keyExists(key);
}
}
- catch(const std::exception& e) {
- warnlog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname, e.what());
+ catch (const std::exception& e) {
+ vinfolog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname, e.what());
}
return false;
}
#include "dnsdist-lua.hh"
#include "dnsdist-lua-ffi.hh"
#include "dolog.hh"
+#include "dns_random.hh"
GlobalStateHolder<ServerPolicy> g_policy;
bool g_roundrobinFailOnNoServer{false};
size_t usableServers = 0;
for (const auto& d : servers) {
if (d.second->isUp()) {
- poss[usableServers] = std::make_pair(std::make_tuple(d.second->outstanding.load(), d.second->d_config.order, d.second->getRelevantLatencyUsec()), d.first);
+ poss.at(usableServers) = std::pair(std::tuple(d.second->outstanding.load(), d.second->d_config.order, d.second->getRelevantLatencyUsec()), d.first);
usableServers++;
}
}
sum += d.second->d_config.d_weight;
}
- poss[usableServers] = std::make_pair(sum, d.first);
+ poss.at(usableServers) = std::pair(sum, d.first);
usableServers++;
}
}
shared_ptr<DownstreamState> wrandom(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
{
- return valrandom(random(), servers);
+ return valrandom(dns_random_uint32(), servers);
}
uint32_t g_hashperturb;
} else {
vinfolog("Setting default pool server selection policy to %s", policy->getName());
}
- pool->policy = policy;
+ pool->policy = std::move(policy);
}
void addServerToPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server)
+++ /dev/null
-../dnsdist-lbpolicies.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+struct dnsdist_ffi_servers_list_t;
+struct dnsdist_ffi_server_t;
+struct dnsdist_ffi_dnsquestion_t;
+
+struct DownstreamState;
+
+struct PerThreadPoliciesState;
+
+class ServerPolicy
+{
+public:
+ template <class T>
+ using NumberedVector = std::vector<std::pair<unsigned int, T>>;
+ using NumberedServerVector = NumberedVector<shared_ptr<DownstreamState>>;
+ typedef std::function<shared_ptr<DownstreamState>(const NumberedServerVector& servers, const DNSQuestion*)> policyfunc_t;
+ typedef std::function<unsigned int(dnsdist_ffi_servers_list_t* servers, dnsdist_ffi_dnsquestion_t* dq)> ffipolicyfunc_t;
+
+ ServerPolicy(const std::string& name_, policyfunc_t policy_, bool isLua_) :
+ d_name(name_), d_policy(std::move(policy_)), d_isLua(isLua_)
+ {
+ }
+
+ ServerPolicy(const std::string& name_, ffipolicyfunc_t policy_) :
+ d_name(name_), d_ffipolicy(std::move(policy_)), d_isLua(true), d_isFFI(true)
+ {
+ }
+
+ /* create a per-thread FFI policy */
+ ServerPolicy(const std::string& name_, const std::string& code);
+
+ ServerPolicy()
+ {
+ }
+
+ std::shared_ptr<DownstreamState> getSelectedBackend(const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dq) const;
+
+ const std::string& getName() const
+ {
+ return d_name;
+ }
+
+ std::string toString() const
+ {
+ return string("ServerPolicy") + (d_isLua ? " (Lua)" : "") + " \"" + d_name + "\"";
+ }
+
+private:
+ struct PerThreadState
+ {
+ LuaContext d_luaContext;
+ std::unordered_map<std::string, ffipolicyfunc_t> d_policies;
+ bool d_initialized{false};
+ };
+
+ const ffipolicyfunc_t& getPerThreadPolicy() const;
+ static thread_local PerThreadState t_perThreadState;
+
+public:
+ std::string d_name;
+ std::string d_perThreadPolicyCode;
+
+ policyfunc_t d_policy;
+ ffipolicyfunc_t d_ffipolicy;
+
+ bool d_isLua{false};
+ bool d_isFFI{false};
+ bool d_isPerThread{false};
+};
+
+struct ServerPool;
+
+using pools_t = map<std::string, std::shared_ptr<ServerPool>>;
+std::shared_ptr<ServerPool> getPool(const pools_t& pools, const std::string& poolName);
+std::shared_ptr<ServerPool> createPoolIfNotExists(pools_t& pools, const string& poolName);
+void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr<ServerPolicy> policy);
+void addServerToPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server);
+void removeServerFromPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server);
+
+const std::shared_ptr<const ServerPolicy::NumberedServerVector> getDownstreamCandidates(const map<std::string, std::shared_ptr<ServerPool>>& pools, const std::string& poolName);
+
+std::shared_ptr<DownstreamState> firstAvailable(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+
+std::shared_ptr<DownstreamState> leastOutstanding(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+std::shared_ptr<DownstreamState> wrandom(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+std::shared_ptr<DownstreamState> whashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+std::shared_ptr<DownstreamState> whashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash);
+std::shared_ptr<DownstreamState> chashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+std::shared_ptr<DownstreamState> chashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash);
+std::shared_ptr<DownstreamState> roundrobin(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+
+extern double g_consistentHashBalancingFactor;
+extern double g_weightedBalancingFactor;
+extern uint32_t g_hashperturb;
+extern bool g_roundrobinFailOnNoServer;
+++ /dev/null
-../dnsdist-lua-actions.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "config.h"
+#include "threadname.hh"
+#include "dnsdist.hh"
+#include "dnsdist-async.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-lua-ffi.hh"
+#include "dnsdist-mac-address.hh"
+#include "dnsdist-protobuf.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-kvs.hh"
+#include "dnsdist-rule-chains.hh"
+#include "dnsdist-svc.hh"
+
+#include "dnstap.hh"
+#include "dnswriter.hh"
+#include "ednsoptions.hh"
+#include "fstrm_logger.hh"
+#include "remote_logger.hh"
+#include "svc-records.hh"
+
+#include <boost/optional/optional_io.hpp>
+
+#include "ipcipher.hh"
+
+class DropAction : public DNSAction
+{
+public:
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ return Action::Drop;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "drop";
+ }
+};
+
+class AllowAction : public DNSAction
+{
+public:
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ return Action::Allow;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "allow";
+ }
+};
+
+class NoneAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "no op";
+ }
+};
+
+class QPSAction : public DNSAction
+{
+public:
+ QPSAction(int limit) :
+ d_qps(QPSLimiter(limit, limit))
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (d_qps.lock()->check()) {
+ return Action::None;
+ }
+ return Action::Drop;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "qps limit to " + std::to_string(d_qps.lock()->getRate());
+ }
+
+private:
+ mutable LockGuarded<QPSLimiter> d_qps;
+};
+
+class DelayAction : public DNSAction
+{
+public:
+ DelayAction(int msec) :
+ d_msec(msec)
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ *ruleresult = std::to_string(d_msec);
+ return Action::Delay;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "delay by " + std::to_string(d_msec) + " ms";
+ }
+
+private:
+ int d_msec;
+};
+
+class TeeAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ TeeAction(const ComboAddress& rca, const boost::optional<ComboAddress>& lca, bool addECS = false, bool addProxyProtocol = false);
+ TeeAction(TeeAction& other) = delete;
+ TeeAction(TeeAction&& other) = delete;
+ TeeAction& operator=(TeeAction& other) = delete;
+ TeeAction& operator=(TeeAction&& other) = delete;
+ ~TeeAction() override;
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override;
+ [[nodiscard]] std::string toString() const override;
+ std::map<std::string, double> getStats() const override;
+
+private:
+ void worker();
+
+ ComboAddress d_remote;
+ std::thread d_worker;
+ Socket d_socket;
+ mutable std::atomic<unsigned long> d_senderrors{0};
+ unsigned long d_recverrors{0};
+ mutable std::atomic<unsigned long> d_queries{0};
+ stat_t d_responses{0};
+ stat_t d_nxdomains{0};
+ stat_t d_servfails{0};
+ stat_t d_refuseds{0};
+ stat_t d_formerrs{0};
+ stat_t d_notimps{0};
+ stat_t d_noerrors{0};
+ mutable stat_t d_tcpdrops{0};
+ stat_t d_otherrcode{0};
+ std::atomic<bool> d_pleaseQuit{false};
+ bool d_addECS{false};
+ bool d_addProxyProtocol{false};
+};
+
+TeeAction::TeeAction(const ComboAddress& rca, const boost::optional<ComboAddress>& lca, bool addECS, bool addProxyProtocol) :
+ d_remote(rca), d_socket(d_remote.sin4.sin_family, SOCK_DGRAM, 0), d_addECS(addECS), d_addProxyProtocol(addProxyProtocol)
+{
+ if (lca) {
+ d_socket.bind(*lca, false);
+ }
+ d_socket.connect(d_remote);
+ d_socket.setNonBlocking();
+ d_worker = std::thread([this]() {
+ worker();
+ });
+}
+
+TeeAction::~TeeAction()
+{
+ d_pleaseQuit = true;
+ close(d_socket.releaseHandle());
+ d_worker.join();
+}
+
+DNSAction::Action TeeAction::operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const
+{
+ if (dnsquestion->overTCP()) {
+ d_tcpdrops++;
+ return DNSAction::Action::None;
+ }
+
+ d_queries++;
+
+ PacketBuffer query;
+ if (d_addECS) {
+ query = dnsquestion->getData();
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+
+ std::string newECSOption;
+ generateECSOption(dnsquestion->ecs ? dnsquestion->ecs->getNetwork() : dnsquestion->ids.origRemote, newECSOption, dnsquestion->ecs ? dnsquestion->ecs->getBits() : dnsquestion->ecsPrefixLength);
+
+ if (!handleEDNSClientSubnet(query, dnsquestion->getMaximumSize(), dnsquestion->ids.qname.wirelength(), ednsAdded, ecsAdded, dnsquestion->ecsOverride, newECSOption)) {
+ return DNSAction::Action::None;
+ }
+ }
+
+ if (d_addProxyProtocol) {
+ auto proxyPayload = getProxyProtocolPayload(*dnsquestion);
+ if (query.empty()) {
+ query = dnsquestion->getData();
+ }
+ if (!addProxyProtocol(query, proxyPayload)) {
+ return DNSAction::Action::None;
+ }
+ }
+
+ {
+ const PacketBuffer& payload = query.empty() ? dnsquestion->getData() : query;
+ auto res = send(d_socket.getHandle(), payload.data(), payload.size(), 0);
+
+ if (res <= 0) {
+ d_senderrors++;
+ }
+ }
+
+ return DNSAction::Action::None;
+}
+
+std::string TeeAction::toString() const
+{
+ return "tee to " + d_remote.toStringWithPort();
+}
+
+std::map<std::string, double> TeeAction::getStats() const
+{
+ return {{"queries", d_queries},
+ {"responses", d_responses},
+ {"recv-errors", d_recverrors},
+ {"send-errors", d_senderrors},
+ {"noerrors", d_noerrors},
+ {"nxdomains", d_nxdomains},
+ {"refuseds", d_refuseds},
+ {"servfails", d_servfails},
+ {"other-rcode", d_otherrcode},
+ {"tcp-drops", d_tcpdrops}};
+}
+
+void TeeAction::worker()
+{
+ setThreadName("dnsdist/TeeWork");
+ std::array<char, s_udpIncomingBufferSize> packet{};
+ ssize_t res = 0;
+ const dnsheader_aligned dnsheader(packet.data());
+ for (;;) {
+ res = waitForData(d_socket.getHandle(), 0, 250000);
+ if (d_pleaseQuit) {
+ break;
+ }
+
+ if (res < 0) {
+ usleep(250000);
+ continue;
+ }
+ if (res == 0) {
+ continue;
+ }
+ res = recv(d_socket.getHandle(), packet.data(), packet.size(), 0);
+ if (static_cast<size_t>(res) <= sizeof(struct dnsheader)) {
+ d_recverrors++;
+ }
+ else {
+ d_responses++;
+ }
+
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+ if (dnsheader->rcode == RCode::NoError) {
+ d_noerrors++;
+ }
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+ else if (dnsheader->rcode == RCode::ServFail) {
+ d_servfails++;
+ }
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+ else if (dnsheader->rcode == RCode::NXDomain) {
+ d_nxdomains++;
+ }
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+ else if (dnsheader->rcode == RCode::Refused) {
+ d_refuseds++;
+ }
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+ else if (dnsheader->rcode == RCode::FormErr) {
+ d_formerrs++;
+ }
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+ else if (dnsheader->rcode == RCode::NotImp) {
+ d_notimps++;
+ }
+ }
+}
+
+class PoolAction : public DNSAction
+{
+public:
+ PoolAction(std::string pool, bool stopProcessing) :
+ d_pool(std::move(pool)), d_stopProcessing(stopProcessing) {}
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (d_stopProcessing) {
+ /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */
+ *ruleresult = d_pool;
+ return Action::Pool;
+ }
+ dnsquestion->ids.poolName = d_pool;
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "to pool " + d_pool;
+ }
+
+private:
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const std::string d_pool;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const bool d_stopProcessing;
+};
+
+class QPSPoolAction : public DNSAction
+{
+public:
+ QPSPoolAction(unsigned int limit, std::string pool, bool stopProcessing) :
+ d_qps(QPSLimiter(limit, limit)), d_pool(std::move(pool)), d_stopProcessing(stopProcessing) {}
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (d_qps.lock()->check()) {
+ if (d_stopProcessing) {
+ /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */
+ *ruleresult = d_pool;
+ return Action::Pool;
+ }
+ dnsquestion->ids.poolName = d_pool;
+ }
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "max " + std::to_string(d_qps.lock()->getRate()) + " to pool " + d_pool;
+ }
+
+private:
+ mutable LockGuarded<QPSLimiter> d_qps;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const std::string d_pool;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const bool d_stopProcessing;
+};
+
+class RCodeAction : public DNSAction
+{
+public:
+ RCodeAction(uint8_t rcode) :
+ d_rcode(rcode) {}
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+ header.rcode = d_rcode;
+ header.qr = true; // for good measure
+ setResponseHeadersFromConfig(header, d_responseConfig);
+ return true;
+ });
+ return Action::HeaderModify;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set rcode " + std::to_string(d_rcode);
+ }
+ [[nodiscard]] ResponseConfig& getResponseConfig()
+ {
+ return d_responseConfig;
+ }
+
+private:
+ ResponseConfig d_responseConfig;
+ uint8_t d_rcode;
+};
+
+class ERCodeAction : public DNSAction
+{
+public:
+ ERCodeAction(uint8_t rcode) :
+ d_rcode(rcode) {}
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+ header.rcode = (d_rcode & 0xF);
+ header.qr = true; // for good measure
+ setResponseHeadersFromConfig(header, d_responseConfig);
+ return true;
+ });
+ dnsquestion->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
+ return Action::HeaderModify;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set ercode " + ERCode::to_s(d_rcode);
+ }
+ [[nodiscard]] ResponseConfig& getResponseConfig()
+ {
+ return d_responseConfig;
+ }
+
+private:
+ ResponseConfig d_responseConfig;
+ uint8_t d_rcode;
+};
+
+class SpoofSVCAction : public DNSAction
+{
+public:
+ SpoofSVCAction(const LuaArray<SVCRecordParameters>& parameters)
+ {
+ d_payloads.reserve(parameters.size());
+
+ for (const auto& param : parameters) {
+ std::vector<uint8_t> payload;
+ if (!generateSVCPayload(payload, param.second)) {
+ throw std::runtime_error("Unable to generate a valid SVC record from the supplied parameters");
+ }
+
+ d_totalPayloadsSize += payload.size();
+ d_payloads.push_back(std::move(payload));
+
+ for (const auto& hint : param.second.ipv4hints) {
+ d_additionals4.insert({param.second.target, ComboAddress(hint)});
+ }
+
+ for (const auto& hint : param.second.ipv6hints) {
+ d_additionals6.insert({param.second.target, ComboAddress(hint)});
+ }
+ }
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ /* it will likely be a bit bigger than that because of additionals */
+ auto numberOfRecords = d_payloads.size();
+ const auto qnameWireLength = dnsquestion->ids.qname.wirelength();
+ if (dnsquestion->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + d_totalPayloadsSize)) {
+ return Action::None;
+ }
+
+ PacketBuffer newPacket;
+ newPacket.reserve(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + d_totalPayloadsSize);
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(newPacket, dnsquestion->ids.qname, dnsquestion->ids.qtype);
+ for (const auto& payload : d_payloads) {
+ packetWriter.startRecord(dnsquestion->ids.qname, dnsquestion->ids.qtype, d_responseConfig.ttl);
+ packetWriter.xfrBlob(payload);
+ packetWriter.commit();
+ }
+
+ if (newPacket.size() < dnsquestion->getMaximumSize()) {
+ for (const auto& additional : d_additionals4) {
+ packetWriter.startRecord(additional.first.isRoot() ? dnsquestion->ids.qname : additional.first, QType::A, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
+ packetWriter.xfrCAWithoutPort(4, additional.second);
+ packetWriter.commit();
+ }
+ }
+
+ if (newPacket.size() < dnsquestion->getMaximumSize()) {
+ for (const auto& additional : d_additionals6) {
+ packetWriter.startRecord(additional.first.isRoot() ? dnsquestion->ids.qname : additional.first, QType::AAAA, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
+ packetWriter.xfrCAWithoutPort(6, additional.second);
+ packetWriter.commit();
+ }
+ }
+
+ if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dnsquestion)) {
+ bool dnssecOK = ((getEDNSZ(*dnsquestion) & EDNS_HEADER_FLAG_DO) != 0);
+ packetWriter.addOpt(g_PayloadSizeSelfGenAnswers, 0, dnssecOK ? EDNS_HEADER_FLAG_DO : 0);
+ packetWriter.commit();
+ }
+
+ if (newPacket.size() >= dnsquestion->getMaximumSize()) {
+ /* sorry! */
+ return Action::None;
+ }
+
+ packetWriter.getHeader()->id = dnsquestion->getHeader()->id;
+ packetWriter.getHeader()->qr = true; // for good measure
+ setResponseHeadersFromConfig(*packetWriter.getHeader(), d_responseConfig);
+ dnsquestion->getMutableData() = std::move(newPacket);
+
+ return Action::HeaderModify;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "spoof SVC record ";
+ }
+
+ [[nodiscard]] ResponseConfig& getResponseConfig()
+ {
+ return d_responseConfig;
+ }
+
+private:
+ ResponseConfig d_responseConfig;
+ std::vector<std::vector<uint8_t>> d_payloads{};
+ std::set<std::pair<DNSName, ComboAddress>> d_additionals4{};
+ std::set<std::pair<DNSName, ComboAddress>> d_additionals6{};
+ size_t d_totalPayloadsSize{0};
+};
+
+class TCAction : public DNSAction
+{
+public:
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ return Action::Truncate;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "tc=1 answer";
+ }
+};
+
+class TCResponseAction : public DNSResponseAction
+{
+public:
+ DNSResponseAction::Action operator()(DNSResponse* dnsResponse, std::string* ruleresult) const override
+ {
+ return Action::Truncate;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "tc=1 answer";
+ }
+};
+
+class LuaAction : public DNSAction
+{
+public:
+ using func_t = std::function<std::tuple<int, boost::optional<string>>(DNSQuestion* dnsquestion)>;
+ LuaAction(LuaAction::func_t func) :
+ d_func(std::move(func))
+ {}
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ try {
+ DNSAction::Action result{};
+ {
+ auto lock = g_lua.lock();
+ auto ret = d_func(dnsquestion);
+ if (ruleresult != nullptr) {
+ if (boost::optional<std::string> rule = std::get<1>(ret)) {
+ *ruleresult = *rule;
+ }
+ else {
+ // default to empty string
+ ruleresult->clear();
+ }
+ }
+ result = static_cast<Action>(std::get<0>(ret));
+ }
+ dnsdist::handleQueuedAsynchronousEvents();
+ return result;
+ }
+ catch (const std::exception& e) {
+ warnlog("LuaAction failed inside Lua, returning ServFail: %s", e.what());
+ }
+ catch (...) {
+ warnlog("LuaAction failed inside Lua, returning ServFail: [unknown exception]");
+ }
+ return DNSAction::Action::ServFail;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "Lua script";
+ }
+
+private:
+ func_t d_func;
+};
+
+class LuaResponseAction : public DNSResponseAction
+{
+public:
+ using func_t = std::function<std::tuple<int, boost::optional<string>>(DNSResponse* response)>;
+ LuaResponseAction(LuaResponseAction::func_t func) :
+ d_func(std::move(func))
+ {}
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ try {
+ DNSResponseAction::Action result{};
+ {
+ auto lock = g_lua.lock();
+ auto ret = d_func(response);
+ if (ruleresult != nullptr) {
+ if (boost::optional<std::string> rule = std::get<1>(ret)) {
+ *ruleresult = *rule;
+ }
+ else {
+ // default to empty string
+ ruleresult->clear();
+ }
+ }
+ result = static_cast<Action>(std::get<0>(ret));
+ }
+ dnsdist::handleQueuedAsynchronousEvents();
+ return result;
+ }
+ catch (const std::exception& e) {
+ warnlog("LuaResponseAction failed inside Lua, returning ServFail: %s", e.what());
+ }
+ catch (...) {
+ warnlog("LuaResponseAction failed inside Lua, returning ServFail: [unknown exception]");
+ }
+ return DNSResponseAction::Action::ServFail;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "Lua response script";
+ }
+
+private:
+ func_t d_func;
+};
+
+class LuaFFIAction : public DNSAction
+{
+public:
+ using func_t = std::function<int(dnsdist_ffi_dnsquestion_t* dnsquestion)>;
+
+ LuaFFIAction(LuaFFIAction::func_t func) :
+ d_func(std::move(func))
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsdist_ffi_dnsquestion_t dqffi(dnsquestion);
+ try {
+ DNSAction::Action result{};
+ {
+ auto lock = g_lua.lock();
+ auto ret = d_func(&dqffi);
+ if (ruleresult != nullptr) {
+ if (dqffi.result) {
+ *ruleresult = *dqffi.result;
+ }
+ else {
+ // default to empty string
+ ruleresult->clear();
+ }
+ }
+ result = static_cast<DNSAction::Action>(ret);
+ }
+ dnsdist::handleQueuedAsynchronousEvents();
+ return result;
+ }
+ catch (const std::exception& e) {
+ warnlog("LuaFFIAction failed inside Lua, returning ServFail: %s", e.what());
+ }
+ catch (...) {
+ warnlog("LuaFFIAction failed inside Lua, returning ServFail: [unknown exception]");
+ }
+ return DNSAction::Action::ServFail;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "Lua FFI script";
+ }
+
+private:
+ func_t d_func;
+};
+
+class LuaFFIPerThreadAction : public DNSAction
+{
+public:
+ using func_t = std::function<int(dnsdist_ffi_dnsquestion_t* dnsquestion)>;
+
+ LuaFFIPerThreadAction(std::string code) :
+ d_functionCode(std::move(code)), d_functionID(s_functionsCounter++)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ try {
+ auto& state = t_perThreadStates[d_functionID];
+ if (!state.d_initialized) {
+ setupLuaFFIPerThreadContext(state.d_luaContext);
+ /* mark the state as initialized first so if there is a syntax error
+ we only try to execute the code once */
+ state.d_initialized = true;
+ state.d_func = state.d_luaContext.executeCode<func_t>(d_functionCode);
+ }
+
+ if (!state.d_func) {
+ /* the function was not properly initialized */
+ return DNSAction::Action::None;
+ }
+
+ dnsdist_ffi_dnsquestion_t dqffi(dnsquestion);
+ auto ret = state.d_func(&dqffi);
+ if (ruleresult != nullptr) {
+ if (dqffi.result) {
+ *ruleresult = *dqffi.result;
+ }
+ else {
+ // default to empty string
+ ruleresult->clear();
+ }
+ }
+ dnsdist::handleQueuedAsynchronousEvents();
+ return static_cast<DNSAction::Action>(ret);
+ }
+ catch (const std::exception& e) {
+ warnlog("LuaFFIPerThreadAction failed inside Lua, returning ServFail: %s", e.what());
+ }
+ catch (...) {
+ warnlog("LuaFFIPerthreadAction failed inside Lua, returning ServFail: [unknown exception]");
+ }
+ return DNSAction::Action::ServFail;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "Lua FFI per-thread script";
+ }
+
+private:
+ struct PerThreadState
+ {
+ LuaContext d_luaContext;
+ func_t d_func;
+ bool d_initialized{false};
+ };
+ static std::atomic<uint64_t> s_functionsCounter;
+ static thread_local std::map<uint64_t, PerThreadState> t_perThreadStates;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const std::string d_functionCode;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const uint64_t d_functionID;
+};
+
+std::atomic<uint64_t> LuaFFIPerThreadAction::s_functionsCounter = 0;
+thread_local std::map<uint64_t, LuaFFIPerThreadAction::PerThreadState> LuaFFIPerThreadAction::t_perThreadStates;
+
+class LuaFFIResponseAction : public DNSResponseAction
+{
+public:
+ using func_t = std::function<int(dnsdist_ffi_dnsresponse_t* dnsquestion)>;
+
+ LuaFFIResponseAction(LuaFFIResponseAction::func_t func) :
+ d_func(std::move(func))
+ {
+ }
+
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ dnsdist_ffi_dnsresponse_t ffiResponse(response);
+ try {
+ DNSResponseAction::Action result{};
+ {
+ auto lock = g_lua.lock();
+ auto ret = d_func(&ffiResponse);
+ if (ruleresult != nullptr) {
+ if (ffiResponse.result) {
+ *ruleresult = *ffiResponse.result;
+ }
+ else {
+ // default to empty string
+ ruleresult->clear();
+ }
+ }
+ result = static_cast<DNSResponseAction::Action>(ret);
+ }
+ dnsdist::handleQueuedAsynchronousEvents();
+ return result;
+ }
+ catch (const std::exception& e) {
+ warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: %s", e.what());
+ }
+ catch (...) {
+ warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: [unknown exception]");
+ }
+ return DNSResponseAction::Action::ServFail;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "Lua FFI script";
+ }
+
+private:
+ func_t d_func;
+};
+
+class LuaFFIPerThreadResponseAction : public DNSResponseAction
+{
+public:
+ using func_t = std::function<int(dnsdist_ffi_dnsresponse_t* response)>;
+
+ LuaFFIPerThreadResponseAction(std::string code) :
+ d_functionCode(std::move(code)), d_functionID(s_functionsCounter++)
+ {
+ }
+
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ try {
+ auto& state = t_perThreadStates[d_functionID];
+ if (!state.d_initialized) {
+ setupLuaFFIPerThreadContext(state.d_luaContext);
+ /* mark the state as initialized first so if there is a syntax error
+ we only try to execute the code once */
+ state.d_initialized = true;
+ state.d_func = state.d_luaContext.executeCode<func_t>(d_functionCode);
+ }
+
+ if (!state.d_func) {
+ /* the function was not properly initialized */
+ return DNSResponseAction::Action::None;
+ }
+
+ dnsdist_ffi_dnsresponse_t ffiResponse(response);
+ auto ret = state.d_func(&ffiResponse);
+ if (ruleresult != nullptr) {
+ if (ffiResponse.result) {
+ *ruleresult = *ffiResponse.result;
+ }
+ else {
+ // default to empty string
+ ruleresult->clear();
+ }
+ }
+ dnsdist::handleQueuedAsynchronousEvents();
+ return static_cast<DNSResponseAction::Action>(ret);
+ }
+ catch (const std::exception& e) {
+ warnlog("LuaFFIPerThreadResponseAction failed inside Lua, returning ServFail: %s", e.what());
+ }
+ catch (...) {
+ warnlog("LuaFFIPerthreadResponseAction failed inside Lua, returning ServFail: [unknown exception]");
+ }
+ return DNSResponseAction::Action::ServFail;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "Lua FFI per-thread script";
+ }
+
+private:
+ struct PerThreadState
+ {
+ LuaContext d_luaContext;
+ func_t d_func;
+ bool d_initialized{false};
+ };
+
+ static std::atomic<uint64_t> s_functionsCounter;
+ static thread_local std::map<uint64_t, PerThreadState> t_perThreadStates;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const std::string d_functionCode;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const uint64_t d_functionID;
+};
+
+std::atomic<uint64_t> LuaFFIPerThreadResponseAction::s_functionsCounter = 0;
+thread_local std::map<uint64_t, LuaFFIPerThreadResponseAction::PerThreadState> LuaFFIPerThreadResponseAction::t_perThreadStates;
+
+thread_local std::default_random_engine SpoofAction::t_randomEngine;
+
+DNSAction::Action SpoofAction::operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const
+{
+ uint16_t qtype = dnsquestion->ids.qtype;
+ // do we even have a response?
+ if (d_cname.empty() && d_rawResponses.empty() &&
+ // make sure pre-forged response is greater than sizeof(dnsheader)
+ (d_raw.size() < sizeof(dnsheader)) && d_types.count(qtype) == 0) {
+ return Action::None;
+ }
+
+ if (d_raw.size() >= sizeof(dnsheader)) {
+ auto questionId = dnsquestion->getHeader()->id;
+ dnsquestion->getMutableData() = d_raw;
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [questionId](dnsheader& header) {
+ header.id = questionId;
+ return true;
+ });
+ return Action::HeaderModify;
+ }
+ std::vector<ComboAddress> addrs = {};
+ std::vector<std::string> rawResponses = {};
+ unsigned int totrdatalen = 0;
+ size_t numberOfRecords = 0;
+ if (!d_cname.empty()) {
+ qtype = QType::CNAME;
+ totrdatalen += d_cname.getStorage().size();
+ numberOfRecords = 1;
+ }
+ else if (!d_rawResponses.empty()) {
+ rawResponses.reserve(d_rawResponses.size());
+ for (const auto& rawResponse : d_rawResponses) {
+ totrdatalen += rawResponse.size();
+ rawResponses.push_back(rawResponse);
+ ++numberOfRecords;
+ }
+ if (rawResponses.size() > 1) {
+ shuffle(rawResponses.begin(), rawResponses.end(), t_randomEngine);
+ }
+ }
+ else {
+ for (const auto& addr : d_addrs) {
+ if (qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) || (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) {
+ continue;
+ }
+ totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr);
+ addrs.push_back(addr);
+ ++numberOfRecords;
+ }
+ }
+
+ if (addrs.size() > 1) {
+ shuffle(addrs.begin(), addrs.end(), t_randomEngine);
+ }
+
+ unsigned int qnameWireLength = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName ignore(reinterpret_cast<const char*>(dnsquestion->getData().data()), dnsquestion->getData().size(), sizeof(dnsheader), false, nullptr, nullptr, &qnameWireLength);
+
+ if (dnsquestion->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen)) {
+ return Action::None;
+ }
+
+ bool dnssecOK = false;
+ bool hadEDNS = false;
+ if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dnsquestion)) {
+ hadEDNS = true;
+ dnssecOK = ((getEDNSZ(*dnsquestion) & EDNS_HEADER_FLAG_DO) != 0);
+ }
+
+ auto& data = dnsquestion->getMutableData();
+ data.resize(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen); // there goes your EDNS
+ uint8_t* dest = &(data.at(sizeof(dnsheader) + qnameWireLength + 4));
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+ header.qr = true; // for good measure
+ setResponseHeadersFromConfig(header, d_responseConfig);
+ header.ancount = 0;
+ header.arcount = 0; // for now, forget about your EDNS, we're marching over it
+ return true;
+ });
+
+ uint32_t ttl = htonl(d_responseConfig.ttl);
+ uint16_t qclass = htons(dnsquestion->ids.qclass);
+ std::array<unsigned char, 12> recordstart = {
+ 0xc0, 0x0c, // compressed name
+ 0, 0, // QTYPE
+ 0, 0, // QCLASS
+ 0, 0, 0, 0, // TTL
+ 0, 0 // rdata length
+ };
+ static_assert(recordstart.size() == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid");
+ memcpy(&recordstart[4], &qclass, sizeof(qclass));
+ memcpy(&recordstart[6], &ttl, sizeof(ttl));
+ bool raw = false;
+
+ if (qtype == QType::CNAME) {
+ const auto& wireData = d_cname.getStorage(); // Note! This doesn't do compression!
+ uint16_t rdataLen = htons(wireData.length());
+ qtype = htons(qtype);
+ memcpy(&recordstart[2], &qtype, sizeof(qtype));
+ memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
+
+ memcpy(dest, recordstart.data(), recordstart.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ dest += recordstart.size();
+ memcpy(dest, wireData.c_str(), wireData.length());
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+ header.ancount++;
+ return true;
+ });
+ }
+ else if (!rawResponses.empty()) {
+ if (qtype == QType::ANY && d_rawTypeForAny) {
+ qtype = *d_rawTypeForAny;
+ }
+ qtype = htons(qtype);
+ for (const auto& rawResponse : rawResponses) {
+ uint16_t rdataLen = htons(rawResponse.size());
+ memcpy(&recordstart[2], &qtype, sizeof(qtype));
+ memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
+
+ memcpy(dest, recordstart.data(), sizeof(recordstart));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ dest += recordstart.size();
+
+ memcpy(dest, rawResponse.c_str(), rawResponse.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ dest += rawResponse.size();
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+ header.ancount++;
+ return true;
+ });
+ }
+ raw = true;
+ }
+ else {
+ for (const auto& addr : addrs) {
+ uint16_t rdataLen = htons(addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+ qtype = htons(addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA);
+ memcpy(&recordstart[2], &qtype, sizeof(qtype));
+ memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
+
+ memcpy(dest, recordstart.data(), recordstart.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ dest += sizeof(recordstart);
+
+ memcpy(dest,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ addr.sin4.sin_family == AF_INET ? reinterpret_cast<const void*>(&addr.sin4.sin_addr.s_addr) : reinterpret_cast<const void*>(&addr.sin6.sin6_addr.s6_addr),
+ addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ dest += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+ header.ancount++;
+ return true;
+ });
+ }
+ }
+
+ auto finalANCount = dnsquestion->getHeader()->ancount;
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [finalANCount](dnsheader& header) {
+ header.ancount = htons(finalANCount);
+ return true;
+ });
+
+ if (hadEDNS && !raw) {
+ addEDNS(dnsquestion->getMutableData(), dnsquestion->getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, 0);
+ }
+
+ return Action::HeaderModify;
+}
+
+class SetMacAddrAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetMacAddrAction(uint16_t code) :
+ d_code(code)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsdist::MacAddress mac{};
+ int res = dnsdist::MacAddressesCache::get(dnsquestion->ids.origRemote, mac.data(), mac.size());
+ if (res != 0) {
+ return Action::None;
+ }
+
+ std::string optRData;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ generateEDNSOption(d_code, reinterpret_cast<const char*>(mac.data()), optRData);
+
+ if (dnsquestion->getHeader()->arcount > 0) {
+ bool ednsAdded = false;
+ bool optionAdded = false;
+ PacketBuffer newContent;
+ newContent.reserve(dnsquestion->getData().size());
+
+ if (!slowRewriteEDNSOptionInQueryWithRecords(dnsquestion->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) {
+ return Action::None;
+ }
+
+ if (newContent.size() > dnsquestion->getMaximumSize()) {
+ return Action::None;
+ }
+
+ dnsquestion->getMutableData() = std::move(newContent);
+ if (!dnsquestion->ids.ednsAdded && ednsAdded) {
+ dnsquestion->ids.ednsAdded = true;
+ }
+
+ return Action::None;
+ }
+
+ auto& data = dnsquestion->getMutableData();
+ if (generateOptRR(optRData, data, dnsquestion->getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+ header.arcount = htons(1);
+ return true;
+ });
+ // make sure that any EDNS sent by the backend is removed before forwarding the response to the client
+ dnsquestion->ids.ednsAdded = true;
+ }
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "add EDNS MAC (code=" + std::to_string(d_code) + ")";
+ }
+
+private:
+ uint16_t d_code{3};
+};
+
+class SetEDNSOptionAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetEDNSOptionAction(uint16_t code, std::string data) :
+ d_code(code), d_data(std::move(data))
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ setEDNSOption(*dnsquestion, d_code, d_data);
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "add EDNS Option (code=" + std::to_string(d_code) + ")";
+ }
+
+private:
+ uint16_t d_code;
+ std::string d_data;
+};
+
+class SetNoRecurseAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+ header.rd = false;
+ return true;
+ });
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set rd=0";
+ }
+};
+
+class LogAction : public DNSAction, public boost::noncopyable
+{
+public:
+ // this action does not stop the processing
+ LogAction() = default;
+
+ LogAction(const std::string& str, bool binary = true, bool append = false, bool buffered = true, bool verboseOnly = true, bool includeTimestamp = false) :
+ d_fname(str), d_binary(binary), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
+ {
+ if (str.empty()) {
+ return;
+ }
+
+ if (!reopenLogFile()) {
+ throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
+ }
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ auto filepointer = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
+ if (!filepointer) {
+ if (!d_verboseOnly || g_verbose) {
+ if (d_includeTimestamp) {
+ infolog("[%u.%u] Packet from %s for %s %s with id %d", static_cast<unsigned long long>(dnsquestion->getQueryRealTime().tv_sec), static_cast<unsigned long>(dnsquestion->getQueryRealTime().tv_nsec), dnsquestion->ids.origRemote.toStringWithPort(), dnsquestion->ids.qname.toString(), QType(dnsquestion->ids.qtype).toString(), dnsquestion->getHeader()->id);
+ }
+ else {
+ infolog("Packet from %s for %s %s with id %d", dnsquestion->ids.origRemote.toStringWithPort(), dnsquestion->ids.qname.toString(), QType(dnsquestion->ids.qtype).toString(), dnsquestion->getHeader()->id);
+ }
+ }
+ }
+ else {
+ if (d_binary) {
+ const auto& out = dnsquestion->ids.qname.getStorage();
+ if (d_includeTimestamp) {
+ auto tv_sec = static_cast<uint64_t>(dnsquestion->getQueryRealTime().tv_sec);
+ auto tv_nsec = static_cast<uint32_t>(dnsquestion->getQueryRealTime().tv_nsec);
+ fwrite(&tv_sec, sizeof(tv_sec), 1, filepointer.get());
+ fwrite(&tv_nsec, sizeof(tv_nsec), 1, filepointer.get());
+ }
+ uint16_t queryId = dnsquestion->getHeader()->id;
+ fwrite(&queryId, sizeof(queryId), 1, filepointer.get());
+ fwrite(out.c_str(), 1, out.size(), filepointer.get());
+ fwrite(&dnsquestion->ids.qtype, sizeof(dnsquestion->ids.qtype), 1, filepointer.get());
+ fwrite(&dnsquestion->ids.origRemote.sin4.sin_family, sizeof(dnsquestion->ids.origRemote.sin4.sin_family), 1, filepointer.get());
+ if (dnsquestion->ids.origRemote.sin4.sin_family == AF_INET) {
+ fwrite(&dnsquestion->ids.origRemote.sin4.sin_addr.s_addr, sizeof(dnsquestion->ids.origRemote.sin4.sin_addr.s_addr), 1, filepointer.get());
+ }
+ else if (dnsquestion->ids.origRemote.sin4.sin_family == AF_INET6) {
+ fwrite(&dnsquestion->ids.origRemote.sin6.sin6_addr.s6_addr, sizeof(dnsquestion->ids.origRemote.sin6.sin6_addr.s6_addr), 1, filepointer.get());
+ }
+ fwrite(&dnsquestion->ids.origRemote.sin4.sin_port, sizeof(dnsquestion->ids.origRemote.sin4.sin_port), 1, filepointer.get());
+ }
+ else {
+ if (d_includeTimestamp) {
+ fprintf(filepointer.get(), "[%llu.%lu] Packet from %s for %s %s with id %u\n", static_cast<unsigned long long>(dnsquestion->getQueryRealTime().tv_sec), static_cast<unsigned long>(dnsquestion->getQueryRealTime().tv_nsec), dnsquestion->ids.origRemote.toStringWithPort().c_str(), dnsquestion->ids.qname.toString().c_str(), QType(dnsquestion->ids.qtype).toString().c_str(), dnsquestion->getHeader()->id);
+ }
+ else {
+ fprintf(filepointer.get(), "Packet from %s for %s %s with id %u\n", dnsquestion->ids.origRemote.toStringWithPort().c_str(), dnsquestion->ids.qname.toString().c_str(), QType(dnsquestion->ids.qtype).toString().c_str(), dnsquestion->getHeader()->id);
+ }
+ }
+ }
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ if (!d_fname.empty()) {
+ return "log to " + d_fname;
+ }
+ return "log";
+ }
+
+ void reload() override
+ {
+ if (!reopenLogFile()) {
+ warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror());
+ }
+ }
+
+private:
+ bool reopenLogFile()
+ {
+ // we are using a naked pointer here because we don't want fclose to be called
+ // with a nullptr, which would happen if we constructor a shared_ptr with fclose
+ // as a custom deleter and nullptr as a FILE*
+ // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
+ auto* nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w");
+ if (nfp == nullptr) {
+ /* don't fall on our sword when reopening */
+ return false;
+ }
+
+ auto filepointer = std::shared_ptr<FILE>(nfp, fclose);
+ nfp = nullptr;
+
+ if (!d_buffered) {
+ setbuf(filepointer.get(), nullptr);
+ }
+
+ std::atomic_store_explicit(&d_fp, std::move(filepointer), std::memory_order_release);
+ return true;
+ }
+
+ std::string d_fname;
+ std::shared_ptr<FILE> d_fp{nullptr};
+ bool d_binary{true};
+ bool d_verboseOnly{true};
+ bool d_includeTimestamp{false};
+ bool d_append{false};
+ bool d_buffered{true};
+};
+
+class LogResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+ LogResponseAction() = default;
+
+ LogResponseAction(const std::string& str, bool append = false, bool buffered = true, bool verboseOnly = true, bool includeTimestamp = false) :
+ d_fname(str), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
+ {
+ if (str.empty()) {
+ return;
+ }
+
+ if (!reopenLogFile()) {
+ throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
+ }
+ }
+
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ auto filepointer = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
+ if (!filepointer) {
+ if (!d_verboseOnly || g_verbose) {
+ if (d_includeTimestamp) {
+ infolog("[%u.%u] Answer to %s for %s %s (%s) with id %u", static_cast<unsigned long long>(response->getQueryRealTime().tv_sec), static_cast<unsigned long>(response->getQueryRealTime().tv_nsec), response->ids.origRemote.toStringWithPort(), response->ids.qname.toString(), QType(response->ids.qtype).toString(), RCode::to_s(response->getHeader()->rcode), response->getHeader()->id);
+ }
+ else {
+ infolog("Answer to %s for %s %s (%s) with id %u", response->ids.origRemote.toStringWithPort(), response->ids.qname.toString(), QType(response->ids.qtype).toString(), RCode::to_s(response->getHeader()->rcode), response->getHeader()->id);
+ }
+ }
+ }
+ else {
+ if (d_includeTimestamp) {
+ fprintf(filepointer.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %u\n", static_cast<unsigned long long>(response->getQueryRealTime().tv_sec), static_cast<unsigned long>(response->getQueryRealTime().tv_nsec), response->ids.origRemote.toStringWithPort().c_str(), response->ids.qname.toString().c_str(), QType(response->ids.qtype).toString().c_str(), RCode::to_s(response->getHeader()->rcode).c_str(), response->getHeader()->id);
+ }
+ else {
+ fprintf(filepointer.get(), "Answer to %s for %s %s (%s) with id %u\n", response->ids.origRemote.toStringWithPort().c_str(), response->ids.qname.toString().c_str(), QType(response->ids.qtype).toString().c_str(), RCode::to_s(response->getHeader()->rcode).c_str(), response->getHeader()->id);
+ }
+ }
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ if (!d_fname.empty()) {
+ return "log to " + d_fname;
+ }
+ return "log";
+ }
+
+ void reload() override
+ {
+ if (!reopenLogFile()) {
+ warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror());
+ }
+ }
+
+private:
+ bool reopenLogFile()
+ {
+ // we are using a naked pointer here because we don't want fclose to be called
+ // with a nullptr, which would happen if we constructor a shared_ptr with fclose
+ // as a custom deleter and nullptr as a FILE*
+ // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
+ auto* nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w");
+ if (nfp == nullptr) {
+ /* don't fall on our sword when reopening */
+ return false;
+ }
+
+ auto filepointer = std::shared_ptr<FILE>(nfp, fclose);
+ nfp = nullptr;
+
+ if (!d_buffered) {
+ setbuf(filepointer.get(), nullptr);
+ }
+
+ std::atomic_store_explicit(&d_fp, std::move(filepointer), std::memory_order_release);
+ return true;
+ }
+
+ std::string d_fname;
+ std::shared_ptr<FILE> d_fp{nullptr};
+ bool d_verboseOnly{true};
+ bool d_includeTimestamp{false};
+ bool d_append{false};
+ bool d_buffered{true};
+};
+
+class SetDisableValidationAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+ header.cd = true;
+ return true;
+ });
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set cd=1";
+ }
+};
+
+class SetSkipCacheAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsquestion->ids.skipCache = true;
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "skip cache";
+ }
+};
+
+class SetSkipCacheResponseAction : public DNSResponseAction
+{
+public:
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ response->ids.skipCache = true;
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "skip cache";
+ }
+};
+
+class SetTempFailureCacheTTLAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetTempFailureCacheTTLAction(uint32_t ttl) :
+ d_ttl(ttl)
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsquestion->ids.tempFailureTTL = d_ttl;
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set tempfailure cache ttl to " + std::to_string(d_ttl);
+ }
+
+private:
+ uint32_t d_ttl;
+};
+
+class SetECSPrefixLengthAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) :
+ d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length)
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsquestion->ecsPrefixLength = dnsquestion->ids.origRemote.sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength;
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength);
+ }
+
+private:
+ uint16_t d_v4PrefixLength;
+ uint16_t d_v6PrefixLength;
+};
+
+class SetECSOverrideAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetECSOverrideAction(bool ecsOverride) :
+ d_ecsOverride(ecsOverride)
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsquestion->ecsOverride = d_ecsOverride;
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set ECS override to " + std::to_string(static_cast<int>(d_ecsOverride));
+ }
+
+private:
+ bool d_ecsOverride;
+};
+
+class SetDisableECSAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsquestion->useECS = false;
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "disable ECS";
+ }
+};
+
+class SetECSAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetECSAction(const Netmask& v4Netmask) :
+ d_v4(v4Netmask), d_hasV6(false)
+ {
+ }
+
+ SetECSAction(const Netmask& v4Netmask, const Netmask& v6Netmask) :
+ d_v4(v4Netmask), d_v6(v6Netmask), d_hasV6(true)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (d_hasV6) {
+ dnsquestion->ecs = std::make_unique<Netmask>(dnsquestion->ids.origRemote.isIPv4() ? d_v4 : d_v6);
+ }
+ else {
+ dnsquestion->ecs = std::make_unique<Netmask>(d_v4);
+ }
+
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ std::string result = "set ECS to " + d_v4.toString();
+ if (d_hasV6) {
+ result += " / " + d_v6.toString();
+ }
+ return result;
+ }
+
+private:
+ Netmask d_v4;
+ Netmask d_v6;
+ bool d_hasV6;
+};
+
+#ifndef DISABLE_PROTOBUF
+static DnstapMessage::ProtocolType ProtocolToDNSTap(dnsdist::Protocol protocol)
+{
+ if (protocol == dnsdist::Protocol::DoUDP) {
+ return DnstapMessage::ProtocolType::DoUDP;
+ }
+ if (protocol == dnsdist::Protocol::DoTCP) {
+ return DnstapMessage::ProtocolType::DoTCP;
+ }
+ if (protocol == dnsdist::Protocol::DoT) {
+ return DnstapMessage::ProtocolType::DoT;
+ }
+ if (protocol == dnsdist::Protocol::DoH || protocol == dnsdist::Protocol::DoH3) {
+ return DnstapMessage::ProtocolType::DoH;
+ }
+ if (protocol == dnsdist::Protocol::DNSCryptUDP) {
+ return DnstapMessage::ProtocolType::DNSCryptUDP;
+ }
+ if (protocol == dnsdist::Protocol::DNSCryptTCP) {
+ return DnstapMessage::ProtocolType::DNSCryptTCP;
+ }
+ if (protocol == dnsdist::Protocol::DoQ) {
+ return DnstapMessage::ProtocolType::DoQ;
+ }
+ throw std::runtime_error("Unhandled protocol for dnstap: " + protocol.toPrettyString());
+}
+
+static void remoteLoggerQueueData(RemoteLoggerInterface& remoteLogger, const std::string& data)
+{
+ auto ret = remoteLogger.queueData(data);
+
+ switch (ret) {
+ case RemoteLoggerInterface::Result::Queued:
+ break;
+ case RemoteLoggerInterface::Result::PipeFull: {
+ vinfolog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret));
+ break;
+ }
+ case RemoteLoggerInterface::Result::TooLarge: {
+ warnlog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret));
+ break;
+ }
+ case RemoteLoggerInterface::Result::OtherError:
+ warnlog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret));
+ }
+}
+
+class DnstapLogAction : public DNSAction, public boost::noncopyable
+{
+public:
+ // this action does not stop the processing
+ DnstapLogAction(std::string identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)>> alterFunc) :
+ d_identity(std::move(identity)), d_logger(logger), d_alterFunc(std::move(alterFunc))
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ static thread_local std::string data;
+ data.clear();
+
+ DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dnsquestion->getProtocol());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DnstapMessage message(std::move(data), !dnsquestion->getHeader()->qr ? DnstapMessage::MessageType::client_query : DnstapMessage::MessageType::client_response, d_identity, &dnsquestion->ids.origRemote, &dnsquestion->ids.origDest, protocol, reinterpret_cast<const char*>(dnsquestion->getData().data()), dnsquestion->getData().size(), &dnsquestion->getQueryRealTime(), nullptr);
+ {
+ if (d_alterFunc) {
+ auto lock = g_lua.lock();
+ (*d_alterFunc)(dnsquestion, &message);
+ }
+ }
+
+ data = message.getBuffer();
+ remoteLoggerQueueData(*d_logger, data);
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "remote log as dnstap to " + (d_logger ? d_logger->toString() : "");
+ }
+
+private:
+ std::string d_identity;
+ std::shared_ptr<RemoteLoggerInterface> d_logger;
+ boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)>> d_alterFunc;
+};
+
+namespace
+{
+void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dnsquestion, const std::vector<std::pair<std::string, ProtoBufMetaKey>>& metas)
+{
+ for (const auto& [name, meta] : metas) {
+ message.addMeta(name, meta.getValues(dnsquestion), {});
+ }
+}
+
+void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dnsquestion, const std::unordered_set<std::string>& allowed)
+{
+ if (!dnsquestion.ids.qTag) {
+ return;
+ }
+
+ for (const auto& [key, value] : *dnsquestion.ids.qTag) {
+ if (!allowed.empty() && allowed.count(key) == 0) {
+ continue;
+ }
+
+ if (value.empty()) {
+ message.addTag(key);
+ }
+ else {
+ auto tag = key;
+ tag.append(":");
+ tag.append(value);
+ message.addTag(tag);
+ }
+ }
+}
+
+void addExtendedDNSErrorToProtobuf(DNSDistProtoBufMessage& message, const DNSResponse& response, const std::string& metaKey)
+{
+ auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(response.getData());
+ if (!infoCode) {
+ return;
+ }
+
+ if (extraText) {
+ message.addMeta(metaKey, {*extraText}, {*infoCode});
+ }
+ else {
+ message.addMeta(metaKey, {}, {*infoCode});
+ }
+}
+}
+
+struct RemoteLogActionConfiguration
+{
+ std::vector<std::pair<std::string, ProtoBufMetaKey>> metas;
+ std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
+ boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)>> alterQueryFunc{boost::none};
+ boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)>> alterResponseFunc{boost::none};
+ std::shared_ptr<RemoteLoggerInterface> logger;
+ std::string serverID;
+ std::string ipEncryptKey;
+ std::optional<std::string> exportExtendedErrorsToMeta{std::nullopt};
+ bool includeCNAME{false};
+};
+
+class RemoteLogAction : public DNSAction, public boost::noncopyable
+{
+public:
+ // this action does not stop the processing
+ RemoteLogAction(RemoteLogActionConfiguration& config) :
+ d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterQueryFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (!dnsquestion->ids.d_protoBufData) {
+ dnsquestion->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+ }
+ if (!dnsquestion->ids.d_protoBufData->uniqueId) {
+ dnsquestion->ids.d_protoBufData->uniqueId = getUniqueID();
+ }
+
+ DNSDistProtoBufMessage message(*dnsquestion);
+ if (!d_serverID.empty()) {
+ message.setServerIdentity(d_serverID);
+ }
+
+#ifdef HAVE_IPCIPHER
+ if (!d_ipEncryptKey.empty()) {
+ message.setRequestor(encryptCA(dnsquestion->ids.origRemote, d_ipEncryptKey));
+ }
+#endif /* HAVE_IPCIPHER */
+
+ if (d_tagsToExport) {
+ addTagsToProtobuf(message, *dnsquestion, *d_tagsToExport);
+ }
+
+ addMetaDataToProtobuf(message, *dnsquestion, d_metas);
+
+ if (d_alterFunc) {
+ auto lock = g_lua.lock();
+ (*d_alterFunc)(dnsquestion, &message);
+ }
+
+ static thread_local std::string data;
+ data.clear();
+ message.serialize(data);
+ remoteLoggerQueueData(*d_logger, data);
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "remote log to " + (d_logger ? d_logger->toString() : "");
+ }
+
+private:
+ std::optional<std::unordered_set<std::string>> d_tagsToExport;
+ std::vector<std::pair<std::string, ProtoBufMetaKey>> d_metas;
+ std::shared_ptr<RemoteLoggerInterface> d_logger;
+ boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)>> d_alterFunc;
+ std::string d_serverID;
+ std::string d_ipEncryptKey;
+};
+
+#endif /* DISABLE_PROTOBUF */
+
+class SNMPTrapAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SNMPTrapAction(std::string reason) :
+ d_reason(std::move(reason))
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendDNSTrap(*dnsquestion, d_reason);
+ }
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "send SNMP trap";
+ }
+
+private:
+ std::string d_reason;
+};
+
+class SetTagAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetTagAction(std::string tag, std::string value) :
+ d_tag(std::move(tag)), d_value(std::move(value))
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsquestion->setTag(d_tag, d_value);
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set tag '" + d_tag + "' to value '" + d_value + "'";
+ }
+
+private:
+ std::string d_tag;
+ std::string d_value;
+};
+
+#ifndef DISABLE_PROTOBUF
+class DnstapLogResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+ // this action does not stop the processing
+ DnstapLogResponseAction(std::string identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)>> alterFunc) :
+ d_identity(std::move(identity)), d_logger(logger), d_alterFunc(std::move(alterFunc))
+ {
+ }
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ static thread_local std::string data;
+ struct timespec now = {};
+ gettime(&now, true);
+ data.clear();
+
+ DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(response->getProtocol());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DnstapMessage message(std::move(data), DnstapMessage::MessageType::client_response, d_identity, &response->ids.origRemote, &response->ids.origDest, protocol, reinterpret_cast<const char*>(response->getData().data()), response->getData().size(), &response->getQueryRealTime(), &now);
+ {
+ if (d_alterFunc) {
+ auto lock = g_lua.lock();
+ (*d_alterFunc)(response, &message);
+ }
+ }
+
+ data = message.getBuffer();
+ remoteLoggerQueueData(*d_logger, data);
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "log response as dnstap to " + (d_logger ? d_logger->toString() : "");
+ }
+
+private:
+ std::string d_identity;
+ std::shared_ptr<RemoteLoggerInterface> d_logger;
+ boost::optional<std::function<void(DNSResponse*, DnstapMessage*)>> d_alterFunc;
+};
+
+class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+ // this action does not stop the processing
+ RemoteLogResponseAction(RemoteLogActionConfiguration& config) :
+ d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterResponseFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey), d_exportExtendedErrorsToMeta(std::move(config.exportExtendedErrorsToMeta)), d_includeCNAME(config.includeCNAME)
+ {
+ }
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ if (!response->ids.d_protoBufData) {
+ response->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+ }
+ if (!response->ids.d_protoBufData->uniqueId) {
+ response->ids.d_protoBufData->uniqueId = getUniqueID();
+ }
+
+ DNSDistProtoBufMessage message(*response, d_includeCNAME);
+ if (!d_serverID.empty()) {
+ message.setServerIdentity(d_serverID);
+ }
+
+#ifdef HAVE_IPCIPHER
+ if (!d_ipEncryptKey.empty()) {
+ message.setRequestor(encryptCA(response->ids.origRemote, d_ipEncryptKey));
+ }
+#endif /* HAVE_IPCIPHER */
+
+ if (d_tagsToExport) {
+ addTagsToProtobuf(message, *response, *d_tagsToExport);
+ }
+
+ addMetaDataToProtobuf(message, *response, d_metas);
+
+ if (d_exportExtendedErrorsToMeta) {
+ addExtendedDNSErrorToProtobuf(message, *response, *d_exportExtendedErrorsToMeta);
+ }
+
+ if (d_alterFunc) {
+ auto lock = g_lua.lock();
+ (*d_alterFunc)(response, &message);
+ }
+
+ static thread_local std::string data;
+ data.clear();
+ message.serialize(data);
+ d_logger->queueData(data);
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "remote log response to " + (d_logger ? d_logger->toString() : "");
+ }
+
+private:
+ std::optional<std::unordered_set<std::string>> d_tagsToExport;
+ std::vector<std::pair<std::string, ProtoBufMetaKey>> d_metas;
+ std::shared_ptr<RemoteLoggerInterface> d_logger;
+ boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)>> d_alterFunc;
+ std::string d_serverID;
+ std::string d_ipEncryptKey;
+ std::optional<std::string> d_exportExtendedErrorsToMeta{std::nullopt};
+ bool d_includeCNAME;
+};
+
+#endif /* DISABLE_PROTOBUF */
+
+class DropResponseAction : public DNSResponseAction
+{
+public:
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ return Action::Drop;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "drop";
+ }
+};
+
+class AllowResponseAction : public DNSResponseAction
+{
+public:
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ return Action::Allow;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "allow";
+ }
+};
+
+class DelayResponseAction : public DNSResponseAction
+{
+public:
+ DelayResponseAction(int msec) :
+ d_msec(msec)
+ {
+ }
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ *ruleresult = std::to_string(d_msec);
+ return Action::Delay;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "delay by " + std::to_string(d_msec) + " ms";
+ }
+
+private:
+ int d_msec;
+};
+
+#ifdef HAVE_NET_SNMP
+class SNMPTrapResponseAction : public DNSResponseAction
+{
+public:
+ // this action does not stop the processing
+ SNMPTrapResponseAction(std::string reason) :
+ d_reason(std::move(reason))
+ {
+ }
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendDNSTrap(*response, d_reason);
+ }
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "send SNMP trap";
+ }
+
+private:
+ std::string d_reason;
+};
+#endif /* HAVE_NET_SNMP */
+
+class SetTagResponseAction : public DNSResponseAction
+{
+public:
+ // this action does not stop the processing
+ SetTagResponseAction(std::string tag, std::string value) :
+ d_tag(std::move(tag)), d_value(std::move(value))
+ {
+ }
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ response->setTag(d_tag, d_value);
+
+ return Action::None;
+ }
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set tag '" + d_tag + "' to value '" + d_value + "'";
+ }
+
+private:
+ std::string d_tag;
+ std::string d_value;
+};
+
+class ClearRecordTypesResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+ ClearRecordTypesResponseAction(std::unordered_set<QType> qtypes) :
+ d_qtypes(std::move(qtypes))
+ {
+ }
+
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ if (!d_qtypes.empty()) {
+ clearDNSPacketRecordTypes(response->getMutableData(), d_qtypes);
+ }
+ return DNSResponseAction::Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "clear record types";
+ }
+
+private:
+ std::unordered_set<QType> d_qtypes{};
+};
+
+class ContinueAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ ContinueAction(std::shared_ptr<DNSAction>& action) :
+ d_action(action)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (d_action) {
+ /* call the action */
+ auto action = (*d_action)(dnsquestion, ruleresult);
+ bool drop = false;
+ /* apply the changes if needed (pool selection, flags, etc */
+ processRulesResult(action, *dnsquestion, *ruleresult, drop);
+ }
+
+ /* but ignore the resulting action no matter what */
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ if (d_action) {
+ return "continue after: " + (d_action ? d_action->toString() : "");
+ }
+ return "no op";
+ }
+
+private:
+ std::shared_ptr<DNSAction> d_action;
+};
+
+#ifdef HAVE_DNS_OVER_HTTPS
+class HTTPStatusAction : public DNSAction
+{
+public:
+ HTTPStatusAction(int code, PacketBuffer body, std::string contentType) :
+ d_body(std::move(body)), d_contentType(std::move(contentType)), d_code(code)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (!dnsquestion->ids.du) {
+ return Action::None;
+ }
+
+ dnsquestion->ids.du->setHTTPResponse(d_code, PacketBuffer(d_body), d_contentType);
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+ header.qr = true; // for good measure
+ setResponseHeadersFromConfig(header, d_responseConfig);
+ return true;
+ });
+ return Action::HeaderModify;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "return an HTTP status of " + std::to_string(d_code);
+ }
+
+ [[nodiscard]] ResponseConfig& getResponseConfig()
+ {
+ return d_responseConfig;
+ }
+
+private:
+ ResponseConfig d_responseConfig;
+ PacketBuffer d_body;
+ std::string d_contentType;
+ int d_code;
+};
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+#if defined(HAVE_LMDB) || defined(HAVE_CDB)
+class KeyValueStoreLookupAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ KeyValueStoreLookupAction(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, std::string destinationTag) :
+ d_kvs(kvs), d_key(lookupKey), d_tag(std::move(destinationTag))
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ std::vector<std::string> keys = d_key->getKeys(*dnsquestion);
+ std::string result;
+ for (const auto& key : keys) {
+ if (d_kvs->getValue(key, result)) {
+ break;
+ }
+ }
+
+ dnsquestion->setTag(d_tag, std::move(result));
+
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "lookup key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
+ }
+
+private:
+ std::shared_ptr<KeyValueStore> d_kvs;
+ std::shared_ptr<KeyValueLookupKey> d_key;
+ std::string d_tag;
+};
+
+class KeyValueStoreRangeLookupAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ KeyValueStoreRangeLookupAction(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, std::string destinationTag) :
+ d_kvs(kvs), d_key(lookupKey), d_tag(std::move(destinationTag))
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ std::vector<std::string> keys = d_key->getKeys(*dnsquestion);
+ std::string result;
+ for (const auto& key : keys) {
+ if (d_kvs->getRangeValue(key, result)) {
+ break;
+ }
+ }
+
+ dnsquestion->setTag(d_tag, std::move(result));
+
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "do a range-based lookup in key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
+ }
+
+private:
+ std::shared_ptr<KeyValueStore> d_kvs;
+ std::shared_ptr<KeyValueLookupKey> d_key;
+ std::string d_tag;
+};
+#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
+
+class MaxReturnedTTLAction : public DNSAction
+{
+public:
+ MaxReturnedTTLAction(uint32_t cap) :
+ d_cap(cap)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ dnsquestion->ids.ttlCap = d_cap;
+ return DNSAction::Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "cap the TTL of the returned response to " + std::to_string(d_cap);
+ }
+
+private:
+ uint32_t d_cap;
+};
+
+class MaxReturnedTTLResponseAction : public DNSResponseAction
+{
+public:
+ MaxReturnedTTLResponseAction(uint32_t cap) :
+ d_cap(cap)
+ {
+ }
+
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ response->ids.ttlCap = d_cap;
+ return DNSResponseAction::Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "cap the TTL of the returned response to " + std::to_string(d_cap);
+ }
+
+private:
+ uint32_t d_cap;
+};
+
+class NegativeAndSOAAction : public DNSAction
+{
+public:
+ struct SOAParams
+ {
+ uint32_t serial;
+ uint32_t refresh;
+ uint32_t retry;
+ uint32_t expire;
+ uint32_t minimum;
+ };
+
+ NegativeAndSOAAction(bool nxd, DNSName zone, uint32_t ttl, DNSName mname, DNSName rname, SOAParams params, bool soaInAuthoritySection) :
+ d_zone(std::move(zone)), d_mname(std::move(mname)), d_rname(std::move(rname)), d_ttl(ttl), d_params(params), d_nxd(nxd), d_soaInAuthoritySection(soaInAuthoritySection)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (!setNegativeAndAdditionalSOA(*dnsquestion, d_nxd, d_zone, d_ttl, d_mname, d_rname, d_params.serial, d_params.refresh, d_params.retry, d_params.expire, d_params.minimum, d_soaInAuthoritySection)) {
+ return Action::None;
+ }
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+ setResponseHeadersFromConfig(header, d_responseConfig);
+ return true;
+ });
+
+ return Action::Allow;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return std::string(d_nxd ? "NXD " : "NODATA") + " with SOA";
+ }
+ [[nodiscard]] ResponseConfig& getResponseConfig()
+ {
+ return d_responseConfig;
+ }
+
+private:
+ ResponseConfig d_responseConfig;
+
+ DNSName d_zone;
+ DNSName d_mname;
+ DNSName d_rname;
+ uint32_t d_ttl;
+ SOAParams d_params;
+ bool d_nxd;
+ bool d_soaInAuthoritySection;
+};
+
+class SetProxyProtocolValuesAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetProxyProtocolValuesAction(const std::vector<std::pair<uint8_t, std::string>>& values)
+ {
+ d_values.reserve(values.size());
+ for (const auto& value : values) {
+ d_values.push_back({value.second, value.first});
+ }
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (!dnsquestion->proxyProtocolValues) {
+ dnsquestion->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+ }
+
+ *(dnsquestion->proxyProtocolValues) = d_values;
+
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set Proxy-Protocol values";
+ }
+
+private:
+ std::vector<ProxyProtocolValue> d_values;
+};
+
+class SetAdditionalProxyProtocolValueAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetAdditionalProxyProtocolValueAction(uint8_t type, std::string value) :
+ d_value(std::move(value)), d_type(type)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+ {
+ if (!dnsquestion->proxyProtocolValues) {
+ dnsquestion->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+ }
+
+ dnsquestion->proxyProtocolValues->push_back({d_value, d_type});
+
+ return Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "add a Proxy-Protocol value of type " + std::to_string(d_type);
+ }
+
+private:
+ std::string d_value;
+ uint8_t d_type;
+};
+
+class SetReducedTTLResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+ // this action does not stop the processing
+ SetReducedTTLResponseAction(uint8_t percentage) :
+ d_ratio(percentage / 100.0)
+ {
+ }
+
+ DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+ {
+ // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+ auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
+ return ttl * d_ratio;
+ };
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ editDNSPacketTTL(reinterpret_cast<char*>(response->getMutableData().data()), response->getData().size(), visitor);
+ return DNSResponseAction::Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "reduce ttl to " + std::to_string(d_ratio * 100) + " percent of its value";
+ }
+
+private:
+ double d_ratio{1.0};
+};
+
+class SetExtendedDNSErrorAction : public DNSAction
+{
+public:
+ // this action does not stop the processing
+ SetExtendedDNSErrorAction(uint16_t infoCode, const std::string& extraText)
+ {
+ d_ede.infoCode = infoCode;
+ d_ede.extraText = extraText;
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsQuestion, std::string* ruleresult) const override
+ {
+ dnsQuestion->ids.d_extendedError = std::make_unique<EDNSExtendedError>(d_ede);
+
+ return DNSAction::Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set EDNS Extended DNS Error to " + std::to_string(d_ede.infoCode) + (d_ede.extraText.empty() ? std::string() : std::string(": \"") + d_ede.extraText + std::string("\""));
+ }
+
+private:
+ EDNSExtendedError d_ede;
+};
+
+class SetExtendedDNSErrorResponseAction : public DNSResponseAction
+{
+public:
+ // this action does not stop the processing
+ SetExtendedDNSErrorResponseAction(uint16_t infoCode, const std::string& extraText)
+ {
+ d_ede.infoCode = infoCode;
+ d_ede.extraText = extraText;
+ }
+
+ DNSResponseAction::Action operator()(DNSResponse* dnsResponse, std::string* ruleresult) const override
+ {
+ dnsResponse->ids.d_extendedError = std::make_unique<EDNSExtendedError>(d_ede);
+
+ return DNSResponseAction::Action::None;
+ }
+
+ [[nodiscard]] std::string toString() const override
+ {
+ return "set EDNS Extended DNS Error to " + std::to_string(d_ede.infoCode) + (d_ede.extraText.empty() ? std::string() : std::string(": \"") + d_ede.extraText + std::string("\""));
+ }
+
+private:
+ EDNSExtendedError d_ede;
+};
+
+template <typename T, typename ActionT>
+static void addAction(GlobalStateHolder<vector<T>>* someRuleActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params)
+{
+ setLuaSideEffect();
+
+ std::string name;
+ boost::uuids::uuid uuid{};
+ uint64_t creationOrder = 0;
+ parseRuleParams(params, uuid, name, creationOrder);
+ checkAllParametersConsumed("addAction", params);
+
+ auto rule = makeRule(var, "addAction");
+ someRuleActions->modify([&rule, &action, &uuid, creationOrder, &name](vector<T>& ruleactions) {
+ ruleactions.push_back({std::move(rule), std::move(action), std::move(name), uuid, creationOrder});
+ });
+}
+
+using responseParams_t = std::unordered_map<std::string, boost::variant<bool, uint32_t>>;
+
+static void parseResponseConfig(boost::optional<responseParams_t>& vars, ResponseConfig& config)
+{
+ getOptionalValue<uint32_t>(vars, "ttl", config.ttl);
+ getOptionalValue<bool>(vars, "aa", config.setAA);
+ getOptionalValue<bool>(vars, "ad", config.setAD);
+ getOptionalValue<bool>(vars, "ra", config.setRA);
+}
+
+void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config)
+{
+ if (config.setAA) {
+ dnsheader.aa = *config.setAA;
+ }
+ if (config.setAD) {
+ dnsheader.ad = *config.setAD;
+ }
+ else {
+ dnsheader.ad = false;
+ }
+ if (config.setRA) {
+ dnsheader.ra = *config.setRA;
+ }
+ else {
+ dnsheader.ra = dnsheader.rd; // for good measure
+ }
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaActions(LuaContext& luaCtx)
+{
+ luaCtx.writeFunction("newRuleAction", [](const luadnsrule_t& dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
+ boost::uuids::uuid uuid{};
+ uint64_t creationOrder = 0;
+ std::string name;
+ parseRuleParams(params, uuid, name, creationOrder);
+ checkAllParametersConsumed("newRuleAction", params);
+
+ auto rule = makeRule(dnsrule, "newRuleAction");
+ dnsdist::rules::RuleAction ruleaction({std::move(rule), std::move(action), std::move(name), uuid, creationOrder});
+ return std::make_shared<dnsdist::rules::RuleAction>(ruleaction);
+ });
+
+ luaCtx.writeFunction("addAction", [](const luadnsrule_t& var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
+ if (era.type() != typeid(std::shared_ptr<DNSAction>)) {
+ throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?");
+ }
+
+ addAction(&dnsdist::rules::g_ruleactions, var, boost::get<std::shared_ptr<DNSAction>>(era), params);
+ });
+
+ for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+ const auto fullName = std::string("add") + chain.prefix + std::string("ResponseAction");
+ luaCtx.writeFunction(fullName, [&fullName, &chain](const luadnsrule_t& var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
+ if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
+ throw std::runtime_error(fullName + "() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
+ }
+
+ addAction(&chain.holder, var, boost::get<std::shared_ptr<DNSResponseAction>>(era), params);
+ });
+ }
+
+ luaCtx.registerFunction<void (DNSAction::*)() const>("printStats", [](const DNSAction& action) {
+ setLuaNoSideEffect();
+ auto stats = action.getStats();
+ for (const auto& stat : stats) {
+ g_outputBuffer += stat.first + "\t";
+ double integral = 0;
+ if (std::modf(stat.second, &integral) == 0.0 && stat.second < static_cast<double>(std::numeric_limits<uint64_t>::max())) {
+ g_outputBuffer += std::to_string(static_cast<uint64_t>(stat.second)) + "\n";
+ }
+ else {
+ g_outputBuffer += std::to_string(stat.second) + "\n";
+ }
+ }
+ });
+
+ luaCtx.writeFunction("getAction", [](unsigned int num) {
+ setLuaNoSideEffect();
+ boost::optional<std::shared_ptr<DNSAction>> ret;
+ auto ruleactions = dnsdist::rules::g_ruleactions.getCopy();
+ if (num < ruleactions.size()) {
+ ret = ruleactions[num].d_action;
+ }
+ return ret;
+ });
+
+ luaCtx.registerFunction("getStats", &DNSAction::getStats);
+ luaCtx.registerFunction("reload", &DNSAction::reload);
+ luaCtx.registerFunction("reload", &DNSResponseAction::reload);
+
+ luaCtx.writeFunction("LuaAction", [](LuaAction::func_t func) {
+ setLuaSideEffect();
+ return std::shared_ptr<DNSAction>(new LuaAction(std::move(func)));
+ });
+
+ luaCtx.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) {
+ setLuaSideEffect();
+ return std::shared_ptr<DNSAction>(new LuaFFIAction(std::move(func)));
+ });
+
+ luaCtx.writeFunction("LuaFFIPerThreadAction", [](const std::string& code) {
+ setLuaSideEffect();
+ return std::shared_ptr<DNSAction>(new LuaFFIPerThreadAction(code));
+ });
+
+ luaCtx.writeFunction("SetNoRecurseAction", []() {
+ return std::shared_ptr<DNSAction>(new SetNoRecurseAction);
+ });
+
+ luaCtx.writeFunction("SetMacAddrAction", [](int code) {
+ return std::shared_ptr<DNSAction>(new SetMacAddrAction(code));
+ });
+
+ luaCtx.writeFunction("SetEDNSOptionAction", [](int code, const std::string& data) {
+ return std::shared_ptr<DNSAction>(new SetEDNSOptionAction(code, data));
+ });
+
+ luaCtx.writeFunction("PoolAction", [](const std::string& poolname, boost::optional<bool> stopProcessing) {
+ return std::shared_ptr<DNSAction>(new PoolAction(poolname, stopProcessing ? *stopProcessing : true));
+ });
+
+ luaCtx.writeFunction("QPSAction", [](int limit) {
+ return std::shared_ptr<DNSAction>(new QPSAction(limit));
+ });
+
+ luaCtx.writeFunction("QPSPoolAction", [](int limit, const std::string& poolname, boost::optional<bool> stopProcessing) {
+ return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, poolname, stopProcessing ? *stopProcessing : true));
+ });
+
+ luaCtx.writeFunction("SpoofAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
+ vector<ComboAddress> addrs;
+ if (auto* ipaddr = boost::get<std::string>(&inp)) {
+ addrs.emplace_back(*ipaddr);
+ }
+ else {
+ const auto& ipsArray = boost::get<LuaArray<std::string>>(inp);
+ for (const auto& ipAddr : ipsArray) {
+ addrs.emplace_back(ipAddr.second);
+ }
+ }
+
+ auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+ auto spoofaction = std::dynamic_pointer_cast<SpoofAction>(ret);
+ parseResponseConfig(vars, spoofaction->getResponseConfig());
+ checkAllParametersConsumed("SpoofAction", vars);
+ return ret;
+ });
+
+ luaCtx.writeFunction("SpoofSVCAction", [](const LuaArray<SVCRecordParameters>& parameters, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new SpoofSVCAction(parameters));
+ auto spoofaction = std::dynamic_pointer_cast<SpoofSVCAction>(ret);
+ parseResponseConfig(vars, spoofaction->getResponseConfig());
+ return ret;
+ });
+
+ luaCtx.writeFunction("SpoofCNAMEAction", [](const std::string& cname, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new SpoofAction(DNSName(cname)));
+ auto spoofaction = std::dynamic_pointer_cast<SpoofAction>(ret);
+ parseResponseConfig(vars, spoofaction->getResponseConfig());
+ checkAllParametersConsumed("SpoofCNAMEAction", vars);
+ return ret;
+ });
+
+ luaCtx.writeFunction("SpoofRawAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
+ vector<string> raws;
+ if (const auto* str = boost::get<std::string>(&inp)) {
+ raws.push_back(*str);
+ }
+ else {
+ const auto& vect = boost::get<LuaArray<std::string>>(inp);
+ for (const auto& raw : vect) {
+ raws.push_back(raw.second);
+ }
+ }
+ uint32_t qtypeForAny{0};
+ getOptionalValue<uint32_t>(vars, "typeForAny", qtypeForAny);
+ if (qtypeForAny > std::numeric_limits<uint16_t>::max()) {
+ qtypeForAny = 0;
+ }
+ std::optional<uint16_t> qtypeForAnyParam;
+ if (qtypeForAny > 0) {
+ qtypeForAnyParam = static_cast<uint16_t>(qtypeForAny);
+ }
+ auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws, qtypeForAnyParam));
+ auto spoofaction = std::dynamic_pointer_cast<SpoofAction>(ret);
+ parseResponseConfig(vars, spoofaction->getResponseConfig());
+ checkAllParametersConsumed("SpoofRawAction", vars);
+ return ret;
+ });
+
+ luaCtx.writeFunction("SpoofPacketAction", [](const std::string& response, size_t len) {
+ if (len < sizeof(dnsheader)) {
+ throw std::runtime_error(std::string("SpoofPacketAction: given packet len is too small"));
+ }
+ auto ret = std::shared_ptr<DNSAction>(new SpoofAction(response.c_str(), len));
+ return ret;
+ });
+
+ luaCtx.writeFunction("DropAction", []() {
+ return std::shared_ptr<DNSAction>(new DropAction);
+ });
+
+ luaCtx.writeFunction("AllowAction", []() {
+ return std::shared_ptr<DNSAction>(new AllowAction);
+ });
+
+ luaCtx.writeFunction("NoneAction", []() {
+ return std::shared_ptr<DNSAction>(new NoneAction);
+ });
+
+ luaCtx.writeFunction("DelayAction", [](int msec) {
+ return std::shared_ptr<DNSAction>(new DelayAction(msec));
+ });
+
+ luaCtx.writeFunction("TCAction", []() {
+ return std::shared_ptr<DNSAction>(new TCAction);
+ });
+
+ luaCtx.writeFunction("TCResponseAction", []() {
+ return std::shared_ptr<DNSResponseAction>(new TCResponseAction);
+ });
+
+ luaCtx.writeFunction("SetDisableValidationAction", []() {
+ return std::shared_ptr<DNSAction>(new SetDisableValidationAction);
+ });
+
+ luaCtx.writeFunction("LogAction", [](boost::optional<std::string> fname, boost::optional<bool> binary, boost::optional<bool> append, boost::optional<bool> buffered, boost::optional<bool> verboseOnly, boost::optional<bool> includeTimestamp) {
+ return std::shared_ptr<DNSAction>(new LogAction(fname ? *fname : "", binary ? *binary : true, append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
+ });
+
+ luaCtx.writeFunction("LogResponseAction", [](boost::optional<std::string> fname, boost::optional<bool> append, boost::optional<bool> buffered, boost::optional<bool> verboseOnly, boost::optional<bool> includeTimestamp) {
+ return std::shared_ptr<DNSResponseAction>(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
+ });
+
+ luaCtx.writeFunction("LimitTTLResponseAction", [](uint32_t min, uint32_t max, boost::optional<LuaArray<uint16_t>> types) {
+ std::unordered_set<QType> capTypes;
+ if (types) {
+ capTypes.reserve(types->size());
+ for (const auto& [idx, type] : *types) {
+ capTypes.insert(QType(type));
+ }
+ }
+ return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min, max, capTypes));
+ });
+
+ luaCtx.writeFunction("SetMinTTLResponseAction", [](uint32_t min) {
+ return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min));
+ });
+
+ luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) {
+ return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(0, max));
+ });
+
+ luaCtx.writeFunction("SetMaxReturnedTTLAction", [](uint32_t max) {
+ return std::shared_ptr<DNSAction>(new MaxReturnedTTLAction(max));
+ });
+
+ luaCtx.writeFunction("SetMaxReturnedTTLResponseAction", [](uint32_t max) {
+ return std::shared_ptr<DNSResponseAction>(new MaxReturnedTTLResponseAction(max));
+ });
+
+ luaCtx.writeFunction("SetReducedTTLResponseAction", [](uint8_t percentage) {
+ if (percentage > 100) {
+ throw std::runtime_error(std::string("SetReducedTTLResponseAction takes a percentage between 0 and 100."));
+ }
+ return std::shared_ptr<DNSResponseAction>(new SetReducedTTLResponseAction(percentage));
+ });
+
+ luaCtx.writeFunction("ClearRecordTypesResponseAction", [](LuaTypeOrArrayOf<int> types) {
+ std::unordered_set<QType> qtypes{};
+ if (types.type() == typeid(int)) {
+ qtypes.insert(boost::get<int>(types));
+ }
+ else if (types.type() == typeid(LuaArray<int>)) {
+ const auto& typesArray = boost::get<LuaArray<int>>(types);
+ for (const auto& tpair : typesArray) {
+ qtypes.insert(tpair.second);
+ }
+ }
+ return std::shared_ptr<DNSResponseAction>(new ClearRecordTypesResponseAction(std::move(qtypes)));
+ });
+
+ luaCtx.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+ auto rca = std::dynamic_pointer_cast<RCodeAction>(ret);
+ parseResponseConfig(vars, rca->getResponseConfig());
+ checkAllParametersConsumed("RCodeAction", vars);
+ return ret;
+ });
+
+ luaCtx.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+ auto erca = std::dynamic_pointer_cast<ERCodeAction>(ret);
+ parseResponseConfig(vars, erca->getResponseConfig());
+ checkAllParametersConsumed("ERCodeAction", vars);
+ return ret;
+ });
+
+ luaCtx.writeFunction("SetSkipCacheAction", []() {
+ return std::shared_ptr<DNSAction>(new SetSkipCacheAction);
+ });
+
+ luaCtx.writeFunction("SetSkipCacheResponseAction", []() {
+ return std::shared_ptr<DNSResponseAction>(new SetSkipCacheResponseAction);
+ });
+
+ luaCtx.writeFunction("SetTempFailureCacheTTLAction", [](int maxTTL) {
+ return std::shared_ptr<DNSAction>(new SetTempFailureCacheTTLAction(maxTTL));
+ });
+
+ luaCtx.writeFunction("DropResponseAction", []() {
+ return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
+ });
+
+ luaCtx.writeFunction("AllowResponseAction", []() {
+ return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
+ });
+
+ luaCtx.writeFunction("DelayResponseAction", [](int msec) {
+ return std::shared_ptr<DNSResponseAction>(new DelayResponseAction(msec));
+ });
+
+ luaCtx.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) {
+ setLuaSideEffect();
+ return std::shared_ptr<DNSResponseAction>(new LuaResponseAction(std::move(func)));
+ });
+
+ luaCtx.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) {
+ setLuaSideEffect();
+ return std::shared_ptr<DNSResponseAction>(new LuaFFIResponseAction(std::move(func)));
+ });
+
+ luaCtx.writeFunction("LuaFFIPerThreadResponseAction", [](const std::string& code) {
+ setLuaSideEffect();
+ return std::shared_ptr<DNSResponseAction>(new LuaFFIPerThreadResponseAction(code));
+ });
+
+#ifndef DISABLE_PROTOBUF
+ luaCtx.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)>> alterFunc, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
+ if (logger) {
+ // avoids potentially-evaluated-expression warning with clang.
+ RemoteLoggerInterface& remoteLoggerRef = *logger;
+ if (typeid(remoteLoggerRef) != typeid(RemoteLogger)) {
+ // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong.
+ throw std::runtime_error(std::string("RemoteLogAction only takes RemoteLogger. For other types, please look at DnstapLogAction."));
+ }
+ }
+
+ std::string tags;
+ RemoteLogActionConfiguration config;
+ config.logger = std::move(logger);
+ config.alterQueryFunc = std::move(alterFunc);
+ getOptionalValue<std::string>(vars, "serverID", config.serverID);
+ getOptionalValue<std::string>(vars, "ipEncryptKey", config.ipEncryptKey);
+ getOptionalValue<std::string>(vars, "exportTags", tags);
+
+ if (metas) {
+ for (const auto& [key, value] : *metas) {
+ config.metas.emplace_back(key, ProtoBufMetaKey(value));
+ }
+ }
+
+ if (!tags.empty()) {
+ config.tagsToExport = std::unordered_set<std::string>();
+ if (tags != "*") {
+ std::vector<std::string> tokens;
+ stringtok(tokens, tags, ",");
+ for (auto& token : tokens) {
+ config.tagsToExport->insert(std::move(token));
+ }
+ }
+ }
+
+ checkAllParametersConsumed("RemoteLogAction", vars);
+
+ return std::shared_ptr<DNSAction>(new RemoteLogAction(config));
+ });
+
+ luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)>> alterFunc, boost::optional<bool> includeCNAME, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
+ if (logger) {
+ // avoids potentially-evaluated-expression warning with clang.
+ RemoteLoggerInterface& remoteLoggerRef = *logger;
+ if (typeid(remoteLoggerRef) != typeid(RemoteLogger)) {
+ // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong.
+ throw std::runtime_error("RemoteLogResponseAction only takes RemoteLogger. For other types, please look at DnstapLogResponseAction.");
+ }
+ }
+
+ std::string tags;
+ RemoteLogActionConfiguration config;
+ config.logger = std::move(logger);
+ config.alterResponseFunc = std::move(alterFunc);
+ config.includeCNAME = includeCNAME ? *includeCNAME : false;
+ getOptionalValue<std::string>(vars, "serverID", config.serverID);
+ getOptionalValue<std::string>(vars, "ipEncryptKey", config.ipEncryptKey);
+ getOptionalValue<std::string>(vars, "exportTags", tags);
+ getOptionalValue<std::string>(vars, "exportExtendedErrorsToMeta", config.exportExtendedErrorsToMeta);
+
+ if (metas) {
+ for (const auto& [key, value] : *metas) {
+ config.metas.emplace_back(key, ProtoBufMetaKey(value));
+ }
+ }
+
+ if (!tags.empty()) {
+ config.tagsToExport = std::unordered_set<std::string>();
+ if (tags != "*") {
+ std::vector<std::string> tokens;
+ stringtok(tokens, tags, ",");
+ for (auto& token : tokens) {
+ config.tagsToExport->insert(std::move(token));
+ }
+ }
+ }
+
+ checkAllParametersConsumed("RemoteLogResponseAction", vars);
+
+ return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(config));
+ });
+
+ luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)>> alterFunc) {
+ return std::shared_ptr<DNSAction>(new DnstapLogAction(identity, logger, std::move(alterFunc)));
+ });
+
+ luaCtx.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)>> alterFunc) {
+ return std::shared_ptr<DNSResponseAction>(new DnstapLogResponseAction(identity, logger, std::move(alterFunc)));
+ });
+#endif /* DISABLE_PROTOBUF */
+
+ luaCtx.writeFunction("TeeAction", [](const std::string& remote, boost::optional<bool> addECS, boost::optional<std::string> local, boost::optional<bool> addProxyProtocol) {
+ boost::optional<ComboAddress> localAddr{boost::none};
+ if (local) {
+ localAddr = ComboAddress(*local, 0);
+ }
+
+ return std::shared_ptr<DNSAction>(new TeeAction(ComboAddress(remote, 53), localAddr, addECS ? *addECS : false, addProxyProtocol ? *addProxyProtocol : false));
+ });
+
+ luaCtx.writeFunction("SetECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) {
+ return std::shared_ptr<DNSAction>(new SetECSPrefixLengthAction(v4PrefixLength, v6PrefixLength));
+ });
+
+ luaCtx.writeFunction("SetECSOverrideAction", [](bool ecsOverride) {
+ return std::shared_ptr<DNSAction>(new SetECSOverrideAction(ecsOverride));
+ });
+
+ luaCtx.writeFunction("SetDisableECSAction", []() {
+ return std::shared_ptr<DNSAction>(new SetDisableECSAction());
+ });
+
+ luaCtx.writeFunction("SetECSAction", [](const std::string& v4Netmask, boost::optional<std::string> v6Netmask) {
+ if (v6Netmask) {
+ return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4Netmask), Netmask(*v6Netmask)));
+ }
+ return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4Netmask)));
+ });
+
+#ifdef HAVE_NET_SNMP
+ luaCtx.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
+ return std::shared_ptr<DNSAction>(new SNMPTrapAction(reason ? *reason : ""));
+ });
+
+ luaCtx.writeFunction("SNMPTrapResponseAction", [](boost::optional<std::string> reason) {
+ return std::shared_ptr<DNSResponseAction>(new SNMPTrapResponseAction(reason ? *reason : ""));
+ });
+#endif /* HAVE_NET_SNMP */
+
+ luaCtx.writeFunction("SetTagAction", [](const std::string& tag, const std::string& value) {
+ return std::shared_ptr<DNSAction>(new SetTagAction(tag, value));
+ });
+
+ luaCtx.writeFunction("SetTagResponseAction", [](const std::string& tag, const std::string& value) {
+ return std::shared_ptr<DNSResponseAction>(new SetTagResponseAction(tag, value));
+ });
+
+ luaCtx.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
+ return std::shared_ptr<DNSAction>(new ContinueAction(action));
+ });
+
+#ifdef HAVE_DNS_OVER_HTTPS
+ luaCtx.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new HTTPStatusAction(status, PacketBuffer(body.begin(), body.end()), contentType ? *contentType : ""));
+ auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
+ parseResponseConfig(vars, hsa->getResponseConfig());
+ checkAllParametersConsumed("HTTPStatusAction", vars);
+ return ret;
+ });
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+#if defined(HAVE_LMDB) || defined(HAVE_CDB)
+ luaCtx.writeFunction("KeyValueStoreLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
+ return std::shared_ptr<DNSAction>(new KeyValueStoreLookupAction(kvs, lookupKey, destinationTag));
+ });
+
+ luaCtx.writeFunction("KeyValueStoreRangeLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
+ return std::shared_ptr<DNSAction>(new KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag));
+ });
+#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
+
+ luaCtx.writeFunction("NegativeAndSOAAction", [](bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, boost::optional<responseParams_t> vars) {
+ bool soaInAuthoritySection = false;
+ getOptionalValue<bool>(vars, "soaInAuthoritySection", soaInAuthoritySection);
+ NegativeAndSOAAction::SOAParams params{
+ .serial = serial,
+ .refresh = refresh,
+ .retry = retry,
+ .expire = expire,
+ .minimum = minimum};
+ auto ret = std::shared_ptr<DNSAction>(new NegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), params, soaInAuthoritySection));
+ auto action = std::dynamic_pointer_cast<NegativeAndSOAAction>(ret);
+ parseResponseConfig(vars, action->getResponseConfig());
+ checkAllParametersConsumed("NegativeAndSOAAction", vars);
+ return ret;
+ });
+
+ luaCtx.writeFunction("SetProxyProtocolValuesAction", [](const std::vector<std::pair<uint8_t, std::string>>& values) {
+ return std::shared_ptr<DNSAction>(new SetProxyProtocolValuesAction(values));
+ });
+
+ luaCtx.writeFunction("SetAdditionalProxyProtocolValueAction", [](uint8_t type, const std::string& value) {
+ return std::shared_ptr<DNSAction>(new SetAdditionalProxyProtocolValueAction(type, value));
+ });
+
+ luaCtx.writeFunction("SetExtendedDNSErrorAction", [](uint16_t infoCode, boost::optional<std::string> extraText) {
+ return std::shared_ptr<DNSAction>(new SetExtendedDNSErrorAction(infoCode, extraText ? *extraText : ""));
+ });
+
+ luaCtx.writeFunction("SetExtendedDNSErrorResponseAction", [](uint16_t infoCode, boost::optional<std::string> extraText) {
+ return std::shared_ptr<DNSResponseAction>(new SetExtendedDNSErrorResponseAction(infoCode, extraText ? *extraText : ""));
+ });
+}
ret << (fmt % "#" % "Serial" % "Version" % "From" % "To" ) << endl;
for (const auto& pair : ctx->getCertificates()) {
- const auto cert = pair->cert;
+ const auto& cert = pair->cert;
const DNSCryptExchangeVersion version = DNSCryptContext::getExchangeVersion(cert);
ret << (fmt % idx % cert.getSerial() % (version == DNSCryptExchangeVersion::VERSION1 ? 1 : 2) % DNSCryptContext::certificateDateToStr(cert.getTSStart()) % DNSCryptContext::certificateDateToStr(cert.getTSEnd())) << endl;
ctx.addNewCertificate(cert, privateKey);
}
}
- catch(const std::exception& e) {
- errlog(e.what());
- g_outputBuffer="Error: "+string(e.what())+"\n";
+ catch (const std::exception& e) {
+ errlog("Error generating a DNSCrypt certificate: %s", e.what());
+ g_outputBuffer = "Error generating a DNSCrypt certificate: " + string(e.what()) + "\n";
}
});
}
}
catch (const std::exception& e) {
- errlog(e.what());
- g_outputBuffer = "Error: " + string(e.what()) + "\n";
+ errlog("Error generating a DNSCrypt certificate: %s", e.what());
+ g_outputBuffer = "Error generating a DNSCrypt certificate: " + string(e.what()) + "\n";
}
});
g_outputBuffer = "Provider fingerprint is: " + DNSCryptContext::getProviderFingerprint(publicKey) + "\n";
}
catch (const std::exception& e) {
- errlog(e.what());
- g_outputBuffer = "Error: " + string(e.what()) + "\n";
+ errlog("Error generating a DNSCrypt provider key: %s", e.what());
+ g_outputBuffer = "Error generating a DNSCrypt provider key: " + string(e.what()) + "\n";
}
sodium_memzero(privateKey, sizeof(privateKey));
g_outputBuffer = "Provider fingerprint is: " + DNSCryptContext::getProviderFingerprint(publicKey) + "\n";
}
catch (const std::exception& e) {
- errlog(e.what());
- g_outputBuffer = "Error: " + string(e.what()) + "\n";
+ errlog("Error getting a DNSCrypt provider fingerprint: %s", e.what());
+ g_outputBuffer = "Error getting a DNSCrypt provider fingerprint: " + string(e.what()) + "\n";
}
});
#endif
return dpo;
});
- luaCtx.registerMember<DNSName(dnsdist::DNSPacketOverlay::*)>(std::string("qname"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qname; });
+ luaCtx.registerMember<DNSName(dnsdist::DNSPacketOverlay::*)>(std::string("qname"), [](const dnsdist::DNSPacketOverlay& overlay) -> const DNSName& { return overlay.d_qname; });
luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::*)>(std::string("qtype"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qtype; });
luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::*)>(std::string("qclass"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qclass; });
luaCtx.registerMember<dnsheader(dnsdist::DNSPacketOverlay::*)>(std::string("dh"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_header; });
+++ /dev/null
-../dnsdist-lua-bindings-dnsquestion.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dnsdist.hh"
+#include "dnsdist-async.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-internal-queries.hh"
+#include "dnsdist-lua.hh"
+#include "dnsparser.hh"
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
+{
+#ifndef DISABLE_NON_FFI_DQ_BINDINGS
+ /* DNSQuestion */
+ /* PowerDNS DNSQuestion compat */
+ luaCtx.registerMember<const ComboAddress(DNSQuestion::*)>(
+ "localaddr", [](const DNSQuestion& dnsQuestion) -> ComboAddress { return dnsQuestion.ids.origDest; }, [](DNSQuestion& dnsQuestion, const ComboAddress newLocal) { (void)newLocal; });
+ luaCtx.registerMember<const DNSName(DNSQuestion::*)>(
+ "qname", [](const DNSQuestion& dnsQuestion) -> DNSName { return dnsQuestion.ids.qname; }, [](DNSQuestion& dnsQuestion, const DNSName& newName) { (void)newName; });
+ luaCtx.registerMember<uint16_t(DNSQuestion::*)>(
+ "qtype", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.ids.qtype; }, [](DNSQuestion& dnsQuestion, uint16_t newType) { (void)newType; });
+ luaCtx.registerMember<uint16_t(DNSQuestion::*)>(
+ "qclass", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.ids.qclass; }, [](DNSQuestion& dnsQuestion, uint16_t newClass) { (void)newClass; });
+ luaCtx.registerMember<int(DNSQuestion::*)>(
+ "rcode",
+ [](const DNSQuestion& dnsQuestion) -> int {
+ return static_cast<int>(dnsQuestion.getHeader()->rcode);
+ },
+ [](DNSQuestion& dnsQuestion, int newRCode) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [newRCode](dnsheader& header) {
+ header.rcode = static_cast<decltype(header.rcode)>(newRCode);
+ return true;
+ });
+ });
+ luaCtx.registerMember<const ComboAddress(DNSQuestion::*)>(
+ "remoteaddr", [](const DNSQuestion& dnsQuestion) -> ComboAddress { return dnsQuestion.ids.origRemote; }, [](DNSQuestion& dnsQuestion, const ComboAddress newRemote) { (void)newRemote; });
+ /* DNSDist DNSQuestion */
+ luaCtx.registerMember<dnsheader*(DNSQuestion::*)>(
+ "dh",
+ [](const DNSQuestion& dnsQuestion) -> dnsheader* {
+ return dnsQuestion.getMutableHeader();
+ },
+ [](DNSQuestion& dnsQuestion, const dnsheader* dnsHeader) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&dnsHeader](dnsheader& header) {
+ header = *dnsHeader;
+ return true;
+ });
+ });
+ luaCtx.registerMember<uint16_t(DNSQuestion::*)>(
+ "len", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.getData().size(); }, [](DNSQuestion& dnsQuestion, uint16_t newlen) { dnsQuestion.getMutableData().resize(newlen); });
+ luaCtx.registerMember<uint8_t(DNSQuestion::*)>(
+ "opcode", [](const DNSQuestion& dnsQuestion) -> uint8_t { return dnsQuestion.getHeader()->opcode; }, [](DNSQuestion& dnsQuestion, uint8_t newOpcode) { (void)newOpcode; });
+ luaCtx.registerMember<bool(DNSQuestion::*)>(
+ "tcp", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.overTCP(); }, [](DNSQuestion& dnsQuestion, bool newTcp) { (void)newTcp; });
+ luaCtx.registerMember<bool(DNSQuestion::*)>(
+ "skipCache", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.ids.skipCache; }, [](DNSQuestion& dnsQuestion, bool newSkipCache) { dnsQuestion.ids.skipCache = newSkipCache; });
+ luaCtx.registerMember<std::string(DNSQuestion::*)>(
+ "pool", [](const DNSQuestion& dnsQuestion) -> std::string { return dnsQuestion.ids.poolName; }, [](DNSQuestion& dnsQuestion, const std::string& newPoolName) { dnsQuestion.ids.poolName = newPoolName; });
+ luaCtx.registerMember<bool(DNSQuestion::*)>(
+ "useECS", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.useECS; }, [](DNSQuestion& dnsQuestion, bool useECS) { dnsQuestion.useECS = useECS; });
+ luaCtx.registerMember<bool(DNSQuestion::*)>(
+ "ecsOverride", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.ecsOverride; }, [](DNSQuestion& dnsQuestion, bool ecsOverride) { dnsQuestion.ecsOverride = ecsOverride; });
+ luaCtx.registerMember<uint16_t(DNSQuestion::*)>(
+ "ecsPrefixLength", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.ecsPrefixLength; }, [](DNSQuestion& dnsQuestion, uint16_t newPrefixLength) { dnsQuestion.ecsPrefixLength = newPrefixLength; });
+ luaCtx.registerMember<boost::optional<uint32_t>(DNSQuestion::*)>(
+ "tempFailureTTL",
+ [](const DNSQuestion& dnsQuestion) -> boost::optional<uint32_t> {
+ return dnsQuestion.ids.tempFailureTTL;
+ },
+ [](DNSQuestion& dnsQuestion, boost::optional<uint32_t> newValue) {
+ dnsQuestion.ids.tempFailureTTL = newValue;
+ });
+ luaCtx.registerMember<std::string(DNSQuestion::*)>(
+ "deviceID", [](const DNSQuestion& dnsQuestion) -> std::string {
+ if (dnsQuestion.ids.d_protoBufData) {
+ return dnsQuestion.ids.d_protoBufData->d_deviceID;
+ }
+ return {}; }, [](DNSQuestion& dnsQuestion, const std::string& newValue) {
+ if (!dnsQuestion.ids.d_protoBufData) {
+ dnsQuestion.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+ }
+ dnsQuestion.ids.d_protoBufData->d_deviceID = newValue; });
+ luaCtx.registerMember<std::string(DNSQuestion::*)>(
+ "deviceName", [](const DNSQuestion& dnsQuestion) -> std::string {
+ if (dnsQuestion.ids.d_protoBufData) {
+ return dnsQuestion.ids.d_protoBufData->d_deviceName;
+ }
+ return {}; }, [](DNSQuestion& dnsQuestion, const std::string& newValue) {
+ if (!dnsQuestion.ids.d_protoBufData) {
+ dnsQuestion.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+ }
+ dnsQuestion.ids.d_protoBufData->d_deviceName = newValue; });
+ luaCtx.registerMember<std::string(DNSQuestion::*)>(
+ "requestorID", [](const DNSQuestion& dnsQuestion) -> std::string {
+ if (dnsQuestion.ids.d_protoBufData) {
+ return dnsQuestion.ids.d_protoBufData->d_requestorID;
+ }
+ return {}; }, [](DNSQuestion& dnsQuestion, const std::string& newValue) {
+ if (!dnsQuestion.ids.d_protoBufData) {
+ dnsQuestion.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+ }
+ dnsQuestion.ids.d_protoBufData->d_requestorID = newValue; });
+ luaCtx.registerFunction<bool (DNSQuestion::*)() const>("getDO", [](const DNSQuestion& dnsQuestion) {
+ return getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO;
+ });
+ luaCtx.registerFunction<std::string (DNSQuestion::*)() const>("getContent", [](const DNSQuestion& dnsQuestion) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ return std::string(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size());
+ });
+ luaCtx.registerFunction<void (DNSQuestion::*)(const std::string&)>("setContent", [](DNSQuestion& dnsQuestion, const std::string& raw) {
+ uint16_t oldID = dnsQuestion.getHeader()->id;
+ auto& buffer = dnsQuestion.getMutableData();
+ buffer.clear();
+ buffer.insert(buffer.begin(), raw.begin(), raw.end());
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [oldID](dnsheader& header) {
+ header.id = oldID;
+ return true;
+ });
+ });
+ luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView> (DNSQuestion::*)() const>("getEDNSOptions", [](const DNSQuestion& dnsQuestion) {
+ if (dnsQuestion.ednsOptions == nullptr) {
+ parseEDNSOptions(dnsQuestion);
+ if (dnsQuestion.ednsOptions == nullptr) {
+ throw std::runtime_error("parseEDNSOptions should have populated the EDNS options");
+ }
+ }
+
+ return *dnsQuestion.ednsOptions;
+ });
+ luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getTrailingData", [](const DNSQuestion& dnsQuestion) {
+ return dnsQuestion.getTrailingData();
+ });
+ luaCtx.registerFunction<bool (DNSQuestion::*)(std::string)>("setTrailingData", [](DNSQuestion& dnsQuestion, const std::string& tail) {
+ return dnsQuestion.setTrailingData(tail);
+ });
+
+ luaCtx.registerFunction<std::string (DNSQuestion::*)() const>("getServerNameIndication", [](const DNSQuestion& dnsQuestion) {
+ return dnsQuestion.sni;
+ });
+
+ luaCtx.registerFunction<std::string (DNSQuestion::*)() const>("getProtocol", [](const DNSQuestion& dnsQuestion) {
+ return dnsQuestion.getProtocol().toPrettyString();
+ });
+
+ luaCtx.registerFunction<timespec (DNSQuestion::*)() const>("getQueryTime", [](const DNSQuestion& dnsQuestion) {
+ return dnsQuestion.ids.queryRealTime.getStartTime();
+ });
+
+ luaCtx.registerFunction<void (DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dnsQuestion, boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+ if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendDNSTrap(dnsQuestion, reason ? *reason : "");
+ }
+#endif /* HAVE_NET_SNMP */
+ });
+
+ luaCtx.registerFunction<void (DNSQuestion::*)(std::string, std::string)>("setTag", [](DNSQuestion& dnsQuestion, const std::string& strLabel, const std::string& strValue) {
+ dnsQuestion.setTag(strLabel, strValue);
+ });
+ luaCtx.registerFunction<void (DNSQuestion::*)(LuaAssociativeTable<std::string>)>("setTagArray", [](DNSQuestion& dnsQuestion, const LuaAssociativeTable<std::string>& tags) {
+ for (const auto& tag : tags) {
+ dnsQuestion.setTag(tag.first, tag.second);
+ }
+ });
+ luaCtx.registerFunction<string (DNSQuestion::*)(std::string) const>("getTag", [](const DNSQuestion& dnsQuestion, const std::string& strLabel) {
+ if (!dnsQuestion.ids.qTag) {
+ return string();
+ }
+
+ std::string strValue;
+ const auto tagIt = dnsQuestion.ids.qTag->find(strLabel);
+ if (tagIt == dnsQuestion.ids.qTag->cend()) {
+ return string();
+ }
+ return tagIt->second;
+ });
+ luaCtx.registerFunction<QTag (DNSQuestion::*)(void) const>("getTagArray", [](const DNSQuestion& dnsQuestion) {
+ if (!dnsQuestion.ids.qTag) {
+ QTag empty;
+ return empty;
+ }
+
+ return *dnsQuestion.ids.qTag;
+ });
+
+ luaCtx.registerFunction<void (DNSQuestion::*)(LuaArray<std::string>)>("setProxyProtocolValues", [](DNSQuestion& dnsQuestion, const LuaArray<std::string>& values) {
+ if (!dnsQuestion.proxyProtocolValues) {
+ dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+ }
+
+ dnsQuestion.proxyProtocolValues->clear();
+ dnsQuestion.proxyProtocolValues->reserve(values.size());
+ for (const auto& value : values) {
+ checkParameterBound("setProxyProtocolValues", value.first, std::numeric_limits<uint8_t>::max());
+ dnsQuestion.proxyProtocolValues->push_back({value.second, static_cast<uint8_t>(value.first)});
+ }
+ });
+
+ luaCtx.registerFunction<void (DNSQuestion::*)(uint64_t, std::string)>("addProxyProtocolValue", [](DNSQuestion& dnsQuestion, uint64_t type, std::string value) {
+ checkParameterBound("addProxyProtocolValue", type, std::numeric_limits<uint8_t>::max());
+ if (!dnsQuestion.proxyProtocolValues) {
+ dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+ }
+
+ dnsQuestion.proxyProtocolValues->push_back({std::move(value), static_cast<uint8_t>(type)});
+ });
+
+ luaCtx.registerFunction<LuaArray<std::string> (DNSQuestion::*)()>("getProxyProtocolValues", [](const DNSQuestion& dnsQuestion) {
+ LuaArray<std::string> result;
+ if (!dnsQuestion.proxyProtocolValues) {
+ return result;
+ }
+
+ result.resize(dnsQuestion.proxyProtocolValues->size());
+ for (const auto& value : *dnsQuestion.proxyProtocolValues) {
+ result.emplace_back(value.type, value.content);
+ }
+
+ return result;
+ });
+
+ luaCtx.registerFunction<bool (DNSQuestion::*)(const DNSName& newName)>("changeName", [](DNSQuestion& dnsQuestion, const DNSName& newName) -> bool {
+ if (!dnsdist::changeNameInDNSPacket(dnsQuestion.getMutableData(), dnsQuestion.ids.qname, newName)) {
+ return false;
+ }
+ dnsQuestion.ids.qname = newName;
+ return true;
+ });
+
+ luaCtx.registerFunction<void (DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>&, boost::optional<uint16_t>)>("spoof", [](DNSQuestion& dnsQuestion, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response, boost::optional<uint16_t> typeForAny) {
+ if (response.type() == typeid(LuaArray<ComboAddress>)) {
+ std::vector<ComboAddress> data;
+ auto responses = boost::get<LuaArray<ComboAddress>>(response);
+ data.reserve(responses.size());
+ for (const auto& resp : responses) {
+ data.push_back(resp.second);
+ }
+ std::string result;
+ SpoofAction tempSpoofAction(data);
+ tempSpoofAction(&dnsQuestion, &result);
+ return;
+ }
+ if (response.type() == typeid(LuaArray<std::string>)) {
+ std::vector<std::string> data;
+ auto responses = boost::get<LuaArray<std::string>>(response);
+ data.reserve(responses.size());
+ for (const auto& resp : responses) {
+ data.push_back(resp.second);
+ }
+ std::string result;
+ SpoofAction tempSpoofAction(data, typeForAny ? *typeForAny : std::optional<uint16_t>());
+ tempSpoofAction(&dnsQuestion, &result);
+ return;
+ }
+ });
+
+ luaCtx.registerFunction<void (DNSQuestion::*)(uint16_t code, const std::string&)>("setEDNSOption", [](DNSQuestion& dnsQuestion, uint16_t code, const std::string& data) {
+ setEDNSOption(dnsQuestion, code, data);
+ });
+
+ luaCtx.registerFunction<void (DNSQuestion::*)(uint16_t infoCode, const boost::optional<std::string>& extraText)>("setExtendedDNSError", [](DNSQuestion& dnsQuestion, uint16_t infoCode, const boost::optional<std::string>& extraText) {
+ EDNSExtendedError ede;
+ ede.infoCode = infoCode;
+ if (extraText) {
+ ede.extraText = *extraText;
+ }
+ dnsQuestion.ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+ });
+
+ luaCtx.registerFunction<bool (DNSQuestion::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSQuestion& dnsQuestion, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
+ dnsQuestion.asynchronous = true;
+ return dnsdist::suspendQuery(dnsQuestion, asyncID, queryID, timeoutMs);
+ });
+
+ luaCtx.registerFunction<bool (DNSQuestion::*)()>("setRestartable", [](DNSQuestion& dnsQuestion) {
+ dnsQuestion.ids.d_packet = std::make_unique<PacketBuffer>(dnsQuestion.getData());
+ return true;
+ });
+
+ class AsynchronousObject
+ {
+ public:
+ AsynchronousObject(std::unique_ptr<CrossProtocolQuery>&& obj_) :
+ object(std::move(obj_))
+ {
+ }
+
+ [[nodiscard]] DNSQuestion getDQ() const
+ {
+ return object->getDQ();
+ }
+
+ [[nodiscard]] DNSResponse getDR() const
+ {
+ return object->getDR();
+ }
+
+ bool resume()
+ {
+ return dnsdist::queueQueryResumptionEvent(std::move(object));
+ }
+
+ bool drop()
+ {
+ auto sender = object->getTCPQuerySender();
+ if (!sender) {
+ return false;
+ }
+
+ timeval now{};
+ gettimeofday(&now, nullptr);
+ sender->notifyIOError(now, TCPResponse(std::move(object->query)));
+ return true;
+ }
+
+ bool setRCode(uint8_t rcode, bool clearAnswers)
+ {
+ return dnsdist::setInternalQueryRCode(object->query.d_idstate, object->query.d_buffer, rcode, clearAnswers);
+ }
+
+ private:
+ std::unique_ptr<CrossProtocolQuery> object;
+ };
+
+ luaCtx.registerFunction<DNSQuestion (AsynchronousObject::*)(void) const>("getDQ", [](const AsynchronousObject& obj) {
+ return obj.getDQ();
+ });
+
+ luaCtx.registerFunction<DNSQuestion (AsynchronousObject::*)(void) const>("getDR", [](const AsynchronousObject& obj) {
+ return obj.getDR();
+ });
+
+ luaCtx.registerFunction<bool (AsynchronousObject::*)(void)>("resume", [](AsynchronousObject& obj) {
+ return obj.resume();
+ });
+
+ luaCtx.registerFunction<bool (AsynchronousObject::*)(void)>("drop", [](AsynchronousObject& obj) {
+ return obj.drop();
+ });
+
+ luaCtx.registerFunction<bool (AsynchronousObject::*)(uint8_t, bool)>("setRCode", [](AsynchronousObject& obj, uint8_t rcode, bool clearAnswers) {
+ return obj.setRCode(rcode, clearAnswers);
+ });
+
+ luaCtx.writeFunction("getAsynchronousObject", [](uint16_t asyncID, uint16_t queryID) -> AsynchronousObject {
+ if (!dnsdist::g_asyncHolder) {
+ throw std::runtime_error("Unable to resume, no asynchronous holder");
+ }
+ auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
+ if (!query) {
+ throw std::runtime_error("Unable to find asynchronous object");
+ }
+ return {std::move(query)};
+ });
+
+ /* LuaWrapper doesn't support inheritance */
+ luaCtx.registerMember<const ComboAddress(DNSResponse::*)>(
+ "localaddr", [](const DNSResponse& dnsQuestion) -> ComboAddress { return dnsQuestion.ids.origDest; }, [](DNSResponse& dnsQuestion, const ComboAddress newLocal) { (void)newLocal; });
+ luaCtx.registerMember<const DNSName(DNSResponse::*)>(
+ "qname", [](const DNSResponse& dnsQuestion) -> DNSName { return dnsQuestion.ids.qname; }, [](DNSResponse& dnsQuestion, const DNSName& newName) { (void)newName; });
+ luaCtx.registerMember<uint16_t(DNSResponse::*)>(
+ "qtype", [](const DNSResponse& dnsQuestion) -> uint16_t { return dnsQuestion.ids.qtype; }, [](DNSResponse& dnsQuestion, uint16_t newType) { (void)newType; });
+ luaCtx.registerMember<uint16_t(DNSResponse::*)>(
+ "qclass", [](const DNSResponse& dnsQuestion) -> uint16_t { return dnsQuestion.ids.qclass; }, [](DNSResponse& dnsQuestion, uint16_t newClass) { (void)newClass; });
+ luaCtx.registerMember<int(DNSResponse::*)>(
+ "rcode",
+ [](const DNSResponse& dnsQuestion) -> int {
+ return static_cast<int>(dnsQuestion.getHeader()->rcode);
+ },
+ [](DNSResponse& dnsQuestion, int newRCode) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [newRCode](dnsheader& header) {
+ header.rcode = static_cast<decltype(header.rcode)>(newRCode);
+ return true;
+ });
+ });
+ luaCtx.registerMember<ComboAddress(DNSResponse::*)>(
+ "remoteaddr", [](const DNSResponse& dnsQuestion) -> ComboAddress { return dnsQuestion.ids.origRemote; }, [](DNSResponse& dnsQuestion, const ComboAddress newRemote) { (void)newRemote; });
+ luaCtx.registerMember<dnsheader*(DNSResponse::*)>(
+ "dh",
+ [](const DNSResponse& dnsResponse) -> dnsheader* {
+ return dnsResponse.getMutableHeader();
+ },
+ [](DNSResponse& dnsResponse, const dnsheader* dnsHeader) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [&dnsHeader](dnsheader& header) {
+ header = *dnsHeader;
+ return true;
+ });
+ });
+ luaCtx.registerMember<uint16_t(DNSResponse::*)>(
+ "len", [](const DNSResponse& dnsQuestion) -> uint16_t { return dnsQuestion.getData().size(); }, [](DNSResponse& dnsQuestion, uint16_t newlen) { dnsQuestion.getMutableData().resize(newlen); });
+ luaCtx.registerMember<uint8_t(DNSResponse::*)>(
+ "opcode", [](const DNSResponse& dnsQuestion) -> uint8_t { return dnsQuestion.getHeader()->opcode; }, [](DNSResponse& dnsQuestion, uint8_t newOpcode) { (void)newOpcode; });
+ luaCtx.registerMember<bool(DNSResponse::*)>(
+ "tcp", [](const DNSResponse& dnsQuestion) -> bool { return dnsQuestion.overTCP(); }, [](DNSResponse& dnsQuestion, bool newTcp) { (void)newTcp; });
+ luaCtx.registerMember<bool(DNSResponse::*)>(
+ "skipCache", [](const DNSResponse& dnsQuestion) -> bool { return dnsQuestion.ids.skipCache; }, [](DNSResponse& dnsQuestion, bool newSkipCache) { dnsQuestion.ids.skipCache = newSkipCache; });
+ luaCtx.registerMember<std::string(DNSResponse::*)>(
+ "pool", [](const DNSResponse& dnsQuestion) -> std::string { return dnsQuestion.ids.poolName; }, [](DNSResponse& dnsQuestion, const std::string& newPoolName) { dnsQuestion.ids.poolName = newPoolName; });
+ luaCtx.registerFunction<void (DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](DNSResponse& dnsResponse, const std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)>& editFunc) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ editDNSPacketTTL(reinterpret_cast<char*>(dnsResponse.getMutableData().data()), dnsResponse.getData().size(), editFunc);
+ });
+ luaCtx.registerFunction<bool (DNSResponse::*)() const>("getDO", [](const DNSResponse& dnsQuestion) {
+ return getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO;
+ });
+ luaCtx.registerFunction<std::string (DNSResponse::*)() const>("getContent", [](const DNSResponse& dnsQuestion) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ return std::string(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size());
+ });
+ luaCtx.registerFunction<void (DNSResponse::*)(const std::string&)>("setContent", [](DNSResponse& dnsResponse, const std::string& raw) {
+ uint16_t oldID = dnsResponse.getHeader()->id;
+ auto& buffer = dnsResponse.getMutableData();
+ buffer.clear();
+ buffer.insert(buffer.begin(), raw.begin(), raw.end());
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [oldID](dnsheader& header) {
+ header.id = oldID;
+ return true;
+ });
+ });
+
+ luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView> (DNSResponse::*)() const>("getEDNSOptions", [](const DNSResponse& dnsQuestion) {
+ if (dnsQuestion.ednsOptions == nullptr) {
+ parseEDNSOptions(dnsQuestion);
+ if (dnsQuestion.ednsOptions == nullptr) {
+ throw std::runtime_error("parseEDNSOptions should have populated the EDNS options");
+ }
+ }
+
+ return *dnsQuestion.ednsOptions;
+ });
+ luaCtx.registerFunction<std::string (DNSResponse::*)(void) const>("getTrailingData", [](const DNSResponse& dnsQuestion) {
+ return dnsQuestion.getTrailingData();
+ });
+ luaCtx.registerFunction<bool (DNSResponse::*)(std::string)>("setTrailingData", [](DNSResponse& dnsQuestion, const std::string& tail) {
+ return dnsQuestion.setTrailingData(tail);
+ });
+
+ luaCtx.registerFunction<void (DNSResponse::*)(std::string, std::string)>("setTag", [](DNSResponse& dnsResponse, const std::string& strLabel, const std::string& strValue) {
+ dnsResponse.setTag(strLabel, strValue);
+ });
+
+ luaCtx.registerFunction<void (DNSResponse::*)(LuaAssociativeTable<std::string>)>("setTagArray", [](DNSResponse& dnsResponse, const LuaAssociativeTable<string>& tags) {
+ for (const auto& tag : tags) {
+ dnsResponse.setTag(tag.first, tag.second);
+ }
+ });
+ luaCtx.registerFunction<string (DNSResponse::*)(std::string) const>("getTag", [](const DNSResponse& dnsResponse, const std::string& strLabel) {
+ if (!dnsResponse.ids.qTag) {
+ return string();
+ }
+
+ std::string strValue;
+ const auto tagIt = dnsResponse.ids.qTag->find(strLabel);
+ if (tagIt == dnsResponse.ids.qTag->cend()) {
+ return string();
+ }
+ return tagIt->second;
+ });
+ luaCtx.registerFunction<QTag (DNSResponse::*)(void) const>("getTagArray", [](const DNSResponse& dnsResponse) {
+ if (!dnsResponse.ids.qTag) {
+ QTag empty;
+ return empty;
+ }
+
+ return *dnsResponse.ids.qTag;
+ });
+
+ luaCtx.registerFunction<std::string (DNSResponse::*)() const>("getProtocol", [](const DNSResponse& dnsResponse) {
+ return dnsResponse.getProtocol().toPrettyString();
+ });
+
+ luaCtx.registerFunction<timespec (DNSResponse::*)() const>("getQueryTime", [](const DNSResponse& dnsResponse) {
+ return dnsResponse.ids.queryRealTime.getStartTime();
+ });
+
+ luaCtx.registerFunction<void (DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dnsResponse, boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+ if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendDNSTrap(dnsResponse, reason ? *reason : "");
+ }
+#endif /* HAVE_NET_SNMP */
+ });
+
+#ifdef HAVE_DNS_OVER_HTTPS
+ luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPPath", [](const DNSQuestion& dnsQuestion) {
+ if (dnsQuestion.ids.du == nullptr) {
+ return std::string();
+ }
+ return dnsQuestion.ids.du->getHTTPPath();
+ });
+
+ luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPQueryString", [](const DNSQuestion& dnsQuestion) {
+ if (dnsQuestion.ids.du == nullptr) {
+ return std::string();
+ }
+ return dnsQuestion.ids.du->getHTTPQueryString();
+ });
+
+ luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPHost", [](const DNSQuestion& dnsQuestion) {
+ if (dnsQuestion.ids.du == nullptr) {
+ return std::string();
+ }
+ return dnsQuestion.ids.du->getHTTPHost();
+ });
+
+ luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPScheme", [](const DNSQuestion& dnsQuestion) {
+ if (dnsQuestion.ids.du == nullptr) {
+ return std::string();
+ }
+ return dnsQuestion.ids.du->getHTTPScheme();
+ });
+
+ luaCtx.registerFunction<LuaAssociativeTable<std::string> (DNSQuestion::*)(void) const>("getHTTPHeaders", [](const DNSQuestion& dnsQuestion) {
+ if (dnsQuestion.ids.du == nullptr) {
+ return LuaAssociativeTable<std::string>();
+ }
+ return dnsQuestion.ids.du->getHTTPHeaders();
+ });
+
+ luaCtx.registerFunction<void (DNSQuestion::*)(uint64_t statusCode, const std::string& body, const boost::optional<std::string> contentType)>("setHTTPResponse", [](DNSQuestion& dnsQuestion, uint64_t statusCode, const std::string& body, const boost::optional<std::string>& contentType) {
+ if (dnsQuestion.ids.du == nullptr) {
+ return;
+ }
+ checkParameterBound("DNSQuestion::setHTTPResponse", statusCode, std::numeric_limits<uint16_t>::max());
+ PacketBuffer vect(body.begin(), body.end());
+ dnsQuestion.ids.du->setHTTPResponse(statusCode, std::move(vect), contentType ? *contentType : "");
+ });
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+ luaCtx.registerFunction<bool (DNSQuestion::*)(bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum)>("setNegativeAndAdditionalSOA", [](DNSQuestion& dnsQuestion, bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum) {
+ checkParameterBound("setNegativeAndAdditionalSOA", ttl, std::numeric_limits<uint32_t>::max());
+ checkParameterBound("setNegativeAndAdditionalSOA", serial, std::numeric_limits<uint32_t>::max());
+ checkParameterBound("setNegativeAndAdditionalSOA", refresh, std::numeric_limits<uint32_t>::max());
+ checkParameterBound("setNegativeAndAdditionalSOA", retry, std::numeric_limits<uint32_t>::max());
+ checkParameterBound("setNegativeAndAdditionalSOA", expire, std::numeric_limits<uint32_t>::max());
+ checkParameterBound("setNegativeAndAdditionalSOA", minimum, std::numeric_limits<uint32_t>::max());
+
+ return setNegativeAndAdditionalSOA(dnsQuestion, nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum, false);
+ });
+
+ luaCtx.registerFunction<void (DNSResponse::*)(uint16_t infoCode, const boost::optional<std::string>& extraText)>("setExtendedDNSError", [](DNSResponse& dnsResponse, uint16_t infoCode, const boost::optional<std::string>& extraText) {
+ EDNSExtendedError ede;
+ ede.infoCode = infoCode;
+ if (extraText) {
+ ede.extraText = *extraText;
+ }
+ dnsResponse.ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+ });
+
+ luaCtx.registerFunction<bool (DNSResponse::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSResponse& dnsResponse, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
+ dnsResponse.asynchronous = true;
+ return dnsdist::suspendResponse(dnsResponse, asyncID, queryID, timeoutMs);
+ });
+
+ luaCtx.registerFunction<bool (DNSResponse::*)(const DNSName& newName)>("changeName", [](DNSResponse& dnsResponse, const DNSName& newName) -> bool {
+ if (!dnsdist::changeNameInDNSPacket(dnsResponse.getMutableData(), dnsResponse.ids.qname, newName)) {
+ return false;
+ }
+ dnsResponse.ids.qname = newName;
+ return true;
+ });
+
+ luaCtx.registerFunction<bool (DNSResponse::*)()>("restart", [](DNSResponse& dnsResponse) {
+ if (!dnsResponse.ids.d_packet) {
+ return false;
+ }
+ dnsResponse.asynchronous = true;
+ dnsResponse.getMutableData() = *dnsResponse.ids.d_packet;
+ auto query = dnsdist::getInternalQueryFromDQ(dnsResponse, false);
+ return dnsdist::queueQueryResumptionEvent(std::move(query));
+ });
+
+ luaCtx.registerFunction<std::shared_ptr<DownstreamState> (DNSResponse::*)(void) const>("getSelectedBackend", [](const DNSResponse& dnsResponse) {
+ return dnsResponse.d_downstream;
+ });
+#endif /* DISABLE_NON_FFI_DQ_BINDINGS */
+}
return false;
}
- return listener->addUnixListeningEndpoint(path, endpointID, [cb](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+ return listener->addUnixListeningEndpoint(path, endpointID, [cb = std::move(cb)](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
{
auto lock = g_lua.lock();
cb(endpoint, dgram, from);
size_t maxNegativeTTL = 3600;
size_t staleTTL = 60;
size_t numberOfShards = 20;
+ size_t maxEntrySize{0};
bool dontAge = false;
bool deferrableInsertLock = true;
bool ecsParsing = false;
getOptionalValue<size_t>(vars, "staleTTL", staleTTL);
getOptionalValue<size_t>(vars, "temporaryFailureTTL", tempFailTTL);
getOptionalValue<bool>(vars, "cookieHashing", cookieHashing);
+ getOptionalValue<size_t>(vars, "maximumEntrySize", maxEntrySize);
if (getOptionalValue<decltype(skipOptions)>(vars, "skipOptions", skipOptions) > 0) {
for (const auto& option : skipOptions) {
res->setKeepStaleData(keepStaleData);
res->setSkippedOptions(optionsToSkip);
+ if (maxEntrySize >= sizeof(dnsheader)) {
+ res->setMaximumEntrySize(maxEntrySize);
+ }
return res;
});
return results;
});
- luaCtx.registerMember<DNSName(LuaRingEntry::*)>(std::string("qname"), [](const LuaRingEntry& entry) {
+ luaCtx.registerMember<DNSName(LuaRingEntry::*)>(std::string("qname"), [](const LuaRingEntry& entry) -> const DNSName& {
return entry.qname;
});
- luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("requestor"), [](const LuaRingEntry& entry) {
+ luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("requestor"), [](const LuaRingEntry& entry) -> const ComboAddress& {
return entry.requestor;
});
- luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("backend"), [](const LuaRingEntry& entry) {
+ luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("backend"), [](const LuaRingEntry& entry) -> const ComboAddress& {
return entry.ds;
});
return entry.when;
});
- luaCtx.registerMember<std::string(LuaRingEntry::*)>(std::string("macAddress"), [](const LuaRingEntry& entry) {
+ luaCtx.registerMember<std::string(LuaRingEntry::*)>(std::string("macAddress"), [](const LuaRingEntry& entry) -> const std::string& {
return entry.macAddr;
});
+++ /dev/null
-../dnsdist-lua-bindings.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "bpf-filter.hh"
+#include "config.h"
+#include "dnsdist.hh"
+#include "dnsdist-async.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-resolver.hh"
+#include "dnsdist-svc.hh"
+#include "dnsdist-xsk.hh"
+
+#include "dolog.hh"
+#include "xsk.hh"
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck)
+{
+ luaCtx.writeFunction("vinfolog", [](const string& arg) {
+ vinfolog("%s", arg);
+ });
+ luaCtx.writeFunction("infolog", [](const string& arg) {
+ infolog("%s", arg);
+ });
+ luaCtx.writeFunction("errlog", [](const string& arg) {
+ errlog("%s", arg);
+ });
+ luaCtx.writeFunction("warnlog", [](const string& arg) {
+ warnlog("%s", arg);
+ });
+ luaCtx.writeFunction("show", [](const string& arg) {
+ g_outputBuffer += arg;
+ g_outputBuffer += "\n";
+ });
+
+ /* Exceptions */
+ luaCtx.registerFunction<string (std::exception_ptr::*)() const>("__tostring", [](const std::exception_ptr& eptr) -> std::string {
+ try {
+ if (eptr) {
+ std::rethrow_exception(eptr);
+ }
+ }
+ catch (const std::exception& e) {
+ return {e.what()};
+ }
+ catch (const PDNSException& e) {
+ return e.reason;
+ }
+ catch (...) {
+ return {"Unknown exception"};
+ }
+ return {"No exception"};
+ });
+#ifndef DISABLE_POLICIES_BINDINGS
+ /* ServerPolicy */
+ luaCtx.writeFunction("newServerPolicy", [](const string& name, const ServerPolicy::policyfunc_t& policy) { return std::make_shared<ServerPolicy>(name, policy, true); });
+ luaCtx.registerMember("name", &ServerPolicy::d_name);
+ luaCtx.registerMember("policy", &ServerPolicy::d_policy);
+ luaCtx.registerMember("ffipolicy", &ServerPolicy::d_ffipolicy);
+ luaCtx.registerMember("isLua", &ServerPolicy::d_isLua);
+ luaCtx.registerMember("isFFI", &ServerPolicy::d_isFFI);
+ luaCtx.registerMember("isPerThread", &ServerPolicy::d_isPerThread);
+ luaCtx.registerFunction("toString", &ServerPolicy::toString);
+ luaCtx.registerFunction("__tostring", &ServerPolicy::toString);
+
+ const std::array<std::shared_ptr<ServerPolicy>, 6> policies = {
+ std::make_shared<ServerPolicy>("firstAvailable", firstAvailable, false),
+ std::make_shared<ServerPolicy>("roundrobin", roundrobin, false),
+ std::make_shared<ServerPolicy>("wrandom", wrandom, false),
+ std::make_shared<ServerPolicy>("whashed", whashed, false),
+ std::make_shared<ServerPolicy>("chashed", chashed, false),
+ std::make_shared<ServerPolicy>("leastOutstanding", leastOutstanding, false)};
+ for (const auto& policy : policies) {
+ luaCtx.writeVariable(policy->d_name, policy);
+ }
+
+#endif /* DISABLE_POLICIES_BINDINGS */
+
+ /* ServerPool */
+ luaCtx.registerFunction<void (std::shared_ptr<ServerPool>::*)(std::shared_ptr<DNSDistPacketCache>)>("setCache", [](const std::shared_ptr<ServerPool>& pool, std::shared_ptr<DNSDistPacketCache> cache) {
+ if (pool) {
+ pool->packetCache = std::move(cache);
+ }
+ });
+ luaCtx.registerFunction("getCache", &ServerPool::getCache);
+ luaCtx.registerFunction<void (std::shared_ptr<ServerPool>::*)()>("unsetCache", [](const std::shared_ptr<ServerPool>& pool) {
+ if (pool) {
+ pool->packetCache = nullptr;
+ }
+ });
+ luaCtx.registerFunction("getECS", &ServerPool::getECS);
+ luaCtx.registerFunction("setECS", &ServerPool::setECS);
+
+#ifndef DISABLE_DOWNSTREAM_BINDINGS
+ /* DownstreamState */
+ luaCtx.registerFunction<void (DownstreamState::*)(int)>("setQPS", [](DownstreamState& state, int lim) { state.qps = lim > 0 ? QPSLimiter(lim, lim) : QPSLimiter(); });
+ luaCtx.registerFunction<void (std::shared_ptr<DownstreamState>::*)(string)>("addPool", [](const std::shared_ptr<DownstreamState>& state, const string& pool) {
+ auto localPools = g_pools.getCopy();
+ addServerToPool(localPools, pool, state);
+ g_pools.setState(localPools);
+ state->d_config.pools.insert(pool);
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](const std::shared_ptr<DownstreamState>& state, const string& pool) {
+ auto localPools = g_pools.getCopy();
+ removeServerFromPool(localPools, pool, state);
+ g_pools.setState(localPools);
+ state->d_config.pools.erase(pool);
+ });
+ luaCtx.registerFunction<uint64_t (DownstreamState::*)() const>("getOutstanding", [](const DownstreamState& state) { return state.outstanding.load(); });
+ luaCtx.registerFunction<uint64_t (DownstreamState::*)() const>("getDrops", [](const DownstreamState& state) { return state.reuseds.load(); });
+ luaCtx.registerFunction<double (DownstreamState::*)() const>("getLatency", [](const DownstreamState& state) { return state.getRelevantLatencyUsec(); });
+ luaCtx.registerFunction("isUp", &DownstreamState::isUp);
+ luaCtx.registerFunction("setDown", &DownstreamState::setDown);
+ luaCtx.registerFunction("setUp", &DownstreamState::setUp);
+ luaCtx.registerFunction<void (DownstreamState::*)(boost::optional<bool> newStatus)>("setAuto", [](DownstreamState& state, boost::optional<bool> newStatus) {
+ if (newStatus) {
+ state.setUpStatus(*newStatus);
+ }
+ state.setAuto();
+ });
+ luaCtx.registerFunction<void (DownstreamState::*)(boost::optional<bool> newStatus)>("setLazyAuto", [](DownstreamState& state, boost::optional<bool> newStatus) {
+ if (newStatus) {
+ state.setUpStatus(*newStatus);
+ }
+ state.setLazyAuto();
+ });
+ luaCtx.registerFunction<std::string (DownstreamState::*)() const>("getName", [](const DownstreamState& state) -> const std::string& { return state.getName(); });
+ luaCtx.registerFunction<std::string (DownstreamState::*)() const>("getNameWithAddr", [](const DownstreamState& state) -> const std::string& { return state.getNameWithAddr(); });
+ luaCtx.registerMember("upStatus", &DownstreamState::upStatus);
+ luaCtx.registerMember<int(DownstreamState::*)>(
+ "weight",
+ [](const DownstreamState& state) -> int { return state.d_config.d_weight; },
+ [](DownstreamState& state, int newWeight) { state.setWeight(newWeight); });
+ luaCtx.registerMember<int(DownstreamState::*)>(
+ "order",
+ [](const DownstreamState& state) -> int { return state.d_config.order; },
+ [](DownstreamState& state, int newOrder) { state.d_config.order = newOrder; });
+ luaCtx.registerMember<const std::string(DownstreamState::*)>(
+ "name", [](const DownstreamState& backend) -> std::string { return backend.getName(); }, [](DownstreamState& backend, const std::string& newName) { backend.setName(newName); });
+ luaCtx.registerFunction<std::string (DownstreamState::*)() const>("getID", [](const DownstreamState& state) { return boost::uuids::to_string(*state.d_config.id); });
+#endif /* DISABLE_DOWNSTREAM_BINDINGS */
+
+#ifndef DISABLE_DNSHEADER_BINDINGS
+ /* dnsheader */
+ luaCtx.registerFunction<void (dnsheader::*)(bool)>("setRD", [](dnsheader& dnsHeader, bool value) {
+ dnsHeader.rd = value;
+ });
+
+ luaCtx.registerFunction<bool (dnsheader::*)() const>("getRD", [](const dnsheader& dnsHeader) {
+ return (bool)dnsHeader.rd;
+ });
+
+ luaCtx.registerFunction<void (dnsheader::*)(bool)>("setRA", [](dnsheader& dnsHeader, bool value) {
+ dnsHeader.ra = value;
+ });
+
+ luaCtx.registerFunction<bool (dnsheader::*)() const>("getRA", [](const dnsheader& dnsHeader) {
+ return (bool)dnsHeader.ra;
+ });
+
+ luaCtx.registerFunction<void (dnsheader::*)(bool)>("setAD", [](dnsheader& dnsHeader, bool value) {
+ dnsHeader.ad = value;
+ });
+
+ luaCtx.registerFunction<bool (dnsheader::*)() const>("getAD", [](const dnsheader& dnsHeader) {
+ return (bool)dnsHeader.ad;
+ });
+
+ luaCtx.registerFunction<void (dnsheader::*)(bool)>("setAA", [](dnsheader& dnsHeader, bool value) {
+ dnsHeader.aa = value;
+ });
+
+ luaCtx.registerFunction<bool (dnsheader::*)() const>("getAA", [](const dnsheader& dnsHeader) {
+ return (bool)dnsHeader.aa;
+ });
+
+ luaCtx.registerFunction<void (dnsheader::*)(bool)>("setCD", [](dnsheader& dnsHeader, bool value) {
+ dnsHeader.cd = value;
+ });
+
+ luaCtx.registerFunction<bool (dnsheader::*)() const>("getCD", [](const dnsheader& dnsHeader) {
+ return (bool)dnsHeader.cd;
+ });
+
+ luaCtx.registerFunction<uint16_t (dnsheader::*)() const>("getID", [](const dnsheader& dnsHeader) {
+ return ntohs(dnsHeader.id);
+ });
+
+ luaCtx.registerFunction<bool (dnsheader::*)() const>("getTC", [](const dnsheader& dnsHeader) {
+ return (bool)dnsHeader.tc;
+ });
+
+ luaCtx.registerFunction<void (dnsheader::*)(bool)>("setTC", [](dnsheader& dnsHeader, bool value) {
+ dnsHeader.tc = value;
+ if (value) {
+ dnsHeader.ra = dnsHeader.rd; // you'll always need this, otherwise TC=1 gets ignored
+ }
+ });
+
+ luaCtx.registerFunction<void (dnsheader::*)(bool)>("setQR", [](dnsheader& dnsHeader, bool value) {
+ dnsHeader.qr = value;
+ });
+#endif /* DISABLE_DNSHEADER_BINDINGS */
+
+#ifndef DISABLE_COMBO_ADDR_BINDINGS
+ /* ComboAddress */
+ luaCtx.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); });
+ luaCtx.writeFunction("newCAFromRaw", [](const std::string& raw, boost::optional<uint16_t> port) {
+ if (raw.size() == 4) {
+ sockaddr_in sin4{};
+ memset(&sin4, 0, sizeof(sin4));
+ sin4.sin_family = AF_INET;
+ memcpy(&sin4.sin_addr.s_addr, raw.c_str(), raw.size());
+ if (port) {
+ sin4.sin_port = htons(*port);
+ }
+ return ComboAddress(&sin4);
+ }
+ if (raw.size() == 16) {
+ sockaddr_in6 sin6{};
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ memcpy(&sin6.sin6_addr.s6_addr, raw.c_str(), raw.size());
+ if (port) {
+ sin6.sin6_port = htons(*port);
+ }
+ return ComboAddress(&sin6);
+ }
+ return ComboAddress();
+ });
+ luaCtx.registerFunction<string (ComboAddress::*)() const>("tostring", [](const ComboAddress& addr) { return addr.toString(); });
+ luaCtx.registerFunction<string (ComboAddress::*)() const>("tostringWithPort", [](const ComboAddress& addr) { return addr.toStringWithPort(); });
+ luaCtx.registerFunction<string (ComboAddress::*)() const>("__tostring", [](const ComboAddress& addr) { return addr.toString(); });
+ luaCtx.registerFunction<string (ComboAddress::*)() const>("toString", [](const ComboAddress& addr) { return addr.toString(); });
+ luaCtx.registerFunction<string (ComboAddress::*)() const>("toStringWithPort", [](const ComboAddress& addr) { return addr.toStringWithPort(); });
+ luaCtx.registerFunction<uint16_t (ComboAddress::*)() const>("getPort", [](const ComboAddress& addr) { return ntohs(addr.sin4.sin_port); });
+ luaCtx.registerFunction<void (ComboAddress::*)(unsigned int)>("truncate", [](ComboAddress& addr, unsigned int bits) { addr.truncate(bits); });
+ luaCtx.registerFunction<bool (ComboAddress::*)() const>("isIPv4", [](const ComboAddress& addr) { return addr.sin4.sin_family == AF_INET; });
+ luaCtx.registerFunction<bool (ComboAddress::*)() const>("isIPv6", [](const ComboAddress& addr) { return addr.sin4.sin_family == AF_INET6; });
+ luaCtx.registerFunction<bool (ComboAddress::*)() const>("isMappedIPv4", [](const ComboAddress& addr) { return addr.isMappedIPv4(); });
+ luaCtx.registerFunction<ComboAddress (ComboAddress::*)() const>("mapToIPv4", [](const ComboAddress& addr) { return addr.mapToIPv4(); });
+ luaCtx.registerFunction<bool (nmts_t::*)(const ComboAddress&)>("match", [](nmts_t& set, const ComboAddress& addr) { return set.match(addr); });
+#endif /* DISABLE_COMBO_ADDR_BINDINGS */
+
+#ifndef DISABLE_DNSNAME_BINDINGS
+ /* DNSName */
+ luaCtx.registerFunction("isPartOf", &DNSName::isPartOf);
+ luaCtx.registerFunction<bool (DNSName::*)()>("chopOff", [](DNSName& name) { return name.chopOff(); });
+ luaCtx.registerFunction<unsigned int (DNSName::*)() const>("countLabels", [](const DNSName& name) { return name.countLabels(); });
+ luaCtx.registerFunction<size_t (DNSName::*)() const>("hash", [](const DNSName& name) { return name.hash(); });
+ luaCtx.registerFunction<size_t (DNSName::*)() const>("wirelength", [](const DNSName& name) { return name.wirelength(); });
+ luaCtx.registerFunction<string (DNSName::*)() const>("tostring", [](const DNSName& name) { return name.toString(); });
+ luaCtx.registerFunction<string (DNSName::*)() const>("toString", [](const DNSName& name) { return name.toString(); });
+ luaCtx.registerFunction<string (DNSName::*)() const>("toStringNoDot", [](const DNSName& name) { return name.toStringNoDot(); });
+ luaCtx.registerFunction<string (DNSName::*)() const>("__tostring", [](const DNSName& name) { return name.toString(); });
+ luaCtx.registerFunction<string (DNSName::*)() const>("toDNSString", [](const DNSName& name) { return name.toDNSString(); });
+ luaCtx.registerFunction<DNSName (DNSName::*)(const DNSName&) const>("makeRelative", [](const DNSName& name, const DNSName& relTo) { return name.makeRelative(relTo); });
+ luaCtx.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); });
+ luaCtx.writeFunction("newDNSNameFromRaw", [](const std::string& name) { return DNSName(name.c_str(), name.size(), 0, false); });
+ luaCtx.writeFunction("newSuffixMatchNode", []() { return SuffixMatchNode(); });
+ luaCtx.writeFunction("newDNSNameSet", []() { return DNSNameSet(); });
+
+ /* DNSNameSet */
+ luaCtx.registerFunction<string (DNSNameSet::*)() const>("toString", [](const DNSNameSet& dns) { return dns.toString(); });
+ luaCtx.registerFunction<string (DNSNameSet::*)() const>("__tostring", [](const DNSNameSet& dns) { return dns.toString(); });
+ luaCtx.registerFunction<void (DNSNameSet::*)(DNSName&)>("add", [](DNSNameSet& dns, DNSName& name) { dns.insert(name); });
+ luaCtx.registerFunction<bool (DNSNameSet::*)(DNSName&)>("check", [](DNSNameSet& dns, DNSName& name) { return dns.find(name) != dns.end(); });
+ // clang-format off
+ luaCtx.registerFunction("delete", (size_t (DNSNameSet::*)(const DNSName&)) &DNSNameSet::erase);
+ luaCtx.registerFunction("size", (size_t (DNSNameSet::*)() const) &DNSNameSet::size);
+ luaCtx.registerFunction("clear", (void (DNSNameSet::*)()) &DNSNameSet::clear);
+ luaCtx.registerFunction("empty", (bool (DNSNameSet::*)() const) &DNSNameSet::empty);
+ // clang-format on
+#endif /* DISABLE_DNSNAME_BINDINGS */
+
+#ifndef DISABLE_SUFFIX_MATCH_BINDINGS
+ /* SuffixMatchNode */
+ luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, std::string, LuaArray<DNSName>, LuaArray<std::string>>& name)>("add", [](SuffixMatchNode& smn, const boost::variant<DNSName, std::string, LuaArray<DNSName>, LuaArray<std::string>>& name) {
+ if (name.type() == typeid(DNSName)) {
+ const auto& actualName = boost::get<DNSName>(name);
+ smn.add(actualName);
+ return;
+ }
+ if (name.type() == typeid(std::string)) {
+ const auto& actualName = boost::get<std::string>(name);
+ smn.add(actualName);
+ return;
+ }
+ if (name.type() == typeid(LuaArray<DNSName>)) {
+ const auto& names = boost::get<LuaArray<DNSName>>(name);
+ for (const auto& actualName : names) {
+ smn.add(actualName.second);
+ }
+ return;
+ }
+ if (name.type() == typeid(LuaArray<std::string>)) {
+ const auto& names = boost::get<LuaArray<string>>(name);
+ for (const auto& actualName : names) {
+ smn.add(actualName.second);
+ }
+ return;
+ }
+ });
+ luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, string, LuaArray<DNSName>, LuaArray<std::string>>& name)>("remove", [](SuffixMatchNode& smn, const boost::variant<DNSName, string, LuaArray<DNSName>, LuaArray<std::string>>& name) {
+ if (name.type() == typeid(DNSName)) {
+ const auto& actualName = boost::get<DNSName>(name);
+ smn.remove(actualName);
+ return;
+ }
+ if (name.type() == typeid(string)) {
+ const auto& actualName = boost::get<string>(name);
+ DNSName dnsName(actualName);
+ smn.remove(dnsName);
+ return;
+ }
+ if (name.type() == typeid(LuaArray<DNSName>)) {
+ const auto& names = boost::get<LuaArray<DNSName>>(name);
+ for (const auto& actualName : names) {
+ smn.remove(actualName.second);
+ }
+ return;
+ }
+ if (name.type() == typeid(LuaArray<std::string>)) {
+ const auto& names = boost::get<LuaArray<std::string>>(name);
+ for (const auto& actualName : names) {
+ DNSName dnsName(actualName.second);
+ smn.remove(dnsName);
+ }
+ return;
+ }
+ });
+
+ // clang-format off
+ luaCtx.registerFunction("check", (bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
+ // clang-format on
+ luaCtx.registerFunction<boost::optional<DNSName> (SuffixMatchNode::*)(const DNSName&) const>("getBestMatch", [](const SuffixMatchNode& smn, const DNSName& needle) {
+ boost::optional<DNSName> result{boost::none};
+ auto res = smn.getBestMatch(needle);
+ if (res) {
+ result = *res;
+ }
+ return result;
+ });
+#endif /* DISABLE_SUFFIX_MATCH_BINDINGS */
+
+#ifndef DISABLE_NETMASK_BINDINGS
+ /* Netmask */
+ luaCtx.writeFunction("newNetmask", [](boost::variant<std::string, ComboAddress> addrOrStr, boost::optional<uint8_t> bits) {
+ if (addrOrStr.type() == typeid(ComboAddress)) {
+ const auto& comboAddr = boost::get<ComboAddress>(addrOrStr);
+ if (bits) {
+ return Netmask(comboAddr, *bits);
+ }
+ return Netmask(comboAddr);
+ }
+ if (addrOrStr.type() == typeid(std::string)) {
+ const auto& str = boost::get<std::string>(addrOrStr);
+ return Netmask(str);
+ }
+ throw std::runtime_error("Invalid parameter passed to 'newNetmask()'");
+ });
+ luaCtx.registerFunction("empty", &Netmask::empty);
+ luaCtx.registerFunction("getBits", &Netmask::getBits);
+ luaCtx.registerFunction<ComboAddress (Netmask::*)() const>("getNetwork", [](const Netmask& netmask) { return netmask.getNetwork(); }); // const reference makes this necessary
+ luaCtx.registerFunction<ComboAddress (Netmask::*)() const>("getMaskedNetwork", [](const Netmask& netmask) { return netmask.getMaskedNetwork(); });
+ luaCtx.registerFunction("isIpv4", &Netmask::isIPv4);
+ luaCtx.registerFunction("isIPv4", &Netmask::isIPv4);
+ luaCtx.registerFunction("isIpv6", &Netmask::isIPv6);
+ luaCtx.registerFunction("isIPv6", &Netmask::isIPv6);
+ // clang-format off
+ luaCtx.registerFunction("match", (bool (Netmask::*)(const string&) const) &Netmask::match);
+ // clang-format on
+ luaCtx.registerFunction("toString", &Netmask::toString);
+ luaCtx.registerFunction("__tostring", &Netmask::toString);
+ luaCtx.registerEqFunction(&Netmask::operator==);
+ luaCtx.registerToStringFunction(&Netmask::toString);
+
+ /* NetmaskGroup */
+ luaCtx.writeFunction("newNMG", []() { return NetmaskGroup(); });
+ luaCtx.registerFunction<void (NetmaskGroup::*)(const std::string& mask)>("addMask", [](NetmaskGroup& nmg, const std::string& mask) {
+ nmg.addMask(mask);
+ });
+ luaCtx.registerFunction<void (NetmaskGroup::*)(const NetmaskGroup& otherNMG)>("addNMG", [](NetmaskGroup& nmg, const NetmaskGroup& otherNMG) {
+ /* this is not going to be very efficient, sorry */
+ auto entries = otherNMG.toStringVector();
+ for (const auto& entry : entries) {
+ nmg.addMask(entry);
+ }
+ });
+ luaCtx.registerFunction<void (NetmaskGroup::*)(const std::map<ComboAddress, int>& map)>("addMasks", [](NetmaskGroup& nmg, const std::map<ComboAddress, int>& map) {
+ for (const auto& entry : map) {
+ nmg.addMask(Netmask(entry.first));
+ }
+ });
+
+ // clang-format off
+ luaCtx.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const) &NetmaskGroup::match);
+ // clang-format on
+ luaCtx.registerFunction("size", &NetmaskGroup::size);
+ luaCtx.registerFunction("clear", &NetmaskGroup::clear);
+ luaCtx.registerFunction<string (NetmaskGroup::*)() const>("toString", [](const NetmaskGroup& nmg) { return "NetmaskGroup " + nmg.toString(); });
+ luaCtx.registerFunction<string (NetmaskGroup::*)() const>("__tostring", [](const NetmaskGroup& nmg) { return "NetmaskGroup " + nmg.toString(); });
+#endif /* DISABLE_NETMASK_BINDINGS */
+
+#ifndef DISABLE_QPS_LIMITER_BINDINGS
+ /* QPSLimiter */
+ luaCtx.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); });
+ luaCtx.registerFunction("check", &QPSLimiter::check);
+#endif /* DISABLE_QPS_LIMITER_BINDINGS */
+
+#ifndef DISABLE_CLIENT_STATE_BINDINGS
+ /* ClientState */
+ luaCtx.registerFunction<std::string (ClientState::*)() const>("toString", [](const ClientState& frontend) {
+ setLuaNoSideEffect();
+ return frontend.local.toStringWithPort();
+ });
+ luaCtx.registerFunction<std::string (ClientState::*)() const>("__tostring", [](const ClientState& frontend) {
+ setLuaNoSideEffect();
+ return frontend.local.toStringWithPort();
+ });
+ luaCtx.registerFunction<std::string (ClientState::*)() const>("getType", [](const ClientState& frontend) {
+ setLuaNoSideEffect();
+ return frontend.getType();
+ });
+ luaCtx.registerFunction<std::string (ClientState::*)() const>("getConfiguredTLSProvider", [](const ClientState& frontend) {
+ setLuaNoSideEffect();
+ if (frontend.doqFrontend != nullptr || frontend.doh3Frontend != nullptr) {
+ return std::string("BoringSSL");
+ }
+ if (frontend.tlsFrontend != nullptr) {
+ return frontend.tlsFrontend->getRequestedProvider();
+ }
+ if (frontend.dohFrontend != nullptr) {
+ return std::string("openssl");
+ }
+ return std::string();
+ });
+ luaCtx.registerFunction<std::string (ClientState::*)() const>("getEffectiveTLSProvider", [](const ClientState& frontend) {
+ setLuaNoSideEffect();
+ if (frontend.doqFrontend != nullptr || frontend.doh3Frontend != nullptr) {
+ return std::string("BoringSSL");
+ }
+ if (frontend.tlsFrontend != nullptr) {
+ return frontend.tlsFrontend->getEffectiveProvider();
+ }
+ if (frontend.dohFrontend != nullptr) {
+ return std::string("openssl");
+ }
+ return std::string();
+ });
+ luaCtx.registerMember("muted", &ClientState::muted);
+#ifdef HAVE_EBPF
+ luaCtx.registerFunction<void (ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
+ if (bpf) {
+ frontend.attachFilter(bpf, frontend.getSocket());
+ }
+ });
+ luaCtx.registerFunction<void (ClientState::*)()>("detachFilter", [](ClientState& frontend) {
+ frontend.detachFilter(frontend.getSocket());
+ });
+#endif /* HAVE_EBPF */
+#endif /* DISABLE_CLIENT_STATE_BINDINGS */
+
+ /* BPF Filter */
+#ifdef HAVE_EBPF
+ using bpfopts_t = LuaAssociativeTable<boost::variant<bool, uint32_t, std::string>>;
+ luaCtx.writeFunction("newBPFFilter", [client](bpfopts_t opts) {
+ if (client) {
+ return std::shared_ptr<BPFFilter>(nullptr);
+ }
+ std::unordered_map<std::string, BPFFilter::MapConfiguration> mapsConfig;
+
+ const auto convertParamsToConfig = [&](const std::string& name, BPFFilter::MapType type) {
+ BPFFilter::MapConfiguration config;
+ config.d_type = type;
+ if (const string key = name + "MaxItems"; opts.count(key)) {
+ const auto& tmp = opts.at(key);
+ if (tmp.type() != typeid(uint32_t)) {
+ throw std::runtime_error("params is invalid");
+ }
+ const auto& params = boost::get<uint32_t>(tmp);
+ config.d_maxItems = params;
+ }
+
+ if (const string key = name + "PinnedPath"; opts.count(key)) {
+ auto& tmp = opts.at(key);
+ if (tmp.type() != typeid(string)) {
+ throw std::runtime_error("params is invalid");
+ }
+ auto& params = boost::get<string>(tmp);
+ config.d_pinnedPath = std::move(params);
+ }
+ mapsConfig[name] = std::move(config);
+ };
+
+ convertParamsToConfig("ipv4", BPFFilter::MapType::IPv4);
+ convertParamsToConfig("ipv6", BPFFilter::MapType::IPv6);
+ convertParamsToConfig("qnames", BPFFilter::MapType::QNames);
+ convertParamsToConfig("cidr4", BPFFilter::MapType::CIDR4);
+ convertParamsToConfig("cidr6", BPFFilter::MapType::CIDR6);
+
+ BPFFilter::MapFormat format = BPFFilter::MapFormat::Legacy;
+ bool external = false;
+ if (opts.count("external") != 0) {
+ const auto& tmp = opts.at("external");
+ if (tmp.type() != typeid(bool)) {
+ throw std::runtime_error("params is invalid");
+ }
+ external = boost::get<bool>(tmp);
+ if (external) {
+ format = BPFFilter::MapFormat::WithActions;
+ }
+ }
+
+ return std::make_shared<BPFFilter>(mapsConfig, format, external);
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const ComboAddress& addr, boost::optional<uint32_t> action)>("block", [](const std::shared_ptr<BPFFilter>& bpf, const ComboAddress& addr, boost::optional<uint32_t> action) {
+ if (bpf) {
+ if (!action) {
+ return bpf->block(addr, BPFFilter::MatchAction::Drop);
+ }
+ BPFFilter::MatchAction match{};
+
+ switch (*action) {
+ case 0:
+ match = BPFFilter::MatchAction::Pass;
+ break;
+ case 1:
+ match = BPFFilter::MatchAction::Drop;
+ break;
+ case 2:
+ match = BPFFilter::MatchAction::Truncate;
+ break;
+ default:
+ throw std::runtime_error("Unsupported action for BPFFilter::block");
+ }
+ return bpf->block(addr, match);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const string& range, uint32_t action, boost::optional<bool> force)>("addRangeRule", [](const std::shared_ptr<BPFFilter>& bpf, const string& range, uint32_t action, boost::optional<bool> force) {
+ if (!bpf) {
+ return;
+ }
+ BPFFilter::MatchAction match{};
+ switch (action) {
+ case 0:
+ match = BPFFilter::MatchAction::Pass;
+ break;
+ case 1:
+ match = BPFFilter::MatchAction::Drop;
+ break;
+ case 2:
+ match = BPFFilter::MatchAction::Truncate;
+ break;
+ default:
+ throw std::runtime_error("Unsupported action for BPFFilter::block");
+ }
+ return bpf->addRangeRule(Netmask(range), force ? *force : false, match);
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action)>("blockQName", [](const std::shared_ptr<BPFFilter>& bpf, const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action) {
+ if (bpf) {
+ if (!action) {
+ return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype ? *qtype : 255);
+ }
+ BPFFilter::MatchAction match{};
+
+ switch (*action) {
+ case 0:
+ match = BPFFilter::MatchAction::Pass;
+ break;
+ case 1:
+ match = BPFFilter::MatchAction::Drop;
+ break;
+ case 2:
+ match = BPFFilter::MatchAction::Truncate;
+ break;
+ default:
+ throw std::runtime_error("Unsupported action for BPFFilter::blockQName");
+ }
+ return bpf->block(qname, match, qtype ? *qtype : 255);
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const ComboAddress& addr)>("unblock", [](const std::shared_ptr<BPFFilter>& bpf, const ComboAddress& addr) {
+ if (bpf) {
+ return bpf->unblock(addr);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const string& range)>("rmRangeRule", [](const std::shared_ptr<BPFFilter>& bpf, const string& range) {
+ if (!bpf) {
+ return;
+ }
+ bpf->rmRangeRule(Netmask(range));
+ });
+ luaCtx.registerFunction<std::string (std::shared_ptr<BPFFilter>::*)() const>("lsRangeRule", [](const std::shared_ptr<BPFFilter>& bpf) {
+ setLuaNoSideEffect();
+ std::string res;
+ if (!bpf) {
+ return res;
+ }
+ const auto rangeStat = bpf->getRangeRule();
+ for (const auto& value : rangeStat) {
+ if (value.first.isIPv4()) {
+ res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + "\n";
+ }
+ else if (value.first.isIPv6()) {
+ res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]\n";
+ }
+ }
+ return res;
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](const std::shared_ptr<BPFFilter>& bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
+ if (bpf) {
+ return bpf->unblock(qname, qtype ? *qtype : 255);
+ }
+ });
+
+ luaCtx.registerFunction<std::string (std::shared_ptr<BPFFilter>::*)() const>("getStats", [](const std::shared_ptr<BPFFilter>& bpf) {
+ setLuaNoSideEffect();
+ std::string res;
+ if (bpf) {
+ auto stats = bpf->getAddrStats();
+ for (const auto& value : stats) {
+ if (value.first.sin4.sin_family == AF_INET) {
+ res += value.first.toString() + ": " + std::to_string(value.second) + "\n";
+ }
+ else if (value.first.sin4.sin_family == AF_INET6) {
+ res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n";
+ }
+ }
+ const auto rangeStat = bpf->getRangeRule();
+ for (const auto& value : rangeStat) {
+ if (value.first.isIPv4()) {
+ res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + ": " + std::to_string(value.second.counter) + "\n";
+ }
+ else if (value.first.isIPv6()) {
+ res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]: " + std::to_string(value.second.counter) + "\n";
+ }
+ }
+ auto qstats = bpf->getQNameStats();
+ for (const auto& value : qstats) {
+ res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n";
+ }
+ }
+ return res;
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter>& bpf) {
+ std::string res;
+ if (!g_configurationDone) {
+ throw std::runtime_error("attachToAllBinds() cannot be used at configuration time!");
+ return;
+ }
+ if (bpf) {
+ for (const auto& frontend : g_frontends) {
+ frontend->attachFilter(bpf, frontend->getSocket());
+ }
+ }
+ });
+
+ luaCtx.writeFunction("newDynBPFFilter", [client](std::shared_ptr<BPFFilter>& bpf) {
+ if (client) {
+ return std::shared_ptr<DynBPFFilter>(nullptr);
+ }
+ return std::make_shared<DynBPFFilter>(bpf);
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](const std::shared_ptr<DynBPFFilter>& dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
+ if (dbpf) {
+ timespec until{};
+ clock_gettime(CLOCK_MONOTONIC, &until);
+ until.tv_sec += seconds ? *seconds : 10;
+ dbpf->block(addr, until);
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](const std::shared_ptr<DynBPFFilter>& dbpf) {
+ if (dbpf) {
+ timespec now{};
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ dbpf->purgeExpired(now);
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DynBPFFilter>::*)(LuaTypeOrArrayOf<std::string>)>("excludeRange", [](const std::shared_ptr<DynBPFFilter>& dbpf, LuaTypeOrArrayOf<std::string> ranges) {
+ if (!dbpf) {
+ return;
+ }
+
+ if (ranges.type() == typeid(LuaArray<std::string>)) {
+ for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+ dbpf->excludeRange(Netmask(range.second));
+ }
+ }
+ else {
+ dbpf->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DynBPFFilter>::*)(LuaTypeOrArrayOf<std::string>)>("includeRange", [](const std::shared_ptr<DynBPFFilter>& dbpf, LuaTypeOrArrayOf<std::string> ranges) {
+ if (!dbpf) {
+ return;
+ }
+
+ if (ranges.type() == typeid(LuaArray<std::string>)) {
+ for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+ dbpf->includeRange(Netmask(range.second));
+ }
+ }
+ else {
+ dbpf->includeRange(Netmask(*boost::get<std::string>(&ranges)));
+ }
+ });
+#endif /* HAVE_EBPF */
+#ifdef HAVE_XSK
+ using xskopt_t = LuaAssociativeTable<boost::variant<uint32_t, std::string>>;
+ luaCtx.writeFunction("newXsk", [client](xskopt_t opts) {
+ if (g_configurationDone) {
+ throw std::runtime_error("newXsk() only can be used at configuration time!");
+ }
+ if (client) {
+ return std::shared_ptr<XskSocket>(nullptr);
+ }
+ uint32_t queue_id{};
+ uint32_t frameNums{65536};
+ std::string ifName;
+ std::string path("/sys/fs/bpf/dnsdist/xskmap");
+ if (opts.count("ifName") == 1) {
+ ifName = boost::get<std::string>(opts.at("ifName"));
+ }
+ else {
+ throw std::runtime_error("ifName field is required!");
+ }
+ if (opts.count("NIC_queue_id") == 1) {
+ queue_id = boost::get<uint32_t>(opts.at("NIC_queue_id"));
+ }
+ else {
+ throw std::runtime_error("NIC_queue_id field is required!");
+ }
+ if (opts.count("frameNums") == 1) {
+ frameNums = boost::get<uint32_t>(opts.at("frameNums"));
+ }
+ if (opts.count("xskMapPath") == 1) {
+ path = boost::get<std::string>(opts.at("xskMapPath"));
+ }
+ auto socket = std::make_shared<XskSocket>(frameNums, ifName, queue_id, path);
+ dnsdist::xsk::g_xsk.push_back(socket);
+ return socket;
+ });
+ luaCtx.registerFunction<std::string (std::shared_ptr<XskSocket>::*)() const>("getMetrics", [](const std::shared_ptr<XskSocket>& xsk) -> std::string {
+ if (!xsk) {
+ return {};
+ }
+ return xsk->getMetrics();
+ });
+#endif /* HAVE_XSK */
+ /* EDNSOptionView */
+ luaCtx.registerFunction<size_t (EDNSOptionView::*)() const>("count", [](const EDNSOptionView& option) {
+ return option.values.size();
+ });
+ luaCtx.registerFunction<std::vector<string> (EDNSOptionView::*)() const>("getValues", [](const EDNSOptionView& option) {
+ std::vector<string> values;
+ values.reserve(values.size());
+ for (const auto& value : option.values) {
+ values.emplace_back(value.content, value.size);
+ }
+ return values;
+ });
+
+ luaCtx.writeFunction("newDOHResponseMapEntry", [](const std::string& regex, uint64_t status, const std::string& content, boost::optional<LuaAssociativeTable<std::string>> customHeaders) {
+ checkParameterBound("newDOHResponseMapEntry", status, std::numeric_limits<uint16_t>::max());
+ boost::optional<LuaAssociativeTable<std::string>> headers{boost::none};
+ if (customHeaders) {
+ headers = LuaAssociativeTable<std::string>();
+ for (const auto& header : *customHeaders) {
+ (*headers)[boost::to_lower_copy(header.first)] = header.second;
+ }
+ }
+ return std::make_shared<DOHResponseMapEntry>(regex, status, PacketBuffer(content.begin(), content.end()), headers);
+ });
+
+ luaCtx.writeFunction("newSVCRecordParameters", [](uint64_t priority, const std::string& target, boost::optional<svcParamsLua_t> additionalParameters) {
+ checkParameterBound("newSVCRecordParameters", priority, std::numeric_limits<uint16_t>::max());
+ SVCRecordParameters parameters;
+ if (additionalParameters) {
+ parameters = parseSVCParameters(*additionalParameters);
+ }
+ parameters.priority = priority;
+ parameters.target = DNSName(target);
+
+ return parameters;
+ });
+
+ luaCtx.writeFunction("getListOfNetworkInterfaces", []() {
+ LuaArray<std::string> result;
+ auto itfs = getListOfNetworkInterfaces();
+ int counter = 1;
+ for (const auto& itf : itfs) {
+ result.emplace_back(counter++, itf);
+ }
+ return result;
+ });
+
+ luaCtx.writeFunction("getListOfAddressesOfNetworkInterface", [](const std::string& itf) {
+ LuaArray<std::string> result;
+ auto addrs = getListOfAddressesOfNetworkInterface(itf);
+ int counter = 1;
+ for (const auto& addr : addrs) {
+ result.emplace_back(counter++, addr.toString());
+ }
+ return result;
+ });
+
+ luaCtx.writeFunction("getListOfRangesOfNetworkInterface", [](const std::string& itf) {
+ LuaArray<std::string> result;
+ auto addrs = getListOfRangesOfNetworkInterface(itf);
+ int counter = 1;
+ for (const auto& addr : addrs) {
+ result.emplace_back(counter++, addr.toString());
+ }
+ return result;
+ });
+
+ luaCtx.writeFunction("getMACAddress", [](const std::string& addr) {
+ return getMACAddress(ComboAddress(addr));
+ });
+
+ luaCtx.writeFunction("getCurrentTime", []() -> timespec {
+ timespec now{};
+ if (gettime(&now, true) < 0) {
+ unixDie("Getting timestamp");
+ }
+ return now;
+ });
+
+ luaCtx.writeFunction("getAddressInfo", [client, configCheck](std::string hostname, std::function<void(const std::string& hostname, const LuaArray<ComboAddress>& ips)> callback) {
+ if (client || configCheck) {
+ return;
+ }
+ std::thread newThread(dnsdist::resolver::asynchronousResolver, std::move(hostname), [callback = std::move(callback)](const std::string& resolvedHostname, std::vector<ComboAddress>& ips) {
+ LuaArray<ComboAddress> result;
+ result.reserve(ips.size());
+ for (const auto& entry : ips) {
+ result.emplace_back(result.size() + 1, entry);
+ }
+ {
+ auto lua = g_lua.lock();
+ callback(resolvedHostname, result);
+ dnsdist::handleQueuedAsynchronousEvents();
+ }
+ });
+ newThread.detach();
+ });
+}
void dnsdist_ffi_dnsquestion_get_localaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default")));
uint16_t dnsdist_ffi_dnsquestion_get_local_port(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnsquestion_is_remote_v6(const dnsdist_ffi_dnsquestion_t* dnsQuestion) __attribute__ ((visibility ("default")));
void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default")));
void dnsdist_ffi_dnsquestion_get_masked_remoteaddr(dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize, uint8_t bits) __attribute__ ((visibility ("default")));
uint16_t dnsdist_ffi_dnsquestion_get_remote_port(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
void dnsdist_ffi_dnsquestion_set_http_response(dnsdist_ffi_dnsquestion_t* dq, uint16_t statusCode, const char* body, size_t bodyLen, const char* contentType) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dnsquestion_set_extended_dns_error(dnsdist_ffi_dnsquestion_t* dnsQuestion, uint16_t infoCode, const char* extraText, size_t extraTextSize) __attribute__ ((visibility ("default")));
+
size_t dnsdist_ffi_dnsquestion_get_trailing_data(dnsdist_ffi_dnsquestion_t* dq, const char** out) __attribute__ ((visibility ("default")));
bool dnsdist_ffi_dnsquestion_set_trailing_data(dnsdist_ffi_dnsquestion_t* dq, const char* data, size_t dataLen) __attribute__ ((visibility ("default")));
typedef struct dnsdist_ffi_ring_entry_list_t dnsdist_ffi_ring_entry_list_t;
bool dnsdist_ffi_ring_entry_is_response(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+double dnsdist_ffi_ring_entry_get_age(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
const char* dnsdist_ffi_ring_entry_get_name(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
uint16_t dnsdist_ffi_ring_entry_get_type(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
const char* dnsdist_ffi_ring_entry_get_requestor(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+const char* dnsdist_ffi_ring_entry_get_backend(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
uint8_t dnsdist_ffi_ring_entry_get_protocol(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
uint16_t dnsdist_ffi_ring_entry_get_size(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_latency(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_id(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint8_t dnsdist_ffi_ring_entry_get_rcode(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_ring_entry_get_aa(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_ring_entry_get_rd(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_ring_entry_get_tc(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_ancount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_nscount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_arcount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
bool dnsdist_ffi_ring_entry_has_mac_address(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
const char* dnsdist_ffi_ring_entry_get_mac_address(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t packetSize, size_t offset, char* name, size_t nameSize) __attribute__ ((visibility ("default")));
void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t*) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName) __attribute__ ((visibility ("default")));
void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value) __attribute__ ((visibility ("default")));
void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value) __attribute__ ((visibility ("default")));
double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter) __attribute__ ((visibility ("default")));
size_t dnsdist_ffi_network_message_get_payload_size(const dnsdist_ffi_network_message_t* msg) __attribute__ ((visibility ("default")));
uint16_t dnsdist_ffi_network_message_get_endpoint_id(const dnsdist_ffi_network_message_t* msg) __attribute__ ((visibility ("default")));
+/* Add a dynamic block:
+ - address should be an IPv4 or IPv6 address, as a string (192.0.2.1). A port might be included (192.0.2.1:).
+ - reason is a description of why the block was inserted
+ - action should be a DNSAction
+ - duration is the duration of the block, in seconds
+ - clientIPMask indicates whether the exact IP address should be blocked (32 for IPv4, 128 for IPv6) or if a range should be used instead, by indicating the number of bits of the address to consider
+ - clientIPPort indicates It is also possible to take the IPv4 UDP and TCP ports into account, for CGNAT deployments, by setting the number of bits of the port to consider. For example passing 2 as the last parameter, which only makes sense if the previous parameters are respectively 32 and 128, will split a given IP address into four port ranges: 0-16383, 16384-32767, 32768-49151 and 49152-65535.
+*/
+bool dnsdist_ffi_dynamic_blocks_add(const char* address, const char* message, uint8_t action, unsigned int duration, uint8_t clientIPMask, uint8_t clientIPPortMask) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dynamic_blocks_smt_add(const char* suffix, const char* message, uint8_t action, unsigned int duration) __attribute__ ((visibility ("default")));
+
+typedef struct dnsdist_ffi_dynamic_block_entry {
+ char* key; /* Client IP for NMT blocks, domain name for SMT ones */
+ char* reason;
+ uint64_t blockedQueries;
+ uint64_t remainingTime;
+ uint8_t action;
+ bool ebpf;
+ bool warning;
+} dnsdist_ffi_dynamic_block_entry_t;
+
+typedef struct dnsdist_ffi_dynamic_blocks_list_t dnsdist_ffi_dynamic_blocks_list_t;
+
+size_t dnsdist_ffi_dynamic_blocks_get_entries(dnsdist_ffi_dynamic_blocks_list_t** out) __attribute__ ((visibility ("default")));
+size_t dnsdist_ffi_dynamic_blocks_smt_get_entries(dnsdist_ffi_dynamic_blocks_list_t** out) __attribute__ ((visibility ("default")));
+const dnsdist_ffi_dynamic_block_entry_t* dnsdist_ffi_dynamic_blocks_list_get(const dnsdist_ffi_dynamic_blocks_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dynamic_blocks_list_free(dnsdist_ffi_dynamic_blocks_list_t*) __attribute__ ((visibility ("default")));
+
+uint32_t dnsdist_ffi_hash(uint32_t seed, const unsigned char* data, size_t dataSize, bool caseInsensitive) __attribute__ ((visibility ("default")));
#include "dnsdist-async.hh"
#include "dnsdist-dnsparser.hh"
+#include "dnsdist-dynblocks.hh"
#include "dnsdist-ecs.hh"
#include "dnsdist-lua-ffi.hh"
#include "dnsdist-mac-address.hh"
+#include "dnsdist-metrics.hh"
#include "dnsdist-lua-network.hh"
#include "dnsdist-lua.hh"
#include "dnsdist-ecs.hh"
dnsdist_ffi_comboaddress_to_raw(dq->dq->ids.origDest, addr, addrSize);
}
+bool dnsdist_ffi_dnsquestion_is_remote_v6(const dnsdist_ffi_dnsquestion_t* dnsQuestion)
+{
+ if (dnsQuestion == nullptr || dnsQuestion->dq == nullptr) {
+ return false;
+ }
+
+ return dnsQuestion->dq->ids.origRemote.isIPv6();
+}
+
void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize)
{
dnsdist_ffi_comboaddress_to_raw(dq->dq->ids.origRemote, addr, addrSize);
void* dnsdist_ffi_dnsquestion_get_header(const dnsdist_ffi_dnsquestion_t* dq)
{
- return dq->dq->getHeader();
+ return dq->dq->getMutableHeader();
}
uint16_t dnsdist_ffi_dnsquestion_get_len(const dnsdist_ffi_dnsquestion_t* dq)
#ifdef HAVE_DNS_OVER_HTTPS
PacketBuffer bodyVect(body, body + bodyLen);
dq->dq->ids.du->setHTTPResponse(statusCode, std::move(bodyVect), contentType);
- dq->dq->getHeader()->qr = true;
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dq->dq->getMutableData(), [](dnsheader& header) {
+ header.qr = true;
+ return true;
+ });
#endif
}
+void dnsdist_ffi_dnsquestion_set_extended_dns_error(dnsdist_ffi_dnsquestion_t* dnsQuestion, uint16_t infoCode, const char* extraText, size_t extraTextSize)
+{
+ EDNSExtendedError ede;
+ ede.infoCode = infoCode;
+ if (extraText != nullptr && extraTextSize > 0) {
+ ede.extraText = std::string(extraText, extraTextSize);
+ }
+ dnsQuestion->dq->ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+}
+
void dnsdist_ffi_dnsquestion_set_rcode(dnsdist_ffi_dnsquestion_t* dq, int rcode)
{
- dq->dq->getHeader()->rcode = rcode;
- dq->dq->getHeader()->qr = true;
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dq->dq->getMutableData(), [rcode](dnsheader& header) {
+ header.rcode = rcode;
+ header.qr = true;
+ return true;
+ });
}
void dnsdist_ffi_dnsquestion_set_len(dnsdist_ffi_dnsquestion_t* dq, uint16_t len)
void dnsdist_ffi_dnsquestion_spoof_packet(dnsdist_ffi_dnsquestion_t* dq, const char* raw, size_t len)
{
std::string result;
- SpoofAction sa(raw, len);
- sa(dq->dq, &result);
+ SpoofAction tempSpoofAction(raw, len);
+ tempSpoofAction(dq->dq, &result);
}
void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount)
}
std::string result;
- SpoofAction sa(data);
- sa(dq->dq, &result);
+ SpoofAction tempSpoofAction(data, std::nullopt);
+ tempSpoofAction(dq->dq, &result);
}
void dnsdist_ffi_dnsquestion_spoof_addrs(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount)
}
std::string result;
- SpoofAction sa(data);
- sa(dq->dq, &result);
+ SpoofAction tempSpoofAction(data);
+ tempSpoofAction(dq->dq, &result);
}
void dnsdist_ffi_dnsquestion_set_max_returned_ttl(dnsdist_ffi_dnsquestion_t* dq, uint32_t max)
}
// set qname to new one
- dr->dr->ids.qname = parsed;
+ dr->dr->ids.qname = std::move(parsed);
dr->dr->ids.skipCache = true;
}
catch (const std::exception& e) {
struct timeval now;
gettimeofday(&now, nullptr);
- sender->notifyIOError(std::move(query->query.d_idstate), now);
+ TCPResponse tresponse(std::move(query->query));
+ sender->notifyIOError(now, std::move(tresponse));
return true;
}
return false;
}
- auto oldId = reinterpret_cast<const dnsheader*>(query->query.d_buffer.data())->id;
+ dnsheader_aligned alignedHeader(query->query.d_buffer.data());
+ auto oldID = alignedHeader->id;
query->query.d_buffer.clear();
query->query.d_buffer.insert(query->query.d_buffer.begin(), raw, raw + rawSize);
- reinterpret_cast<dnsheader*>(query->query.d_buffer.data())->id = oldId;
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(query->query.d_buffer, [oldID](dnsheader& header) {
+ header.id = oldID;
+ return true;
+ });
query->query.d_idstate.skipCache = true;
return dnsdist::queueQueryResumptionEvent(std::move(query));
void setupLuaLoadBalancingContext(LuaContext& luaCtx)
{
- setupLuaBindings(luaCtx, true);
+ setupLuaBindings(luaCtx, true, false);
setupLuaBindingsDNSQuestion(luaCtx);
setupLuaBindingsKVS(luaCtx, true);
setupLuaVars(luaCtx);
std::string qname;
std::string requestor;
std::string macAddr;
- size_t size;
+ std::string ds;
+ dnsheader dh;
+ double age;
+ unsigned int latency;
+ uint16_t size;
uint16_t qtype;
dnsdist::Protocol protocol;
bool isResponse;
return list->d_entries.at(idx).isResponse;
}
+double dnsdist_ffi_ring_entry_get_age(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return 0;
+ }
+
+ return list->d_entries.at(idx).age;
+}
+
const char* dnsdist_ffi_ring_entry_get_name(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
{
if (list == nullptr || idx >= list->d_entries.size()) {
}
return list->d_entries.at(idx).qtype;
-
}
const char* dnsdist_ffi_ring_entry_get_requestor(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
return list->d_entries.at(idx).requestor.c_str();
}
+const char* dnsdist_ffi_ring_entry_get_backend(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return nullptr;
+ }
+
+ return list->d_entries.at(idx).ds.c_str();
+}
+
uint8_t dnsdist_ffi_ring_entry_get_protocol(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
{
if (list == nullptr || idx >= list->d_entries.size()) {
}
return list->d_entries.at(idx).size;
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_latency(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return 0;
+ }
+
+ return list->d_entries.at(idx).latency;
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_id(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return 0;
+ }
+
+ return ntohs(list->d_entries.at(idx).dh.id);
+}
+
+uint8_t dnsdist_ffi_ring_entry_get_rcode(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return 0;
+ }
+
+ return list->d_entries.at(idx).dh.rcode;
+}
+
+bool dnsdist_ffi_ring_entry_get_aa(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return false;
+ }
+
+ return list->d_entries.at(idx).dh.aa == 1;
+}
+
+bool dnsdist_ffi_ring_entry_get_rd(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return false;
+ }
+
+ return list->d_entries.at(idx).dh.rd == 1;
+}
+
+bool dnsdist_ffi_ring_entry_get_tc(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return false;
+ }
+
+ return list->d_entries.at(idx).dh.tc == 1;
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_ancount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return 0;
+ }
+
+ return ntohs(list->d_entries.at(idx).dh.ancount);
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_nscount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return 0;
+ }
+
+ return ntohs(list->d_entries.at(idx).dh.nscount);
+}
+uint16_t dnsdist_ffi_ring_entry_get_arcount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+ if (list == nullptr || idx >= list->d_entries.size()) {
+ return 0;
+ }
+
+ return ntohs(list->d_entries.at(idx).dh.arcount);
}
bool dnsdist_ffi_ring_entry_has_mac_address(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
}
return list->d_entries.at(idx).macAddr.data();
-
}
void dnsdist_ffi_ring_entry_list_free(dnsdist_ffi_ring_entry_list_t* list)
delete list;
}
-template<typename T> static void addRingEntryToList(std::unique_ptr<dnsdist_ffi_ring_entry_list_t>& list, const T& entry)
+template<typename T> static void addRingEntryToList(std::unique_ptr<dnsdist_ffi_ring_entry_list_t>& list, const struct timespec& now, const T& entry)
{
+ auto age = DiffTime(entry.when, now);
+
constexpr bool response = std::is_same_v<T, Rings::Response>;
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
if constexpr (!response) {
- dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toString(), entry.hasmac ? std::string(reinterpret_cast<const char*>(entry.macaddress.data()), entry.macaddress.size()) : std::string(), entry.size, entry.qtype, entry.protocol, response};
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+ dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toStringWithPort(), entry.hasmac ? std::string(reinterpret_cast<const char*>(entry.macaddress.data()), entry.macaddress.size()) : std::string(), std::string(), entry.dh, age, 0, entry.size, entry.qtype, entry.protocol, response};
+#else
+ dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toStringWithPort(), std::string(), std::string(), entry.dh, age, 0, entry.size, entry.qtype, entry.protocol, response};
+#endif
list->d_entries.push_back(std::move(tmp));
}
else {
- dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toString(), std::string(), entry.size, entry.qtype, entry.protocol, response};
+ dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toStringWithPort(), std::string(), entry.ds.toStringWithPort(), entry.dh, age, entry.usec, entry.size, entry.qtype, entry.protocol, response};
list->d_entries.push_back(std::move(tmp));
}
-#else
- dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toString(), std::string(), entry.size, entry.qtype, entry.protocol, response};
- list->d_entries.push_back(std::move(tmp));
-#endif
}
size_t dnsdist_ffi_ring_get_entries(dnsdist_ffi_ring_entry_list_t** out)
return 0;
}
auto list = std::make_unique<dnsdist_ffi_ring_entry_list_t>();
+ struct timespec now
+ {
+ };
+ gettime(&now);
for (const auto& shard : g_rings.d_shards) {
{
auto ql = shard->queryRing.lock();
for (const auto& entry : *ql) {
- addRingEntryToList(list, entry);
+ addRingEntryToList(list, now, entry);
}
}
{
auto rl = shard->respRing.lock();
for (const auto& entry : *rl) {
- addRingEntryToList(list, entry);
+ addRingEntryToList(list, now, entry);
}
}
}
}
auto list = std::make_unique<dnsdist_ffi_ring_entry_list_t>();
+ struct timespec now
+ {
+ };
+ gettime(&now);
auto compare = ComboAddress::addressOnlyEqual();
for (const auto& shard : g_rings.d_shards) {
continue;
}
- addRingEntryToList(list, entry);
+ addRingEntryToList(list, now, entry);
}
}
{
continue;
}
- addRingEntryToList(list, entry);
+ addRingEntryToList(list, now, entry);
}
}
}
return 0;
#else
auto list = std::make_unique<dnsdist_ffi_ring_entry_list_t>();
+ struct timespec now
+ {
+ };
+ gettime(&now);
for (const auto& shard : g_rings.d_shards) {
auto ql = shard->queryRing.lock();
continue;
}
- addRingEntryToList(list, entry);
+ addRingEntryToList(list, now, entry);
}
}
}
}
+bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName)
+{
+ if (name == nullptr || nameLen == 0 || type == nullptr || description == nullptr) {
+ return false;
+ }
+ auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName != nullptr ? std::optional<std::string>(customName) : std::nullopt);
+ return !result;
+}
+
void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen)
{
- auto metric = g_stats.customCounters.find(std::string_view(metricName, metricNameLen));
- if (metric != g_stats.customCounters.end()) {
- ++metric->second;
+ auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), 1U);
+ if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+ return;
+ }
+}
+
+void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value)
+{
+ auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), value);
+ if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+ return;
}
}
void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen)
{
- auto metric = g_stats.customCounters.find(std::string_view(metricName, metricNameLen));
- if (metric != g_stats.customCounters.end()) {
- --metric->second;
+ auto result = dnsdist::metrics::decrementCustomCounter(std::string_view(metricName, metricNameLen), 1U);
+ if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+ return;
}
}
void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value)
{
- auto metric = g_stats.customGauges.find(std::string_view(metricName, metricNameLen));
- if (metric != g_stats.customGauges.end()) {
- metric->second = value;
+ auto result = dnsdist::metrics::setCustomGauge(std::string_view(metricName, metricNameLen), value);
+ if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+ return;
}
}
double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter)
{
- auto name = std::string_view(metricName, metricNameLen);
- if (isCounter) {
- auto counter = g_stats.customCounters.find(name);
- if (counter != g_stats.customCounters.end()) {
- return (double)counter->second.load();
- }
- }
- else {
- auto gauge = g_stats.customGauges.find(name);
- if (gauge != g_stats.customGauges.end()) {
- return gauge->second.load();
- }
+ auto result = dnsdist::metrics::getCustomMetric(std::string_view(metricName, metricNameLen));
+ if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+ return 0.;
}
- return 0.;
+ return std::get<double>(result);
}
const char* dnsdist_ffi_network_message_get_payload(const dnsdist_ffi_network_message_t* msg)
}
return 0;
}
+
+#ifndef DISABLE_DYNBLOCKS
+bool dnsdist_ffi_dynamic_blocks_add(const char* address, const char* message, uint8_t action, unsigned int duration, uint8_t clientIPMask, uint8_t clientIPPortMask)
+{
+ try {
+ ComboAddress clientIPCA;
+ try {
+ clientIPCA = ComboAddress(address);
+ }
+ catch (const std::exception& exp) {
+ errlog("dnsdist_ffi_dynamic_blocks_add: Unable to parse '%s': %s", address, exp.what());
+ return false;
+ }
+ catch (const PDNSException& exp) {
+ errlog("dnsdist_ffi_dynamic_blocks_add: Unable to parse '%s': %s", address, exp.reason);
+ return false;
+ }
+
+ AddressAndPortRange target(clientIPCA, clientIPMask, clientIPPortMask);
+
+ struct timespec now
+ {
+ };
+ gettime(&now);
+ auto slow = g_dynblockNMG.getCopy();
+ if (dnsdist::DynamicBlocks::addOrRefreshBlock(slow, now, target, message, duration, static_cast<DNSAction::Action>(action), false, false)) {
+ g_dynblockNMG.setState(slow);
+ return true;
+ }
+ }
+ catch (const std::exception& exp) {
+ errlog("Exception in dnsdist_ffi_dynamic_blocks_add: %s", exp.what());
+ }
+ catch (const PDNSException& exp) {
+ errlog("Exception in dnsdist_ffi_dynamic_blocks_add: %s", exp.reason);
+ }
+ catch (...) {
+ errlog("Exception in dnsdist_ffi_dynamic_blocks_add");
+ }
+ return false;
+}
+
+bool dnsdist_ffi_dynamic_blocks_smt_add(const char* suffix, const char* message, uint8_t action, unsigned int duration)
+{
+ try {
+ DNSName domain;
+ try {
+ domain = DNSName(suffix);
+ domain.makeUsLowerCase();
+ }
+ catch (const std::exception& exp) {
+ errlog("dnsdist_ffi_dynamic_blocks_smt_add: Unable to parse '%s': %s", suffix, exp.what());
+ return false;
+ }
+ catch (const PDNSException& exp) {
+ errlog("dnsdist_ffi_dynamic_blocks_smt_add: Unable to parse '%s': %s", suffix, exp.reason);
+ return false;
+ }
+
+ struct timespec now
+ {
+ };
+ gettime(&now);
+ auto slow = g_dynblockSMT.getCopy();
+ if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(slow, now, domain, message, duration, static_cast<DNSAction::Action>(action), false)) {
+ g_dynblockSMT.setState(slow);
+ return true;
+ }
+ }
+ catch (const std::exception& exp) {
+ errlog("Exception in dnsdist_ffi_dynamic_blocks_smt_add: %s", exp.what());
+ }
+ catch (const PDNSException& exp) {
+ errlog("Exception in dnsdist_ffi_dynamic_blocks_smt_add: %s", exp.reason);
+ }
+ catch (...) {
+ errlog("Exception in dnsdist_ffi_dynamic_blocks_smt_add");
+ }
+ return false;
+}
+
+struct dnsdist_ffi_dynamic_blocks_list_t
+{
+ std::vector<dnsdist_ffi_dynamic_block_entry_t> d_entries;
+};
+
+size_t dnsdist_ffi_dynamic_blocks_get_entries(dnsdist_ffi_dynamic_blocks_list_t** out)
+{
+ if (out == nullptr) {
+ return 0;
+ }
+
+ auto list = std::make_unique<dnsdist_ffi_dynamic_blocks_list_t>();
+
+ struct timespec now
+ {
+ };
+ gettime(&now);
+
+ auto fullCopy = g_dynblockNMG.getCopy();
+ for (const auto& entry : fullCopy) {
+ const auto& client = entry.first;
+ const auto& details = entry.second;
+ if (!(now < details.until)) {
+ continue;
+ }
+
+ uint64_t counter = details.blocks;
+ if (g_defaultBPFFilter && details.bpf) {
+ counter += g_defaultBPFFilter->getHits(client.getNetwork());
+ }
+ list->d_entries.push_back({strdup(client.toString().c_str()), strdup(details.reason.c_str()), counter, static_cast<uint64_t>(details.until.tv_sec - now.tv_sec), static_cast<uint8_t>(details.action != DNSAction::Action::None ? details.action : g_dynBlockAction), g_defaultBPFFilter && details.bpf, details.warning});
+ }
+
+ auto count = list->d_entries.size();
+ *out = list.release();
+ return count;
+}
+
+size_t dnsdist_ffi_dynamic_blocks_smt_get_entries(dnsdist_ffi_dynamic_blocks_list_t** out)
+{
+ if (out == nullptr) {
+ return 0;
+ }
+
+ auto list = std::make_unique<dnsdist_ffi_dynamic_blocks_list_t>();
+
+ struct timespec now
+ {
+ };
+ gettime(&now);
+
+ auto fullCopy = g_dynblockSMT.getCopy();
+ fullCopy.visit([&now, &list](const SuffixMatchTree<DynBlock>& node) {
+ if (!(now < node.d_value.until)) {
+ return;
+ }
+ auto entry = node.d_value;
+ string key("empty");
+ if (!entry.domain.empty()) {
+ key = entry.domain.toString();
+ }
+ if (entry.action == DNSAction::Action::None) {
+ entry.action = g_dynBlockAction;
+ }
+ list->d_entries.push_back({strdup(key.c_str()), strdup(entry.reason.c_str()), entry.blocks, static_cast<uint64_t>(entry.until.tv_sec - now.tv_sec), static_cast<uint8_t>(entry.action), entry.bpf, entry.warning});
+ });
+
+ auto count = list->d_entries.size();
+ *out = list.release();
+ return count;
+}
+
+const dnsdist_ffi_dynamic_block_entry_t* dnsdist_ffi_dynamic_blocks_list_get(const dnsdist_ffi_dynamic_blocks_list_t* list, size_t idx)
+{
+ if (list == nullptr) {
+ return nullptr;
+ }
+
+ if (idx >= list->d_entries.size()) {
+ return nullptr;
+ }
+
+ return &list->d_entries.at(idx);
+}
+
+void dnsdist_ffi_dynamic_blocks_list_free(dnsdist_ffi_dynamic_blocks_list_t* list)
+{
+ if (list == nullptr) {
+ return;
+ }
+
+ for (auto& entry : list->d_entries) {
+ // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc): this is a C API, RAII is not an option
+ free(entry.key);
+ // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc): this is a C API, RAII is not an option
+ free(entry.reason);
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): this is a C API, RAII is not an option
+ delete list;
+}
+
+#endif /* DISABLE_DYNBLOCKS */
+
+uint32_t dnsdist_ffi_hash(uint32_t seed, const unsigned char* data, size_t dataSize, bool caseInsensitive)
+{
+ if (data == nullptr || dataSize == 0) {
+ return seed;
+ }
+
+ if (caseInsensitive) {
+ return burtleCI(data, dataSize, seed);
+ }
+
+ return burtle(data, dataSize, seed);
+}
DNSQuestion* dq{nullptr};
ComboAddress maskedRemote;
std::string trailingData;
- boost::optional<std::string> result{boost::none};
- boost::optional<std::string> httpPath{boost::none};
- boost::optional<std::string> httpQueryString{boost::none};
- boost::optional<std::string> httpHost{boost::none};
- boost::optional<std::string> httpScheme{boost::none};
+ std::optional<std::string> result{std::nullopt};
+ std::optional<std::string> httpPath{std::nullopt};
+ std::optional<std::string> httpQueryString{std::nullopt};
+ std::optional<std::string> httpHost{std::nullopt};
+ std::optional<std::string> httpScheme{std::nullopt};
std::unique_ptr<std::vector<dnsdist_ffi_ednsoption_t>> ednsOptionsVect;
std::unique_ptr<std::vector<dnsdist_ffi_http_header_t>> httpHeadersVect;
std::unique_ptr<std::vector<dnsdist_ffi_tag_t>> tagsVect;
}
DNSResponse* dr{nullptr};
- boost::optional<std::string> result{boost::none};
+ std::optional<std::string> result{std::nullopt};
};
// dnsdist_ffi_server_t is a lightuserdata
--- /dev/null
+
+#include "dnsdist-lua-hooks.hh"
+#include "dnsdist-lua.hh"
+#include "lock.hh"
+
+namespace dnsdist::lua::hooks
+{
+static LockGuarded<std::vector<MaintenanceCallback>> s_maintenanceHooks;
+
+void runMaintenanceHooks(const LuaContext& context)
+{
+ (void)context;
+ for (const auto& callback : *(s_maintenanceHooks.lock())) {
+ callback();
+ }
+}
+
+void addMaintenanceCallback(const LuaContext& context, MaintenanceCallback callback)
+{
+ (void)context;
+ s_maintenanceHooks.lock()->push_back(std::move(callback));
+}
+
+void clearMaintenanceHooks()
+{
+ s_maintenanceHooks.lock()->clear();
+}
+
+void setupLuaHooks(LuaContext& luaCtx)
+{
+ luaCtx.writeFunction("addMaintenanceCallback", [&luaCtx](const MaintenanceCallback& callback) {
+ setLuaSideEffect();
+ addMaintenanceCallback(luaCtx, callback);
+ });
+}
+
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <functional>
+
+class LuaContext;
+
+namespace dnsdist::lua::hooks
+{
+using MaintenanceCallback = std::function<void()>;
+void runMaintenanceHooks(const LuaContext& context);
+void addMaintenanceCallback(const LuaContext& context, MaintenanceCallback callback);
+void clearMaintenanceHooks();
+void setupLuaHooks(LuaContext& luaCtx);
+}
void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize)
{
- node->reason = std::string(reason, reasonSize);
+ node->d_blockParameters.d_reason = std::string(reason, reasonSize);
+}
+
+void dnsdist_ffi_state_node_set_action(dnsdist_ffi_stat_node_t* node, int blockAction)
+{
+ node->d_blockParameters.d_action = static_cast<DNSAction::Action>(blockAction);
}
#endif /* DISABLE_DYNBLOCKS */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+typedef struct dnsdist_ffi_stat_node_t dnsdist_ffi_stat_node_t;
+
+uint64_t dnsdist_ffi_stat_node_get_queries_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_noerrors_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_nxdomains_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_servfails_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_drops_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_bytes(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_hits(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+unsigned int dnsdist_ffi_stat_node_get_labels_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+void dnsdist_ffi_stat_node_get_full_name_raw(const dnsdist_ffi_stat_node_t* node, const char** name, size_t* nameSize) __attribute__((visibility("default")));
+
+unsigned int dnsdist_ffi_stat_node_get_children_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+
+uint64_t dnsdist_ffi_stat_node_get_children_queries_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_children_noerrors_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_children_nxdomains_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_children_servfails_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_children_drops_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_children_bytes_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_children_hits(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+
+void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize) __attribute__((visibility("default")));
+void dnsdist_ffi_state_node_set_action(dnsdist_ffi_stat_node_t* node, int blockAction) __attribute__((visibility("default")));
+
+typedef enum
+{
+ dnsdist_ffi_dynamic_block_type_nmt = 0,
+ dnsdist_ffi_dynamic_block_type_smt = 1,
+} dnsdist_ffi_dynamic_block_type;
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-extern "C" {
- typedef struct dnsdist_ffi_stat_node_t dnsdist_ffi_stat_node_t;
-
- uint64_t dnsdist_ffi_stat_node_get_queries_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_noerrors_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_nxdomains_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_servfails_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_drops_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_bytes(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_hits(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- unsigned int dnsdist_ffi_stat_node_get_labels_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- void dnsdist_ffi_stat_node_get_full_name_raw(const dnsdist_ffi_stat_node_t* node, const char** name, size_t* nameSize) __attribute__ ((visibility ("default")));
-
- unsigned int dnsdist_ffi_stat_node_get_children_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-
- uint64_t dnsdist_ffi_stat_node_get_children_queries_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_children_noerrors_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_children_nxdomains_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_children_servfails_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_children_drops_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_children_bytes_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
- uint64_t dnsdist_ffi_stat_node_get_children_hits(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-
- void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize) __attribute__ ((visibility ("default")));
-}
+++ /dev/null
-../dnsdist-lua-inspection.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <fcntl.h>
+
+#include "dnsdist.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-nghttp2.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-tcp.hh"
+
+#include "statnode.hh"
+
+#ifndef DISABLE_TOP_N_BINDINGS
+static LuaArray<std::vector<boost::variant<string, double>>> getGenResponses(uint64_t top, boost::optional<int> labels, const std::function<bool(const Rings::Response&)>& pred)
+{
+ setLuaNoSideEffect();
+ map<DNSName, unsigned int> counts;
+ unsigned int total = 0;
+ {
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->respRing.lock();
+ if (!labels) {
+ for (const auto& entry : *respRing) {
+ if (!pred(entry)) {
+ continue;
+ }
+ counts[entry.name]++;
+ total++;
+ }
+ }
+ else {
+ unsigned int lab = *labels;
+ for (const auto& entry : *respRing) {
+ if (!pred(entry)) {
+ continue;
+ }
+
+ DNSName temp(entry.name);
+ temp.trimToLabels(lab);
+ counts[temp]++;
+ total++;
+ }
+ }
+ }
+ }
+ // cout<<"Looked at "<<total<<" responses, "<<counts.size()<<" different ones"<<endl;
+ vector<pair<unsigned int, DNSName>> rcounts;
+ rcounts.reserve(counts.size());
+ for (const auto& val : counts) {
+ rcounts.emplace_back(val.second, val.first.makeLowerCase());
+ }
+
+ sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
+ return rhs.first < lhs.first;
+ });
+
+ LuaArray<vector<boost::variant<string, double>>> ret;
+ ret.reserve(std::min(rcounts.size(), static_cast<size_t>(top + 1U)));
+ int count = 1;
+ unsigned int rest = 0;
+ for (const auto& rcEntry : rcounts) {
+ if (count == static_cast<int>(top + 1)) {
+ rest += rcEntry.first;
+ }
+ else {
+ ret.emplace_back(count++, std::vector<boost::variant<string, double>>{rcEntry.second.toString(), rcEntry.first, 100.0 * rcEntry.first / total});
+ }
+ }
+
+ if (total > 0) {
+ ret.push_back({count, {"Rest", rest, 100.0 * rest / total}});
+ }
+ else {
+ ret.push_back({count, {"Rest", rest, 100.0}});
+ }
+
+ return ret;
+}
+#endif /* DISABLE_TOP_N_BINDINGS */
+
+#ifndef DISABLE_DYNBLOCKS
+#ifndef DISABLE_DEPRECATED_DYNBLOCK
+
+using counts_t = std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>;
+
+static counts_t filterScore(const counts_t& counts,
+ double delta, unsigned int rate)
+{
+ counts_t ret;
+
+ double lim = delta * rate;
+ for (const auto& entry : counts) {
+ if (entry.second > lim) {
+ ret[entry.first] = entry.second;
+ }
+ }
+
+ return ret;
+}
+
+using statvisitor_t = std::function<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
+
+static void statNodeRespRing(statvisitor_t visitor, uint64_t seconds)
+{
+ timespec now{};
+ gettime(&now);
+ timespec cutoff{now};
+ cutoff.tv_sec -= static_cast<time_t>(seconds);
+
+ StatNode root;
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->respRing.lock();
+
+ for (const auto& entry : *respRing) {
+ if (now < entry.when) {
+ continue;
+ }
+
+ if (seconds != 0 && entry.when < cutoff) {
+ continue;
+ }
+
+ const bool hit = entry.isACacheHit();
+ root.submit(entry.name, ((entry.dh.rcode == 0 && entry.usec == std::numeric_limits<unsigned int>::max()) ? -1 : entry.dh.rcode), entry.size, hit, boost::none);
+ }
+ }
+
+ StatNode::Stat node;
+ root.visit([visitor = std::move(visitor)](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { visitor(*node_, self, children); }, node);
+}
+
+static LuaArray<LuaAssociativeTable<std::string>> getRespRing(boost::optional<int> rcode)
+{
+ using entry_t = LuaAssociativeTable<std::string>;
+ LuaArray<entry_t> ret;
+
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->respRing.lock();
+
+ int count = 1;
+ for (const auto& entry : *respRing) {
+ if (rcode && (rcode.get() != entry.dh.rcode)) {
+ continue;
+ }
+ entry_t newEntry;
+ newEntry["qname"] = entry.name.toString();
+ newEntry["rcode"] = std::to_string(entry.dh.rcode);
+ ret.emplace_back(count, std::move(newEntry));
+ count++;
+ }
+ }
+
+ return ret;
+}
+
+static counts_t exceedRespGen(unsigned int rate, int seconds, const std::function<void(counts_t&, const Rings::Response&)>& visitor)
+{
+ counts_t counts;
+ timespec now{};
+ gettime(&now);
+ timespec mintime{now};
+ timespec cutoff{now};
+ cutoff.tv_sec -= seconds;
+
+ counts.reserve(g_rings.getNumberOfResponseEntries());
+
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->respRing.lock();
+ for (const auto& entry : *respRing) {
+
+ if (seconds != 0 && entry.when < cutoff) {
+ continue;
+ }
+ if (now < entry.when) {
+ continue;
+ }
+
+ visitor(counts, entry);
+ if (entry.when < mintime) {
+ mintime = entry.when;
+ }
+ }
+ }
+
+ double delta = seconds != 0 ? seconds : DiffTime(now, mintime);
+ return filterScore(counts, delta, rate);
+}
+
+static counts_t exceedQueryGen(unsigned int rate, int seconds, const std::function<void(counts_t&, const Rings::Query&)>& visitor)
+{
+ counts_t counts;
+ timespec now{};
+ gettime(&now);
+ timespec mintime{now};
+ timespec cutoff{now};
+ cutoff.tv_sec -= seconds;
+
+ counts.reserve(g_rings.getNumberOfQueryEntries());
+
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->queryRing.lock();
+ for (const auto& entry : *respRing) {
+ if (seconds != 0 && entry.when < cutoff) {
+ continue;
+ }
+ if (now < entry.when) {
+ continue;
+ }
+ visitor(counts, entry);
+ if (entry.when < mintime) {
+ mintime = entry.when;
+ }
+ }
+ }
+
+ double delta = seconds != 0 ? seconds : DiffTime(now, mintime);
+ return filterScore(counts, delta, rate);
+}
+
+static counts_t exceedRCode(unsigned int rate, int seconds, int rcode)
+{
+ return exceedRespGen(rate, seconds, [rcode](counts_t& counts, const Rings::Response& resp) {
+ if (resp.dh.rcode == rcode) {
+ counts[resp.requestor]++;
+ }
+ });
+}
+
+static counts_t exceedRespByterate(unsigned int rate, int seconds)
+{
+ return exceedRespGen(rate, seconds, [](counts_t& counts, const Rings::Response& resp) {
+ counts[resp.requestor] += resp.size;
+ });
+}
+
+#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+#endif /* DISABLE_DYNBLOCKS */
+
+// NOLINTNEXTLINE(bugprone-exception-escape)
+struct GrepQParams
+{
+ boost::optional<Netmask> netmask;
+ boost::optional<DNSName> name;
+ pdns::UniqueFilePtr outputFile{nullptr};
+ int msec = -1;
+};
+
+static std::optional<GrepQParams> parseGrepQParams(const LuaTypeOrArrayOf<std::string>& inp, boost::optional<LuaAssociativeTable<std::string>>& options)
+{
+ GrepQParams result{};
+
+ if (options) {
+ std::string outputFileName;
+ if (getOptionalValue<std::string>(options, "outputFile", outputFileName) > 0) {
+ int fileDesc = open(outputFileName.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600);
+ if (fileDesc < 0) {
+ g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
+ return std::nullopt;
+ }
+ result.outputFile = pdns::UniqueFilePtr(fdopen(fileDesc, "w"));
+ if (result.outputFile == nullptr) {
+ g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
+ close(fileDesc);
+ return std::nullopt;
+ }
+ }
+ checkAllParametersConsumed("grepq", options);
+ }
+
+ vector<string> filters;
+ const auto* str = boost::get<string>(&inp);
+ if (str != nullptr) {
+ filters.push_back(*str);
+ }
+ else {
+ auto values = boost::get<LuaArray<std::string>>(inp);
+ for (const auto& filter : values) {
+ filters.push_back(filter.second);
+ }
+ }
+
+ for (const auto& filter : filters) {
+ try {
+ result.netmask = Netmask(filter);
+ }
+ catch (...) {
+ if (boost::ends_with(filter, "ms") && sscanf(filter.c_str(), "%ums", &result.msec) != 0) {
+ ;
+ }
+ else {
+ try {
+ result.name = DNSName(filter);
+ }
+ catch (...) {
+ g_outputBuffer = "Could not parse '" + filter + "' as domain name or netmask";
+ return std::nullopt;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+template <class C>
+static bool ringEntryMatches(const GrepQParams& params, const C& entry)
+{
+ bool nmmatch = true;
+ bool dnmatch = true;
+ bool msecmatch = true;
+ if (params.netmask) {
+ nmmatch = params.netmask->match(entry.requestor);
+ }
+ if (params.name) {
+ if (entry.name.empty()) {
+ dnmatch = false;
+ }
+ else {
+ dnmatch = entry.name.isPartOf(*params.name);
+ }
+ }
+
+ constexpr bool response = std::is_same_v<C, Rings::Response>;
+ if constexpr (response) {
+ if (params.msec != -1) {
+ msecmatch = (entry.usec / 1000 > static_cast<unsigned int>(params.msec));
+ }
+ }
+
+ return nmmatch && dnmatch && msecmatch;
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaInspection(LuaContext& luaCtx)
+{
+#ifndef DISABLE_TOP_N_BINDINGS
+ luaCtx.writeFunction("topClients", [](boost::optional<uint64_t> top_) {
+ setLuaNoSideEffect();
+ uint64_t top = top_ ? *top_ : 10U;
+ map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts;
+ unsigned int total = 0;
+ {
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->queryRing.lock();
+ for (const auto& entry : *respRing) {
+ counts[entry.requestor]++;
+ total++;
+ }
+ }
+ }
+ vector<pair<unsigned int, ComboAddress>> rcounts;
+ rcounts.reserve(counts.size());
+ for (const auto& entry : counts) {
+ rcounts.emplace_back(entry.second, entry.first);
+ }
+
+ sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
+ return rhs.first < lhs.first;
+ });
+ unsigned int count = 1;
+ unsigned int rest = 0;
+ boost::format fmt("%4d %-40s %4d %4.1f%%\n");
+ for (const auto& entry : rcounts) {
+ if (count == top + 1) {
+ rest += entry.first;
+ }
+ else {
+ g_outputBuffer += (fmt % (count++) % entry.second.toString() % entry.first % (100.0 * entry.first / total)).str();
+ }
+ }
+ g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0 * rest / total : 100.0)).str();
+ });
+
+ luaCtx.writeFunction("getTopQueries", [](uint64_t top, boost::optional<int> labels) {
+ setLuaNoSideEffect();
+ map<DNSName, unsigned int> counts;
+ unsigned int total = 0;
+ if (!labels) {
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->queryRing.lock();
+ for (const auto& entry : *respRing) {
+ counts[entry.name]++;
+ total++;
+ }
+ }
+ }
+ else {
+ unsigned int lab = *labels;
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->queryRing.lock();
+ for (const auto& entry : *respRing) {
+ auto name = entry.name;
+ name.trimToLabels(lab);
+ counts[name]++;
+ total++;
+ }
+ }
+ }
+
+ vector<pair<unsigned int, DNSName>> rcounts;
+ rcounts.reserve(counts.size());
+ for (const auto& entry : counts) {
+ rcounts.emplace_back(entry.second, entry.first.makeLowerCase());
+ }
+
+ sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
+ return rhs.first < lhs.first;
+ });
+
+ std::unordered_map<unsigned int, vector<boost::variant<string, double>>> ret;
+ unsigned int count = 1;
+ unsigned int rest = 0;
+ for (const auto& entry : rcounts) {
+ if (count == top + 1) {
+ rest += entry.first;
+ }
+ else {
+ ret.insert({count++, {entry.second.toString(), entry.first, 100.0 * entry.first / total}});
+ }
+ }
+
+ if (total > 0) {
+ ret.insert({count, {"Rest", rest, 100.0 * rest / total}});
+ }
+ else {
+ ret.insert({count, {"Rest", rest, 100.0}});
+ }
+
+ return ret;
+ });
+
+ luaCtx.executeCode(R"(function topQueries(top, labels) top = top or 10; for k,v in ipairs(getTopQueries(top,labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2], v[3])) end end)");
+
+ luaCtx.writeFunction("getResponseRing", []() {
+ setLuaNoSideEffect();
+ size_t totalEntries = 0;
+ std::vector<boost::circular_buffer<Rings::Response>> rings;
+ rings.reserve(g_rings.getNumberOfShards());
+ for (const auto& shard : g_rings.d_shards) {
+ {
+ auto respRing = shard->respRing.lock();
+ rings.push_back(*respRing);
+ }
+ totalEntries += rings.back().size();
+ }
+ vector<std::unordered_map<string, boost::variant<string, unsigned int>>> ret;
+ ret.reserve(totalEntries);
+ for (const auto& ring : rings) {
+ for (const auto& entry : ring) {
+ decltype(ret)::value_type item;
+ item["name"] = entry.name.toString();
+ item["qtype"] = entry.qtype;
+ item["rcode"] = entry.dh.rcode;
+ item["usec"] = entry.usec;
+ ret.push_back(std::move(item));
+ }
+ }
+ return ret;
+ });
+
+ luaCtx.writeFunction("getTopResponses", [](uint64_t top, uint64_t kind, boost::optional<int> labels) {
+ return getGenResponses(top, labels, [kind](const Rings::Response& resp) { return resp.dh.rcode == kind; });
+ });
+
+ luaCtx.executeCode(R"(function topResponses(top, kind, labels) top = top or 10; kind = kind or 0; for k,v in ipairs(getTopResponses(top, kind, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
+
+ luaCtx.writeFunction("getSlowResponses", [](uint64_t top, uint64_t msec, boost::optional<int> labels) {
+ return getGenResponses(top, labels, [msec](const Rings::Response& resp) { return resp.usec > msec * 1000; });
+ });
+
+ luaCtx.executeCode(R"(function topSlow(top, msec, labels) top = top or 10; msec = msec or 500; for k,v in ipairs(getSlowResponses(top, msec, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
+
+ luaCtx.writeFunction("getTopBandwidth", [](uint64_t top) {
+ setLuaNoSideEffect();
+ return g_rings.getTopBandwidth(top);
+ });
+
+ luaCtx.executeCode(R"(function topBandwidth(top) top = top or 10; for k,v in ipairs(getTopBandwidth(top)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
+#endif /* DISABLE_TOP_N_BINDINGS */
+
+ luaCtx.writeFunction("delta", []() {
+ setLuaNoSideEffect();
+ // we hold the lua lock already!
+ for (const auto& entry : g_confDelta) {
+ tm entryTime{};
+ localtime_r(&entry.first.tv_sec, &entryTime);
+ std::array<char, 80> date{};
+ strftime(date.data(), date.size() - 1, "-- %a %b %d %Y %H:%M:%S %Z\n", &entryTime);
+ g_outputBuffer += date.data();
+ g_outputBuffer += entry.second + "\n";
+ }
+ });
+
+ luaCtx.writeFunction("grepq", [](const LuaTypeOrArrayOf<std::string>& inp, boost::optional<unsigned int> limit, boost::optional<LuaAssociativeTable<std::string>> options) {
+ setLuaNoSideEffect();
+
+ auto paramsOrError = parseGrepQParams(inp, options);
+ if (!paramsOrError) {
+ return;
+ }
+ auto params = std::move(*paramsOrError);
+
+ std::vector<Rings::Query> queries;
+ std::vector<Rings::Response> responses;
+ queries.reserve(g_rings.getNumberOfQueryEntries());
+ responses.reserve(g_rings.getNumberOfResponseEntries());
+ for (const auto& shard : g_rings.d_shards) {
+ {
+ auto respRing = shard->queryRing.lock();
+ for (const auto& entry : *respRing) {
+ queries.push_back(entry);
+ }
+ }
+ {
+ auto respRing = shard->respRing.lock();
+ for (const auto& entry : *respRing) {
+ responses.push_back(entry);
+ }
+ }
+ }
+
+ sort(queries.begin(), queries.end(), [](const decltype(queries)::value_type& lhs, const decltype(queries)::value_type& rhs) {
+ return rhs.when < lhs.when;
+ });
+
+ sort(responses.begin(), responses.end(), [](const decltype(responses)::value_type& lhs, const decltype(responses)::value_type& rhs) {
+ return rhs.when < lhs.when;
+ });
+
+ unsigned int num = 0;
+ timespec now{};
+ gettime(&now);
+
+ std::multimap<struct timespec, string> out;
+
+ boost::format fmt("%-7.1f %-47s %-12s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %-s\n");
+ const auto headLine = (fmt % "Time" % "Client" % "Protocol" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str();
+ if (!params.outputFile) {
+ g_outputBuffer += headLine;
+ }
+ else {
+ fprintf(params.outputFile.get(), "%s", headLine.c_str());
+ }
+
+ if (params.msec == -1) {
+ for (const auto& entry : queries) {
+ if (!ringEntryMatches(params, entry)) {
+ continue;
+ }
+ QType qtype(entry.qtype);
+ std::string extra;
+ if (entry.dh.opcode != 0) {
+ extra = " (" + Opcode::to_s(entry.dh.opcode) + ")";
+ }
+ out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % dnsdist::Protocol(entry.protocol).toString() % "" % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % "" % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % ("Question" + extra)).str());
+
+ if (limit && *limit == ++num) {
+ break;
+ }
+ }
+ }
+ num = 0;
+
+ string extra;
+ for (const auto& entry : responses) {
+ if (!ringEntryMatches(params, entry)) {
+ continue;
+ }
+ QType qtype(entry.qtype);
+ if (entry.dh.rcode == 0) {
+ extra = ". " + std::to_string(htons(entry.dh.ancount)) + " answers";
+ }
+ else {
+ extra.clear();
+ }
+
+ std::string server = entry.ds.toStringWithPort();
+ std::string protocol = dnsdist::Protocol(entry.protocol).toString();
+ if (server == "0.0.0.0:0") {
+ server = "Cache";
+ protocol = "-";
+ }
+ if (entry.usec != std::numeric_limits<decltype(entry.usec)>::max()) {
+ out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % protocol % server % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % (entry.usec / 1000.0) % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % (RCode::to_s(entry.dh.rcode) + extra)).str());
+ }
+ else {
+ out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % protocol % server % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % "T.O" % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % (RCode::to_s(entry.dh.rcode) + extra)).str());
+ }
+
+ if (limit && *limit == ++num) {
+ break;
+ }
+ }
+
+ for (const auto& entry : out) {
+ if (!params.outputFile) {
+ g_outputBuffer += entry.second;
+ }
+ else {
+ fprintf(params.outputFile.get(), "%s", entry.second.c_str());
+ }
+ }
+ });
+
+ luaCtx.writeFunction("showResponseLatency", []() {
+ setLuaNoSideEffect();
+ map<double, unsigned int> histo;
+ double bin = 100;
+ for (int idx = 0; idx < 15; ++idx) {
+ histo[bin];
+ bin *= 2;
+ }
+
+ double totlat = 0;
+ unsigned int size = 0;
+ {
+ for (const auto& shard : g_rings.d_shards) {
+ auto respRing = shard->respRing.lock();
+ for (const auto& entry : *respRing) {
+ /* skip actively discovered timeouts */
+ if (entry.usec == std::numeric_limits<unsigned int>::max()) {
+ continue;
+ }
+
+ ++size;
+ auto iter = histo.lower_bound(entry.usec);
+ if (iter != histo.end()) {
+ iter->second++;
+ }
+ else {
+ histo.rbegin()++;
+ }
+ totlat += entry.usec;
+ }
+ }
+ }
+
+ if (size == 0) {
+ g_outputBuffer = "No traffic yet.\n";
+ return;
+ }
+
+ g_outputBuffer = (boost::format("Average response latency: %.02f ms\n") % (0.001 * totlat / size)).str();
+ double highest = 0;
+
+ for (const auto& entry : histo) {
+ highest = std::max(highest, entry.second * 1.0);
+ }
+ boost::format fmt("%7.2f\t%s\n");
+ g_outputBuffer += (fmt % "ms" % "").str();
+
+ for (const auto& entry : histo) {
+ int stars = static_cast<int>(70.0 * entry.second / highest);
+ char value = '*';
+ if (stars == 0 && entry.second != 0) {
+ stars = 1; // you get 1 . to show something is there..
+ if (70.0 * entry.second / highest > 0.5) {
+ value = ':';
+ }
+ else {
+ value = '.';
+ }
+ }
+ g_outputBuffer += (fmt % (entry.first / 1000.0) % string(stars, value)).str();
+ }
+ });
+
+ luaCtx.writeFunction("showTCPStats", [] {
+ setLuaNoSideEffect();
+ ostringstream ret;
+ boost::format fmt("%-12d %-12d %-12d %-12d");
+ ret << (fmt % "Workers" % "Max Workers" % "Queued" % "Max Queued") << endl;
+ ret << (fmt % g_tcpclientthreads->getThreadsCount() % (g_maxTCPClientThreads ? *g_maxTCPClientThreads : 0) % g_tcpclientthreads->getQueuedCount() % g_maxTCPQueuedConnections) << endl;
+ ret << endl;
+
+ ret << "Frontends:" << endl;
+ fmt = boost::format("%-3d %-20.20s %-20d %-20d %-20d %-25d %-20d %-20d %-20d %-20f %-20f %-20d %-20d %-25d %-25d %-15d %-15d %-15d %-15d %-15d");
+ ret << (fmt % "#" % "Address" % "Connections" % "Max concurrent conn" % "Died reading query" % "Died sending response" % "Gave up" % "Client timeouts" % "Downstream timeouts" % "Avg queries/conn" % "Avg duration" % "TLS new sessions" % "TLS Resumptions" % "TLS unknown ticket keys" % "TLS inactive ticket keys" % "TLS 1.0" % "TLS 1.1" % "TLS 1.2" % "TLS 1.3" % "TLS other") << endl;
+
+ size_t counter = 0;
+ for (const auto& frontend : g_frontends) {
+ ret << (fmt % counter % frontend->local.toStringWithPort() % frontend->tcpCurrentConnections % frontend->tcpMaxConcurrentConnections % frontend->tcpDiedReadingQuery % frontend->tcpDiedSendingResponse % frontend->tcpGaveUp % frontend->tcpClientTimeouts % frontend->tcpDownstreamTimeouts % frontend->tcpAvgQueriesPerConnection % frontend->tcpAvgConnectionDuration % frontend->tlsNewSessions % frontend->tlsResumptions % frontend->tlsUnknownTicketKey % frontend->tlsInactiveTicketKey % frontend->tls10queries % frontend->tls11queries % frontend->tls12queries % frontend->tls13queries % frontend->tlsUnknownqueries) << endl;
+ ++counter;
+ }
+ ret << endl;
+
+ ret << "Backends:" << endl;
+ fmt = boost::format("%-3d %-20.20s %-20.20s %-20d %-20d %-25d %-25d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20f %-20f");
+ ret << (fmt % "#" % "Name" % "Address" % "Connections" % "Max concurrent conn" % "Died sending query" % "Died reading response" % "Gave up" % "Read timeouts" % "Write timeouts" % "Connect timeouts" % "Too many conn" % "Total connections" % "Reused connections" % "TLS resumptions" % "Avg queries/conn" % "Avg duration") << endl;
+
+ auto states = g_dstates.getLocal();
+ counter = 0;
+ for (const auto& backend : *states) {
+ ret << (fmt % counter % backend->getName() % backend->d_config.remote.toStringWithPort() % backend->tcpCurrentConnections % backend->tcpMaxConcurrentConnections % backend->tcpDiedSendingQuery % backend->tcpDiedReadingResponse % backend->tcpGaveUp % backend->tcpReadTimeouts % backend->tcpWriteTimeouts % backend->tcpConnectTimeouts % backend->tcpTooManyConcurrentConnections % backend->tcpNewConnections % backend->tcpReusedConnections % backend->tlsResumptions % backend->tcpAvgQueriesPerConnection % backend->tcpAvgConnectionDuration) << endl;
+ ++counter;
+ }
+
+ g_outputBuffer = ret.str();
+ });
+
+ luaCtx.writeFunction("showTLSErrorCounters", [] {
+ setLuaNoSideEffect();
+ ostringstream ret;
+ boost::format fmt("%-3d %-20.20s %-23d %-23d %-23d %-23d %-23d %-23d %-23d %-23d");
+
+ ret << (fmt % "#" % "Address" % "DH key too small" % "Inappropriate fallback" % "No shared cipher" % "Unknown cipher type" % "Unknown exchange type" % "Unknown protocol" % "Unsupported EC" % "Unsupported protocol") << endl;
+
+ size_t counter = 0;
+ for (const auto& frontend : g_frontends) {
+ if (!frontend->hasTLS()) {
+ continue;
+ }
+ const TLSErrorCounters* errorCounters = nullptr;
+ if (frontend->tlsFrontend != nullptr) {
+ errorCounters = &frontend->tlsFrontend->d_tlsCounters;
+ }
+ else if (frontend->dohFrontend != nullptr) {
+ errorCounters = &frontend->dohFrontend->d_tlsContext.d_tlsCounters;
+ }
+ if (errorCounters == nullptr) {
+ continue;
+ }
+
+ ret << (fmt % counter % frontend->local.toStringWithPort() % errorCounters->d_dhKeyTooSmall % errorCounters->d_inappropriateFallBack % errorCounters->d_noSharedCipher % errorCounters->d_unknownCipherType % errorCounters->d_unknownKeyExchangeType % errorCounters->d_unknownProtocol % errorCounters->d_unsupportedEC % errorCounters->d_unsupportedProtocol) << endl;
+ ++counter;
+ }
+ ret << endl;
+
+ g_outputBuffer = ret.str();
+ });
+
+ luaCtx.writeFunction("requestTCPStatesDump", [] {
+ setLuaNoSideEffect();
+ extern std::atomic<uint64_t> g_tcpStatesDumpRequested;
+ g_tcpStatesDumpRequested += g_tcpclientthreads->getThreadsCount();
+ });
+
+ luaCtx.writeFunction("requestDoHStatesDump", [] {
+ setLuaNoSideEffect();
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ g_dohStatesDumpRequested += g_dohClientThreads->getThreadsCount();
+#endif
+ });
+
+ luaCtx.writeFunction("dumpStats", [] {
+ setLuaNoSideEffect();
+ vector<string> leftcolumn;
+ vector<string> rightcolumn;
+
+ boost::format fmt("%-35s\t%+11s");
+ g_outputBuffer.clear();
+ auto entries = *dnsdist::metrics::g_stats.entries.read_lock();
+ sort(entries.begin(), entries.end(),
+ [](const decltype(entries)::value_type& lhs, const decltype(entries)::value_type& rhs) {
+ return lhs.d_name < rhs.d_name;
+ });
+ boost::format flt(" %9.1f");
+ for (const auto& entry : entries) {
+ string second;
+ if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+ second = std::to_string((*val)->load());
+ }
+ else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+ second = (flt % (*adval)->load()).str();
+ }
+ else if (const auto& dval = std::get_if<double*>(&entry.d_value)) {
+ second = (flt % (**dval)).str();
+ }
+ else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
+ second = std::to_string((*func)(entry.d_name));
+ }
+
+ if (leftcolumn.size() < entries.size() / 2) {
+ leftcolumn.push_back((fmt % entry.d_name % second).str());
+ }
+ else {
+ rightcolumn.push_back((fmt % entry.d_name % second).str());
+ }
+ }
+
+ auto leftiter = leftcolumn.begin();
+ auto rightiter = rightcolumn.begin();
+ boost::format clmn("%|0t|%1% %|51t|%2%\n");
+
+ for (; leftiter != leftcolumn.end() || rightiter != rightcolumn.end();) {
+ string lentry;
+ string rentry;
+ if (leftiter != leftcolumn.end()) {
+ lentry = *leftiter;
+ leftiter++;
+ }
+ if (rightiter != rightcolumn.end()) {
+ rentry = *rightiter;
+ rightiter++;
+ }
+ g_outputBuffer += (clmn % lentry % rentry).str();
+ }
+ });
+
+#ifndef DISABLE_DYNBLOCKS
+#ifndef DISABLE_DEPRECATED_DYNBLOCK
+ luaCtx.writeFunction("exceedServFails", [](unsigned int rate, int seconds) {
+ setLuaNoSideEffect();
+ return exceedRCode(rate, seconds, RCode::ServFail);
+ });
+ luaCtx.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) {
+ setLuaNoSideEffect();
+ return exceedRCode(rate, seconds, RCode::NXDomain);
+ });
+
+ luaCtx.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) {
+ setLuaNoSideEffect();
+ return exceedRespByterate(rate, seconds);
+ });
+
+ luaCtx.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) {
+ setLuaNoSideEffect();
+ return exceedQueryGen(rate, seconds, [type](counts_t& counts, const Rings::Query& query) {
+ if (query.qtype == type) {
+ counts[query.requestor]++;
+ }
+ });
+ });
+
+ luaCtx.writeFunction("exceedQRate", [](unsigned int rate, int seconds) {
+ setLuaNoSideEffect();
+ return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& query) {
+ counts[query.requestor]++;
+ });
+ });
+
+ luaCtx.writeFunction("getRespRing", getRespRing);
+
+ /* StatNode */
+ luaCtx.registerFunction<unsigned int (StatNode::*)() const>("numChildren",
+ [](const StatNode& node) -> unsigned int {
+ return node.children.size();
+ });
+ luaCtx.registerMember("fullname", &StatNode::fullname);
+ luaCtx.registerMember("labelsCount", &StatNode::labelsCount);
+ luaCtx.registerMember("servfails", &StatNode::Stat::servfails);
+ luaCtx.registerMember("nxdomains", &StatNode::Stat::nxdomains);
+ luaCtx.registerMember("queries", &StatNode::Stat::queries);
+ luaCtx.registerMember("noerrors", &StatNode::Stat::noerrors);
+ luaCtx.registerMember("drops", &StatNode::Stat::drops);
+ luaCtx.registerMember("bytes", &StatNode::Stat::bytes);
+ luaCtx.registerMember("hits", &StatNode::Stat::hits);
+
+ luaCtx.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional<uint64_t> seconds) {
+ statNodeRespRing(std::move(visitor), seconds ? *seconds : 0U);
+ });
+#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+
+ /* DynBlockRulesGroup */
+ luaCtx.writeFunction("dynBlockRulesGroup", []() { return std::make_shared<DynBlockRulesGroup>(); });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQueryRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
+ if (group) {
+ group->setQueryRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setResponseByteRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
+ if (group) {
+ group->setResponseByteRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, DynBlockRulesGroup::smtVisitor_t)>("setSuffixMatchRule", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, DynBlockRulesGroup::smtVisitor_t visitor) {
+ if (group) {
+ group->setSuffixMatchRule(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, std::move(visitor));
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, dnsdist_ffi_stat_node_visitor_t)>("setSuffixMatchRuleFFI", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, dnsdist_ffi_stat_node_visitor_t visitor) {
+ if (group) {
+ group->setSuffixMatchRuleFFI(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, std::move(visitor));
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(const dnsdist_ffi_dynamic_block_inserted_hook&)>("setNewBlockInsertedHook", [](std::shared_ptr<DynBlockRulesGroup>& group, const dnsdist_ffi_dynamic_block_inserted_hook& hook) {
+ if (group) {
+ group->setNewBlockHook(hook);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setRCodeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
+ if (group) {
+ group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, double, unsigned int, const std::string&, unsigned int, size_t, boost::optional<DNSAction::Action>, boost::optional<double>)>("setRCodeRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio) {
+ if (group) {
+ group->setRCodeRatio(rcode, ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQTypeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint16_t qtype, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
+ if (group) {
+ group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(double, unsigned int, const std::string&, unsigned int, size_t, double, boost::optional<DNSAction::Action>, boost::optional<double>)>("setCacheMissRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio) {
+ if (group) {
+ group->setCacheMissRatio(ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses, minimumGlobalCacheHitRatio);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, uint8_t, uint8_t)>("setMasks", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t v4addr, uint8_t v6addr, uint8_t port) {
+ if (group) {
+ if (v4addr > 32) {
+ throw std::runtime_error("Trying to set an invalid IPv4 mask (" + std::to_string(v4addr) + ") to a Dynamic Block object");
+ }
+ if (v6addr > 128) {
+ throw std::runtime_error("Trying to set an invalid IPv6 mask (" + std::to_string(v6addr) + ") to a Dynamic Block object");
+ }
+ if (port > 16) {
+ throw std::runtime_error("Trying to set an invalid port mask (" + std::to_string(port) + ") to a Dynamic Block object");
+ }
+ if (port > 0 && v4addr != 32) {
+ throw std::runtime_error("Setting a non-zero port mask for Dynamic Blocks while only considering parts of IPv4 addresses does not make sense");
+ }
+ group->setMasks(v4addr, v6addr, port);
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
+ if (ranges.type() == typeid(LuaArray<std::string>)) {
+ for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+ group->excludeRange(Netmask(range.second));
+ }
+ }
+ else if (ranges.type() == typeid(NetmaskGroup)) {
+ group->excludeRange(*boost::get<NetmaskGroup>(&ranges));
+ }
+ else {
+ group->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("includeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
+ if (ranges.type() == typeid(LuaArray<std::string>)) {
+ for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+ group->includeRange(Netmask(range.second));
+ }
+ }
+ else if (ranges.type() == typeid(NetmaskGroup)) {
+ group->includeRange(*boost::get<NetmaskGroup>(&ranges));
+ }
+ else {
+ group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("removeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
+ if (ranges.type() == typeid(LuaArray<std::string>)) {
+ for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+ group->removeRange(Netmask(range.second));
+ }
+ }
+ else if (ranges.type() == typeid(NetmaskGroup)) {
+ group->removeRange(*boost::get<NetmaskGroup>(&ranges));
+ }
+ else {
+ group->removeRange(Netmask(*boost::get<std::string>(&ranges)));
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(LuaTypeOrArrayOf<std::string>)>("excludeDomains", [](std::shared_ptr<DynBlockRulesGroup>& group, LuaTypeOrArrayOf<std::string> domains) {
+ if (domains.type() == typeid(LuaArray<std::string>)) {
+ for (const auto& range : *boost::get<LuaArray<std::string>>(&domains)) {
+ group->excludeDomain(DNSName(range.second));
+ }
+ }
+ else {
+ group->excludeDomain(DNSName(*boost::get<std::string>(&domains)));
+ }
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
+ group->apply();
+ });
+ luaCtx.registerFunction("setQuiet", &DynBlockRulesGroup::setQuiet);
+ luaCtx.registerFunction("toString", &DynBlockRulesGroup::toString);
+
+ /* DynBlock object accessors */
+ luaCtx.registerMember("reason", &DynBlock::reason);
+ luaCtx.registerMember("domain", &DynBlock::domain);
+ luaCtx.registerMember("until", &DynBlock::until);
+ luaCtx.registerMember<DynBlock, unsigned int>(
+ "blocks", [](const DynBlock& block) { return block.blocks.load(); }, [](DynBlock& block, [[maybe_unused]] unsigned int blocks) {});
+ luaCtx.registerMember("action", &DynBlock::action);
+ luaCtx.registerMember("warning", &DynBlock::warning);
+ luaCtx.registerMember("bpf", &DynBlock::bpf);
+#endif /* DISABLE_DYNBLOCKS */
+}
namespace dnsdist
{
-NetworkListener::NetworkListener() :
+NetworkListener::ListenerData::ListenerData() :
d_mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(10)))
{
}
+NetworkListener::NetworkListener() :
+ d_data(std::make_shared<ListenerData>())
+{
+}
+
+NetworkListener::~NetworkListener()
+{
+ d_data->d_exiting = true;
+}
+
void NetworkListener::readCB(int desc, FDMultiplexer::funcparam_t& param)
{
auto cbData = boost::any_cast<std::shared_ptr<NetworkListener::CBData>>(param);
#ifdef MSG_TRUNC
/* first we peek to avoid allocating a very large buffer. "MSG_TRUNC [...] return the real length of the datagram, even when it was longer than the passed buffer" */
- auto peeked = recvfrom(desc, nullptr, 0, MSG_PEEK | MSG_TRUNC, nullptr, 0);
+ auto peeked = recvfrom(desc, nullptr, 0, MSG_PEEK | MSG_TRUNC, nullptr, nullptr);
if (peeked > 0) {
packet.resize(static_cast<size_t>(peeked));
}
#endif
- if (packet.size() == 0) {
+ if (packet.empty()) {
packet.resize(65535);
}
- struct sockaddr_un from;
+ sockaddr_un from{};
memset(&from, 0, sizeof(from));
socklen_t fromLen = sizeof(from);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
auto got = recvfrom(desc, &packet.at(0), packet.size(), 0, reinterpret_cast<sockaddr*>(&from), &fromLen);
if (got > 0) {
packet.resize(static_cast<size_t>(got));
std::string fromAddr;
if (fromLen <= sizeof(from)) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
fromAddr = std::string(from.sun_path, strlen(from.sun_path));
}
try {
}
}
-bool NetworkListener::addUnixListeningEndpoint(const std::string& path, NetworkListener::EndpointID id, NetworkListener::NetworkDatagramCB cb)
+bool NetworkListener::addUnixListeningEndpoint(const std::string& path, NetworkListener::EndpointID endpointID, NetworkListener::NetworkDatagramCB callback)
{
- if (d_running == true) {
+ if (d_data->d_running) {
throw std::runtime_error("NetworkListener should not be altered at runtime");
}
- struct sockaddr_un sun;
+ sockaddr_un sun{};
if (makeUNsockaddr(path, &sun) != 0) {
throw std::runtime_error("Invalid Unix socket path '" + path + "'");
}
sunLength = sizeof(sa_family_t) + path.size();
}
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
if (bind(sock.getHandle(), reinterpret_cast<const struct sockaddr*>(&sun), sunLength) != 0) {
std::string sanitizedPath(path);
if (abstractPath) {
sock.setNonBlocking();
auto cbData = std::make_shared<CBData>();
- cbData->d_endpoint = id;
- cbData->d_cb = cb;
- d_mplexer->addReadFD(sock.getHandle(), readCB, cbData);
+ cbData->d_endpoint = endpointID;
+ cbData->d_cb = std::move(callback);
+ d_data->d_mplexer->addReadFD(sock.getHandle(), readCB, cbData);
- d_sockets.insert({path, std::move(sock)});
+ d_data->d_sockets.insert({path, std::move(sock)});
return true;
}
-void NetworkListener::runOnce(struct timeval& now, uint32_t timeout)
+void NetworkListener::runOnce(ListenerData& data, timeval& now, uint32_t timeout)
{
- d_running = true;
- if (d_sockets.empty()) {
+ if (data.d_exiting) {
+ return;
+ }
+
+ data.d_running = true;
+ if (data.d_sockets.empty()) {
throw runtime_error("NetworkListener started with no sockets");
}
- d_mplexer->run(&now, timeout);
+ data.d_mplexer->run(&now, static_cast<int>(timeout));
+}
+
+void NetworkListener::runOnce(timeval& now, uint32_t timeout)
+{
+ runOnce(*d_data, now, timeout);
}
-void NetworkListener::mainThread()
+void NetworkListener::mainThread(std::shared_ptr<ListenerData>& dataArg)
{
+ /* take our own copy of the shared_ptr so it's still alive if the NetworkListener object
+ gets destroyed while we are still running */
+ // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): we really need a copy here, or we end up with use-after-free as explained above
+ auto data = dataArg;
setThreadName("dnsdist/lua-net");
- struct timeval now;
+ timeval now{};
- while (true) {
- runOnce(now, -1);
+ while (!data->d_exiting) {
+ runOnce(*data, now, -1);
}
}
void NetworkListener::start()
{
std::thread main = std::thread([this] {
- mainThread();
+ mainThread(d_data);
});
main.detach();
}
NetworkEndpoint::NetworkEndpoint(const std::string& path) :
d_socket(AF_UNIX, SOCK_DGRAM, 0)
{
- struct sockaddr_un sun;
+ sockaddr_un sun{};
if (makeUNsockaddr(path, &sun) != 0) {
throw std::runtime_error("Invalid Unix socket path '" + path + "'");
}
/* abstract paths can contain null bytes so we need to set the actual size */
sunLength = sizeof(sa_family_t) + path.size();
}
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
if (connect(d_socket.getHandle(), reinterpret_cast<const struct sockaddr*>(&sun), sunLength) != 0) {
std::string sanitizedPath(path);
if (abstractPath) {
{
public:
NetworkListener();
+ NetworkListener(const NetworkListener&) = delete;
+ NetworkListener(NetworkListener&&) = delete;
+ NetworkListener& operator=(const NetworkListener&) = delete;
+ NetworkListener& operator=(NetworkListener&&) = delete;
+ ~NetworkListener();
using EndpointID = uint16_t;
using NetworkDatagramCB = std::function<void(EndpointID endpoint, std::string&& dgram, const std::string& from)>;
- bool addUnixListeningEndpoint(const std::string& path, EndpointID id, NetworkDatagramCB cb);
+ bool addUnixListeningEndpoint(const std::string& path, EndpointID endpointID, NetworkDatagramCB callback);
void start();
- void runOnce(struct timeval& now, uint32_t timeout);
+ void runOnce(timeval& now, uint32_t timeout);
private:
+ struct ListenerData
+ {
+ ListenerData();
+
+ std::unique_ptr<FDMultiplexer> d_mplexer;
+ std::unordered_map<std::string, Socket> d_sockets;
+ std::atomic<bool> d_running{false};
+ std::atomic<bool> d_exiting{false};
+ };
+
static void readCB(int desc, FDMultiplexer::funcparam_t& param);
- void mainThread();
+ static void mainThread(std::shared_ptr<ListenerData>& data);
+ static void runOnce(ListenerData& data, timeval& now, uint32_t timeout);
struct CBData
{
EndpointID d_endpoint;
};
- std::unique_ptr<FDMultiplexer> d_mplexer;
- std::unordered_map<std::string, Socket> d_sockets;
- std::atomic<bool> d_running{false};
+ std::shared_ptr<ListenerData> d_data;
};
class NetworkEndpoint
+++ /dev/null
-../dnsdist-lua-rules.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dnsdist.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-rules.hh"
+#include "dnsdist-rule-chains.hh"
+#include "dns_random.hh"
+
+std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var, const std::string& calledFrom)
+{
+ if (var.type() == typeid(std::shared_ptr<DNSRule>)) {
+ return *boost::get<std::shared_ptr<DNSRule>>(&var);
+ }
+
+ bool suffixSeen = false;
+ SuffixMatchNode smn;
+ NetmaskGroup nmg;
+ auto add = [&nmg, &smn, &suffixSeen](const string& src) {
+ try {
+ nmg.addMask(src); // need to try mask first, all masks are domain names!
+ }
+ catch (...) {
+ suffixSeen = true;
+ smn.add(DNSName(src));
+ }
+ };
+
+ if (var.type() == typeid(string)) {
+ add(*boost::get<string>(&var));
+ }
+ else if (var.type() == typeid(LuaArray<std::string>)) {
+ for (const auto& str : *boost::get<LuaArray<std::string>>(&var)) {
+ add(str.second);
+ }
+ }
+ else if (var.type() == typeid(DNSName)) {
+ smn.add(*boost::get<DNSName>(&var));
+ }
+ else if (var.type() == typeid(LuaArray<DNSName>)) {
+ smn = SuffixMatchNode();
+ for (const auto& name : *boost::get<LuaArray<DNSName>>(&var)) {
+ smn.add(name.second);
+ }
+ }
+
+ if (nmg.empty()) {
+ return std::make_shared<SuffixMatchNodeRule>(smn);
+ }
+ if (suffixSeen) {
+ warnlog("At least one parameter to %s has been parsed as a domain name amongst network masks, and will be ignored!", calledFrom);
+ }
+ return std::make_shared<NetmaskGroupRule>(nmg, true);
+}
+
+static boost::uuids::uuid makeRuleID(std::string& identifier)
+{
+ if (identifier.empty()) {
+ return getUniqueID();
+ }
+
+ return getUniqueID(identifier);
+}
+
+void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder)
+{
+ static uint64_t s_creationOrder = 0;
+
+ string uuidStr;
+
+ getOptionalValue<std::string>(params, "uuid", uuidStr);
+ getOptionalValue<std::string>(params, "name", name);
+
+ uuid = makeRuleID(uuidStr);
+ creationOrder = s_creationOrder++;
+}
+
+using ruleparams_t = LuaAssociativeTable<boost::variant<bool, int, std::string, LuaArray<int>>>;
+
+template <typename T>
+static std::string rulesToString(const std::vector<T>& rules, boost::optional<ruleparams_t>& vars)
+{
+ int num = 0;
+ bool showUUIDs = false;
+ size_t truncateRuleWidth = string::npos;
+ std::string result;
+
+ getOptionalValue<bool>(vars, "showUUIDs", showUUIDs);
+ getOptionalValue<int>(vars, "truncateRuleWidth", truncateRuleWidth);
+ checkAllParametersConsumed("rulesToString", vars);
+
+ if (showUUIDs) {
+ boost::format fmt("%-3d %-30s %-38s %9d %9d %-56s %s\n");
+ result += (fmt % "#" % "Name" % "UUID" % "Cr. Order" % "Matches" % "Rule" % "Action").str();
+ for (const auto& lim : rules) {
+ string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
+ result += (fmt % num % lim.d_name % boost::uuids::to_string(lim.d_id) % lim.d_creationOrder % lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
+ ++num;
+ }
+ }
+ else {
+ boost::format fmt("%-3d %-30s %9d %-56s %s\n");
+ result += (fmt % "#" % "Name" % "Matches" % "Rule" % "Action").str();
+ for (const auto& lim : rules) {
+ string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
+ result += (fmt % num % lim.d_name % lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
+ ++num;
+ }
+ }
+ return result;
+}
+
+template <typename T>
+static void showRules(GlobalStateHolder<vector<T>>* someRuleActions, boost::optional<ruleparams_t>& vars)
+{
+ setLuaNoSideEffect();
+
+ auto rules = someRuleActions->getLocal();
+ g_outputBuffer += rulesToString(*rules, vars);
+}
+
+template <typename T>
+static void rmRule(GlobalStateHolder<vector<T>>* someRuleActions, const boost::variant<unsigned int, std::string>& ruleID)
+{
+ setLuaSideEffect();
+ auto rules = someRuleActions->getCopy();
+ if (const auto* str = boost::get<std::string>(&ruleID)) {
+ try {
+ const auto uuid = getUniqueID(*str);
+ auto removeIt = std::remove_if(rules.begin(),
+ rules.end(),
+ [&uuid](const T& rule) { return rule.d_id == uuid; });
+ if (removeIt == rules.end()) {
+ g_outputBuffer = "Error: no rule matched\n";
+ return;
+ }
+ rules.erase(removeIt,
+ rules.end());
+ }
+ catch (const std::runtime_error& e) {
+ /* it was not an UUID, let's see if it was a name instead */
+ auto removeIt = std::remove_if(rules.begin(),
+ rules.end(),
+ [&str](const T& rule) { return rule.d_name == *str; });
+ if (removeIt == rules.end()) {
+ g_outputBuffer = "Error: no rule matched\n";
+ return;
+ }
+ rules.erase(removeIt,
+ rules.end());
+ }
+ }
+ else if (const auto* pos = boost::get<unsigned int>(&ruleID)) {
+ if (*pos >= rules.size()) {
+ g_outputBuffer = "Error: attempt to delete non-existing rule\n";
+ return;
+ }
+ rules.erase(rules.begin() + *pos);
+ }
+ someRuleActions->setState(std::move(rules));
+}
+
+template <typename T>
+static void moveRuleToTop(GlobalStateHolder<vector<T>>* someRuleActions)
+{
+ setLuaSideEffect();
+ auto rules = someRuleActions->getCopy();
+ if (rules.empty()) {
+ return;
+ }
+ auto subject = *rules.rbegin();
+ rules.erase(std::prev(rules.end()));
+ rules.insert(rules.begin(), subject);
+ someRuleActions->setState(std::move(rules));
+}
+
+template <typename T>
+static void mvRule(GlobalStateHolder<vector<T>>* someRespRuleActions, unsigned int from, unsigned int destination)
+{
+ setLuaSideEffect();
+ auto rules = someRespRuleActions->getCopy();
+ if (from >= rules.size() || destination > rules.size()) {
+ g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
+ return;
+ }
+ auto subject = rules[from];
+ rules.erase(rules.begin() + from);
+ if (destination > rules.size()) {
+ rules.push_back(subject);
+ }
+ else {
+ if (from < destination) {
+ --destination;
+ }
+ rules.insert(rules.begin() + destination, subject);
+ }
+ someRespRuleActions->setState(std::move(rules));
+}
+
+template <typename T>
+static std::vector<T> getTopRules(const std::vector<T>& rules, unsigned int top)
+{
+ std::vector<std::pair<size_t, size_t>> counts;
+ counts.reserve(rules.size());
+
+ size_t pos = 0;
+ for (const auto& rule : rules) {
+ counts.push_back({rule.d_rule->d_matches.load(), pos});
+ pos++;
+ }
+
+ sort(counts.begin(), counts.end(), [](const decltype(counts)::value_type& lhs, const decltype(counts)::value_type& rhs) {
+ return rhs.first < lhs.first;
+ });
+
+ std::vector<T> results;
+ results.reserve(top);
+
+ size_t count = 0;
+ for (const auto& entry : counts) {
+ results.emplace_back(rules.at(entry.second));
+ ++count;
+ if (count == top) {
+ break;
+ }
+ }
+
+ return results;
+}
+
+template <typename T>
+static LuaArray<T> toLuaArray(std::vector<T>&& rules)
+{
+ LuaArray<T> results;
+ results.reserve(rules.size());
+
+ size_t pos = 1;
+ for (auto& rule : rules) {
+ results.emplace_back(pos, std::move(rule));
+ pos++;
+ }
+
+ return results;
+}
+
+template <typename T>
+static boost::optional<T> getRuleFromSelector(const std::vector<T>& rules, const boost::variant<int, std::string>& selector)
+{
+ if (const auto* str = boost::get<std::string>(&selector)) {
+ /* let's see if this a UUID */
+ try {
+ const auto uuid = getUniqueID(*str);
+ for (const auto& rule : rules) {
+ if (rule.d_id == uuid) {
+ return rule;
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ /* a name, then */
+ for (const auto& rule : rules) {
+ if (rule.d_name == *str) {
+ return rule;
+ }
+ }
+ }
+ }
+ else if (const auto* pos = boost::get<int>(&selector)) {
+ /* this will throw a std::out_of_range exception if the
+ supplied position is out of bounds, this is fine */
+ return rules.at(*pos);
+ }
+ return boost::none;
+}
+
+namespace
+{
+std::shared_ptr<DNSRule> qnameSuffixRule(const boost::variant<const SuffixMatchNode&, std::string, const LuaArray<std::string>> names, boost::optional<bool> quiet)
+{
+ if (names.type() == typeid(string)) {
+ SuffixMatchNode smn;
+ smn.add(DNSName(*boost::get<std::string>(&names)));
+ return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
+ }
+
+ if (names.type() == typeid(LuaArray<std::string>)) {
+ SuffixMatchNode smn;
+ for (const auto& str : *boost::get<const LuaArray<std::string>>(&names)) {
+ smn.add(DNSName(str.second));
+ }
+ return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
+ }
+
+ const auto& smn = *boost::get<const SuffixMatchNode&>(&names);
+ return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
+}
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaRules(LuaContext& luaCtx)
+{
+ luaCtx.writeFunction("makeRule", [](const luadnsrule_t& var) -> std::shared_ptr<DNSRule> {
+ return makeRule(var, "makeRule");
+ });
+
+ luaCtx.registerFunction<string (std::shared_ptr<DNSRule>::*)() const>("toString", [](const std::shared_ptr<DNSRule>& rule) { return rule->toString(); });
+
+ luaCtx.registerFunction<uint64_t (std::shared_ptr<DNSRule>::*)() const>("getMatches", [](const std::shared_ptr<DNSRule>& rule) { return rule->d_matches.load(); });
+
+ luaCtx.registerFunction<std::shared_ptr<DNSRule> (dnsdist::rules::RuleAction::*)() const>("getSelector", [](const dnsdist::rules::RuleAction& rule) { return rule.d_rule; });
+
+ luaCtx.registerFunction<std::shared_ptr<DNSAction> (dnsdist::rules::RuleAction::*)() const>("getAction", [](const dnsdist::rules::RuleAction& rule) { return rule.d_action; });
+
+ luaCtx.registerFunction<std::shared_ptr<DNSRule> (dnsdist::rules::ResponseRuleAction::*)() const>("getSelector", [](const dnsdist::rules::ResponseRuleAction& rule) { return rule.d_rule; });
+
+ luaCtx.registerFunction<std::shared_ptr<DNSResponseAction> (dnsdist::rules::ResponseRuleAction::*)() const>("getAction", [](const dnsdist::rules::ResponseRuleAction& rule) { return rule.d_action; });
+
+ for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+ luaCtx.writeFunction("show" + chain.prefix + "ResponseRules", [&chain](boost::optional<ruleparams_t> vars) {
+ showRules(&chain.holder, vars);
+ });
+ luaCtx.writeFunction("rm" + chain.prefix + "ResponseRule", [&chain](const boost::variant<unsigned int, std::string>& identifier) {
+ rmRule(&chain.holder, identifier);
+ });
+ luaCtx.writeFunction("mv" + chain.prefix + "ResponseRuleToTop", [&chain]() {
+ moveRuleToTop(&chain.holder);
+ });
+ luaCtx.writeFunction("mv" + chain.prefix + "ResponseRule", [&chain](unsigned int from, unsigned int dest) {
+ mvRule(&chain.holder, from, dest);
+ });
+ luaCtx.writeFunction("get" + chain.prefix + "ResponseRule", [&chain](const boost::variant<int, std::string>& selector) -> boost::optional<dnsdist::rules::ResponseRuleAction> {
+ auto rules = chain.holder.getLocal();
+ return getRuleFromSelector(*rules, selector);
+ });
+
+ luaCtx.writeFunction("getTop" + chain.prefix + "ResponseRules", [&chain](boost::optional<unsigned int> top) {
+ setLuaNoSideEffect();
+ auto rules = chain.holder.getLocal();
+ return toLuaArray(getTopRules(*rules, (top ? *top : 10)));
+ });
+
+ luaCtx.writeFunction("top" + chain.prefix + "ResponseRules", [&chain](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
+ setLuaNoSideEffect();
+ auto rules = chain.holder.getLocal();
+ return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
+ });
+ }
+
+ luaCtx.writeFunction("rmRule", [](const boost::variant<unsigned int, std::string>& identifier) {
+ rmRule(&dnsdist::rules::g_ruleactions, identifier);
+ });
+
+ luaCtx.writeFunction("mvRuleToTop", []() {
+ moveRuleToTop(&dnsdist::rules::g_ruleactions);
+ });
+
+ luaCtx.writeFunction("mvRule", [](unsigned int from, unsigned int dest) {
+ mvRule(&dnsdist::rules::g_ruleactions, from, dest);
+ });
+
+ luaCtx.writeFunction("clearRules", []() {
+ setLuaSideEffect();
+ dnsdist::rules::g_ruleactions.modify([](decltype(dnsdist::rules::g_ruleactions)::value_type& ruleactions) {
+ ruleactions.clear();
+ });
+ });
+
+ luaCtx.writeFunction("setRules", [](const LuaArray<std::shared_ptr<dnsdist::rules::RuleAction>>& newruleactions) {
+ setLuaSideEffect();
+ dnsdist::rules::g_ruleactions.modify([newruleactions](decltype(dnsdist::rules::g_ruleactions)::value_type& gruleactions) {
+ gruleactions.clear();
+ for (const auto& pair : newruleactions) {
+ const auto& newruleaction = pair.second;
+ if (newruleaction->d_action) {
+ auto rule = newruleaction->d_rule;
+ gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_name, newruleaction->d_id, newruleaction->d_creationOrder});
+ }
+ }
+ });
+ });
+
+ luaCtx.writeFunction("getRule", [](const boost::variant<int, std::string>& selector) -> boost::optional<dnsdist::rules::RuleAction> {
+ auto rules = dnsdist::rules::g_ruleactions.getLocal();
+ return getRuleFromSelector(*rules, selector);
+ });
+
+ luaCtx.writeFunction("getTopRules", [](boost::optional<unsigned int> top) {
+ setLuaNoSideEffect();
+ auto rules = dnsdist::rules::g_ruleactions.getLocal();
+ return toLuaArray(getTopRules(*rules, (top ? *top : 10)));
+ });
+
+ luaCtx.writeFunction("topRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
+ setLuaNoSideEffect();
+ auto rules = dnsdist::rules::g_ruleactions.getLocal();
+ return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
+ });
+
+ luaCtx.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<unsigned int> ipv4trunc, boost::optional<unsigned int> ipv6trunc, boost::optional<unsigned int> burst, boost::optional<unsigned int> expiration, boost::optional<unsigned int> cleanupDelay, boost::optional<unsigned int> scanFraction, boost::optional<unsigned int> shards) {
+ return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, (burst ? *burst : qps), (ipv4trunc ? *ipv4trunc : 32), (ipv6trunc ? *ipv6trunc : 64), (expiration ? *expiration : 300), (cleanupDelay ? *cleanupDelay : 60), (scanFraction ? *scanFraction : 10), (shards ? *shards : 10)));
+ });
+
+ luaCtx.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional<unsigned int> burst) {
+ if (!burst) {
+ return std::shared_ptr<DNSRule>(new MaxQPSRule(qps));
+ }
+ return std::shared_ptr<DNSRule>(new MaxQPSRule(qps, *burst));
+ });
+
+ luaCtx.writeFunction("RegexRule", [](const std::string& str) {
+ return std::shared_ptr<DNSRule>(new RegexRule(str));
+ });
+
+#ifdef HAVE_DNS_OVER_HTTPS
+ luaCtx.writeFunction("HTTPHeaderRule", [](const std::string& header, const std::string& regex) {
+ return std::shared_ptr<DNSRule>(new HTTPHeaderRule(header, regex));
+ });
+ luaCtx.writeFunction("HTTPPathRule", [](const std::string& path) {
+ return std::shared_ptr<DNSRule>(new HTTPPathRule(path));
+ });
+ luaCtx.writeFunction("HTTPPathRegexRule", [](const std::string& regex) {
+ return std::shared_ptr<DNSRule>(new HTTPPathRegexRule(regex));
+ });
+#endif
+
+#ifdef HAVE_RE2
+ luaCtx.writeFunction("RE2Rule", [](const std::string& str) {
+ return std::shared_ptr<DNSRule>(new RE2Rule(str));
+ });
+#endif
+
+ luaCtx.writeFunction("SNIRule", [](const std::string& name) {
+ return std::shared_ptr<DNSRule>(new SNIRule(name));
+ });
+
+ luaCtx.writeFunction("SuffixMatchNodeRule", qnameSuffixRule);
+
+ luaCtx.writeFunction("NetmaskGroupRule", [](const boost::variant<const NetmaskGroup&, std::string, const LuaArray<std::string>> netmasks, boost::optional<bool> src, boost::optional<bool> quiet) {
+ if (netmasks.type() == typeid(string)) {
+ NetmaskGroup nmg;
+ nmg.addMask(*boost::get<std::string>(&netmasks));
+ return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false));
+ }
+
+ if (netmasks.type() == typeid(LuaArray<std::string>)) {
+ NetmaskGroup nmg;
+ for (const auto& str : *boost::get<const LuaArray<std::string>>(&netmasks)) {
+ nmg.addMask(str.second);
+ }
+ return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false));
+ }
+
+ const auto& nmg = *boost::get<const NetmaskGroup&>(&netmasks);
+ return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false));
+ });
+
+ luaCtx.writeFunction("benchRule", [](const std::shared_ptr<DNSRule>& rule, boost::optional<unsigned int> times_, boost::optional<string> suffix_) {
+ setLuaNoSideEffect();
+ unsigned int times = times_ ? *times_ : 100000;
+ DNSName suffix(suffix_ ? *suffix_ : "powerdns.com");
+ // NOLINTNEXTLINE(bugprone-exception-escape): not sure what clang-tidy smoked, but we do not really care here
+ struct item
+ {
+ PacketBuffer packet;
+ InternalQueryState ids;
+ };
+ vector<item> items;
+ items.reserve(1000);
+ for (int counter = 0; counter < 1000; ++counter) {
+ item entry;
+ entry.ids.qname = DNSName(std::to_string(dns_random_uint32()));
+ entry.ids.qname += suffix;
+ entry.ids.qtype = dns_random(0xff);
+ entry.ids.qclass = QClass::IN;
+ entry.ids.protocol = dnsdist::Protocol::DoUDP;
+ entry.ids.origRemote = ComboAddress("127.0.0.1");
+ entry.ids.origRemote.sin4.sin_addr.s_addr = random();
+ entry.ids.queryRealTime.start();
+ GenericDNSPacketWriter<PacketBuffer> writer(entry.packet, entry.ids.qname, entry.ids.qtype);
+ items.push_back(std::move(entry));
+ }
+
+ int matches = 0;
+ ComboAddress dummy("127.0.0.1");
+ StopWatch swatch;
+ swatch.start();
+ for (unsigned int counter = 0; counter < times; ++counter) {
+ item& entry = items[counter % items.size()];
+ DNSQuestion dnsQuestion(entry.ids, entry.packet);
+
+ if (rule->matches(&dnsQuestion)) {
+ matches++;
+ }
+ }
+ double udiff = swatch.udiff();
+ g_outputBuffer = (boost::format("Had %d matches out of %d, %.1f qps, in %.1f us\n") % matches % times % (1000000 * (1.0 * times / udiff)) % udiff).str();
+ });
+
+ luaCtx.writeFunction("AllRule", []() {
+ return std::shared_ptr<DNSRule>(new AllRule());
+ });
+
+ luaCtx.writeFunction("ProbaRule", [](double proba) {
+ return std::shared_ptr<DNSRule>(new ProbaRule(proba));
+ });
+
+ luaCtx.writeFunction("QNameRule", [](const std::string& qname) {
+ return std::shared_ptr<DNSRule>(new QNameRule(DNSName(qname)));
+ });
+
+ luaCtx.writeFunction("QNameSuffixRule", qnameSuffixRule);
+
+ luaCtx.writeFunction("QTypeRule", [](boost::variant<unsigned int, std::string> str) {
+ uint16_t qtype{};
+ if (const auto* dir = boost::get<unsigned int>(&str)) {
+ qtype = *dir;
+ }
+ else {
+ string val = boost::get<string>(str);
+ qtype = QType::chartocode(val.c_str());
+ if (qtype == 0) {
+ throw std::runtime_error("Unable to convert '" + val + "' to a DNS type");
+ }
+ }
+ return std::shared_ptr<DNSRule>(new QTypeRule(qtype));
+ });
+
+ luaCtx.writeFunction("QClassRule", [](uint64_t cla) {
+ checkParameterBound("QClassRule", cla, std::numeric_limits<uint16_t>::max());
+ return std::shared_ptr<DNSRule>(new QClassRule(cla));
+ });
+
+ luaCtx.writeFunction("OpcodeRule", [](uint64_t code) {
+ checkParameterBound("OpcodeRule", code, std::numeric_limits<uint8_t>::max());
+ return std::shared_ptr<DNSRule>(new OpcodeRule(code));
+ });
+
+ luaCtx.writeFunction("AndRule", [](const LuaArray<std::shared_ptr<DNSRule>>& rules) {
+ return std::shared_ptr<DNSRule>(new AndRule(rules));
+ });
+
+ luaCtx.writeFunction("OrRule", [](const LuaArray<std::shared_ptr<DNSRule>>& rules) {
+ return std::shared_ptr<DNSRule>(new OrRule(rules));
+ });
+
+ luaCtx.writeFunction("DSTPortRule", [](uint64_t port) {
+ checkParameterBound("DSTPortRule", port, std::numeric_limits<uint16_t>::max());
+ return std::shared_ptr<DNSRule>(new DSTPortRule(port));
+ });
+
+ luaCtx.writeFunction("TCPRule", [](bool tcp) {
+ return std::shared_ptr<DNSRule>(new TCPRule(tcp));
+ });
+
+ luaCtx.writeFunction("DNSSECRule", []() {
+ return std::shared_ptr<DNSRule>(new DNSSECRule());
+ });
+
+ luaCtx.writeFunction("NotRule", [](const std::shared_ptr<DNSRule>& rule) {
+ return std::shared_ptr<DNSRule>(new NotRule(rule));
+ });
+
+ luaCtx.writeFunction("RecordsCountRule", [](uint64_t section, uint64_t minCount, uint64_t maxCount) {
+ checkParameterBound("RecordsCountRule", section, std::numeric_limits<uint8_t>::max());
+ checkParameterBound("RecordsCountRule", minCount, std::numeric_limits<uint16_t>::max());
+ checkParameterBound("RecordsCountRule", maxCount, std::numeric_limits<uint16_t>::max());
+ return std::shared_ptr<DNSRule>(new RecordsCountRule(section, minCount, maxCount));
+ });
+
+ luaCtx.writeFunction("RecordsTypeCountRule", [](uint64_t section, uint64_t type, uint64_t minCount, uint64_t maxCount) {
+ checkParameterBound("RecordsTypeCountRule", section, std::numeric_limits<uint8_t>::max());
+ checkParameterBound("RecordsTypeCountRule", type, std::numeric_limits<uint16_t>::max());
+ checkParameterBound("RecordsTypeCountRule", minCount, std::numeric_limits<uint16_t>::max());
+ checkParameterBound("RecordsTypeCountRule", maxCount, std::numeric_limits<uint16_t>::max());
+ return std::shared_ptr<DNSRule>(new RecordsTypeCountRule(section, type, minCount, maxCount));
+ });
+
+ luaCtx.writeFunction("TrailingDataRule", []() {
+ return std::shared_ptr<DNSRule>(new TrailingDataRule());
+ });
+
+ luaCtx.writeFunction("QNameLabelsCountRule", [](uint64_t minLabelsCount, uint64_t maxLabelsCount) {
+ checkParameterBound("QNameLabelsCountRule", minLabelsCount, std::numeric_limits<unsigned int>::max());
+ checkParameterBound("QNameLabelsCountRule", maxLabelsCount, std::numeric_limits<unsigned int>::max());
+ return std::shared_ptr<DNSRule>(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount));
+ });
+
+ luaCtx.writeFunction("QNameWireLengthRule", [](uint64_t min, uint64_t max) {
+ return std::shared_ptr<DNSRule>(new QNameWireLengthRule(min, max));
+ });
+
+ luaCtx.writeFunction("RCodeRule", [](uint64_t rcode) {
+ checkParameterBound("RCodeRule", rcode, std::numeric_limits<uint8_t>::max());
+ return std::shared_ptr<DNSRule>(new RCodeRule(rcode));
+ });
+
+ luaCtx.writeFunction("ERCodeRule", [](uint64_t rcode) {
+ checkParameterBound("ERCodeRule", rcode, std::numeric_limits<uint8_t>::max());
+ return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
+ });
+
+ luaCtx.writeFunction("EDNSVersionRule", [](uint64_t version) {
+ checkParameterBound("EDNSVersionRule", version, std::numeric_limits<uint8_t>::max());
+ return std::shared_ptr<DNSRule>(new EDNSVersionRule(version));
+ });
+
+ luaCtx.writeFunction("EDNSOptionRule", [](uint64_t optcode) {
+ checkParameterBound("EDNSOptionRule", optcode, std::numeric_limits<uint16_t>::max());
+ return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
+ });
+
+ luaCtx.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
+ showRules(&dnsdist::rules::g_ruleactions, vars);
+ });
+
+ luaCtx.writeFunction("RDRule", []() {
+ return std::shared_ptr<DNSRule>(new RDRule());
+ });
+
+ luaCtx.writeFunction("TagRule", [](const std::string& tag, boost::optional<std::string> value) {
+ return std::shared_ptr<DNSRule>(new TagRule(tag, std::move(value)));
+ });
+
+ luaCtx.writeFunction("TimedIPSetRule", []() {
+ return std::make_shared<TimedIPSetRule>();
+ });
+
+ luaCtx.writeFunction("PoolAvailableRule", [](const std::string& poolname) {
+ return std::shared_ptr<DNSRule>(new PoolAvailableRule(poolname));
+ });
+
+ luaCtx.writeFunction("PoolOutstandingRule", [](const std::string& poolname, uint64_t limit) {
+ return std::shared_ptr<DNSRule>(new PoolOutstandingRule(poolname, limit));
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TimedIPSetRule>::*)()>("clear", [](const std::shared_ptr<TimedIPSetRule>& tisr) {
+ tisr->clear();
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TimedIPSetRule>::*)()>("cleanup", [](const std::shared_ptr<TimedIPSetRule>& tisr) {
+ tisr->cleanup();
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TimedIPSetRule>::*)(const ComboAddress&, int)>("add", [](const std::shared_ptr<TimedIPSetRule>& tisr, const ComboAddress& addr, int additional) {
+ tisr->add(addr, time(nullptr) + additional);
+ });
+
+ luaCtx.registerFunction<std::shared_ptr<DNSRule> (std::shared_ptr<TimedIPSetRule>::*)()>("slice", [](const std::shared_ptr<TimedIPSetRule>& tisr) {
+ return std::dynamic_pointer_cast<DNSRule>(tisr);
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<TimedIPSetRule>::*)()>("__tostring", [](const std::shared_ptr<TimedIPSetRule>& tisr) {
+ tisr->toString();
+ });
+
+ luaCtx.writeFunction("QNameSetRule", [](const DNSNameSet& names) {
+ return std::shared_ptr<DNSRule>(new QNameSetRule(names));
+ });
+
+#if defined(HAVE_LMDB) || defined(HAVE_CDB)
+ luaCtx.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
+ return std::shared_ptr<DNSRule>(new KeyValueStoreLookupRule(kvs, lookupKey));
+ });
+
+ luaCtx.writeFunction("KeyValueStoreRangeLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
+ return std::shared_ptr<DNSRule>(new KeyValueStoreRangeLookupRule(kvs, lookupKey));
+ });
+#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
+
+ luaCtx.writeFunction("LuaRule", [](const LuaRule::func_t& func) {
+ return std::shared_ptr<DNSRule>(new LuaRule(func));
+ });
+
+ luaCtx.writeFunction("LuaFFIRule", [](const LuaFFIRule::func_t& func) {
+ return std::shared_ptr<DNSRule>(new LuaFFIRule(func));
+ });
+
+ luaCtx.writeFunction("LuaFFIPerThreadRule", [](const std::string& code) {
+ return std::shared_ptr<DNSRule>(new LuaFFIPerThreadRule(code));
+ });
+
+ luaCtx.writeFunction("ProxyProtocolValueRule", [](uint8_t type, boost::optional<std::string> value) {
+ return std::shared_ptr<DNSRule>(new ProxyProtocolValueRule(type, std::move(value)));
+ });
+
+ luaCtx.writeFunction("PayloadSizeRule", [](const std::string& comparison, uint16_t size) {
+ return std::shared_ptr<DNSRule>(new PayloadSizeRule(comparison, size));
+ });
+}
+++ /dev/null
-../dnsdist-lua-vars.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dnsdist.hh"
+#include "dnsdist-lua.hh"
+#include "ednsoptions.hh"
+
+#undef BADSIG // signal.h SIG_ERR
+
+void setupLuaVars(LuaContext& luaCtx)
+{
+ luaCtx.writeVariable("DNSAction", LuaAssociativeTable<int>{{"Drop", (int)DNSAction::Action::Drop}, {"Nxdomain", (int)DNSAction::Action::Nxdomain}, {"Refused", (int)DNSAction::Action::Refused}, {"Spoof", (int)DNSAction::Action::Spoof}, {"SpoofPacket", (int)DNSAction::Action::SpoofPacket}, {"SpoofRaw", (int)DNSAction::Action::SpoofRaw}, {"Allow", (int)DNSAction::Action::Allow}, {"HeaderModify", (int)DNSAction::Action::HeaderModify}, {"Pool", (int)DNSAction::Action::Pool}, {"None", (int)DNSAction::Action::None}, {"NoOp", (int)DNSAction::Action::NoOp}, {"Delay", (int)DNSAction::Action::Delay}, {"Truncate", (int)DNSAction::Action::Truncate}, {"ServFail", (int)DNSAction::Action::ServFail}, {"NoRecurse", (int)DNSAction::Action::NoRecurse}});
+
+ luaCtx.writeVariable("DNSResponseAction", LuaAssociativeTable<int>{{"Allow", (int)DNSResponseAction::Action::Allow}, {"Delay", (int)DNSResponseAction::Action::Delay}, {"Drop", (int)DNSResponseAction::Action::Drop}, {"HeaderModify", (int)DNSResponseAction::Action::HeaderModify}, {"ServFail", (int)DNSResponseAction::Action::ServFail}, {"Truncate", (int)DNSResponseAction::Action::Truncate}, {"None", (int)DNSResponseAction::Action::None}});
+
+ luaCtx.writeVariable("DNSClass", LuaAssociativeTable<int>{{"IN", QClass::IN}, {"CHAOS", QClass::CHAOS}, {"NONE", QClass::NONE}, {"ANY", QClass::ANY}});
+
+ luaCtx.writeVariable("DNSOpcode", LuaAssociativeTable<int>{{"Query", Opcode::Query}, {"IQuery", Opcode::IQuery}, {"Status", Opcode::Status}, {"Notify", Opcode::Notify}, {"Update", Opcode::Update}});
+
+ luaCtx.writeVariable("DNSSection", LuaAssociativeTable<int>{{"Question", 0}, {"Answer", 1}, {"Authority", 2}, {"Additional", 3}});
+
+ luaCtx.writeVariable("EDNSOptionCode", LuaAssociativeTable<int>{{"NSID", EDNSOptionCode::NSID}, {"DAU", EDNSOptionCode::DAU}, {"DHU", EDNSOptionCode::DHU}, {"N3U", EDNSOptionCode::N3U}, {"ECS", EDNSOptionCode::ECS}, {"EXPIRE", EDNSOptionCode::EXPIRE}, {"COOKIE", EDNSOptionCode::COOKIE}, {"TCPKEEPALIVE", EDNSOptionCode::TCPKEEPALIVE}, {"PADDING", EDNSOptionCode::PADDING}, {"CHAIN", EDNSOptionCode::CHAIN}, {"KEYTAG", EDNSOptionCode::KEYTAG}});
+
+ luaCtx.writeVariable("DNSRCode", LuaAssociativeTable<int>{{"NOERROR", RCode::NoError}, {"FORMERR", RCode::FormErr}, {"SERVFAIL", RCode::ServFail}, {"NXDOMAIN", RCode::NXDomain}, {"NOTIMP", RCode::NotImp}, {"REFUSED", RCode::Refused}, {"YXDOMAIN", RCode::YXDomain}, {"YXRRSET", RCode::YXRRSet}, {"NXRRSET", RCode::NXRRSet}, {"NOTAUTH", RCode::NotAuth}, {"NOTZONE", RCode::NotZone}, {"BADVERS", ERCode::BADVERS}, {"BADSIG", ERCode::BADSIG}, {"BADKEY", ERCode::BADKEY}, {"BADTIME", ERCode::BADTIME}, {"BADMODE", ERCode::BADMODE}, {"BADNAME", ERCode::BADNAME}, {"BADALG", ERCode::BADALG}, {"BADTRUNC", ERCode::BADTRUNC}, {"BADCOOKIE", ERCode::BADCOOKIE}});
+
+ LuaAssociativeTable<int> dnsqtypes;
+ for (const auto& name : QType::names) {
+ dnsqtypes[name.first] = name.second;
+ }
+ luaCtx.writeVariable("DNSQType", dnsqtypes);
+
+#ifdef HAVE_DNSCRYPT
+ luaCtx.writeVariable("DNSCryptExchangeVersion", LuaAssociativeTable<int>{
+ {"VERSION1", DNSCryptExchangeVersion::VERSION1},
+ {"VERSION2", DNSCryptExchangeVersion::VERSION2},
+ });
+#endif
+}
+++ /dev/null
-../dnsdist-lua.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cstdint>
+#include <cstdio>
+#include <dirent.h>
+#include <fstream>
+#include <cinttypes>
+
+// for OpenBSD, sys/socket.h needs to come before net/if.h
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <regex>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <thread>
+#include <vector>
+
+#include "dnsdist.hh"
+#include "dnsdist-carbon.hh"
+#include "dnsdist-concurrent-connections.hh"
+#include "dnsdist-console.hh"
+#include "dnsdist-crypto.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-discovery.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-healthchecks.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-lua-hooks.hh"
+#include "xsk.hh"
+#ifdef LUAJIT_VERSION
+#include "dnsdist-lua-ffi.hh"
+#endif /* LUAJIT_VERSION */
+#include "dnsdist-metrics.hh"
+#include "dnsdist-nghttp2.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-secpoll.hh"
+#include "dnsdist-session-cache.hh"
+#include "dnsdist-tcp-downstream.hh"
+#include "dnsdist-web.hh"
+
+#include "base64.hh"
+#include "coverage.hh"
+#include "doh.hh"
+#include "doq-common.hh"
+#include "dolog.hh"
+#include "threadname.hh"
+
+#ifdef HAVE_LIBSSL
+#include "libssl.hh"
+#endif
+
+#include <boost/logic/tribool.hpp>
+#include <boost/uuid/string_generator.hpp>
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+using std::thread;
+
+static boost::optional<std::vector<std::function<void(void)>>> g_launchWork = boost::none;
+
+boost::tribool g_noLuaSideEffect;
+static bool g_included{false};
+
+/* this is a best effort way to prevent logging calls with no side-effects in the output of delta()
+ Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing
+ has done so before on this invocation, this call won't be part of delta() output */
+void setLuaNoSideEffect()
+{
+ if (g_noLuaSideEffect == false) {
+ // there has been a side effect already
+ return;
+ }
+ g_noLuaSideEffect = true;
+}
+
+void setLuaSideEffect()
+{
+ g_noLuaSideEffect = false;
+}
+
+bool getLuaNoSideEffect()
+{
+ if (g_noLuaSideEffect) {
+ // NOLINTNEXTLINE(readability-simplify-boolean-expr): it's a tribool, not a boolean
+ return true;
+ }
+ return false;
+}
+
+void resetLuaSideEffect()
+{
+ g_noLuaSideEffect = boost::logic::indeterminate;
+}
+
+using localbind_t = LuaAssociativeTable<boost::variant<bool, int, std::string, LuaArray<int>, LuaArray<std::string>, LuaAssociativeTable<std::string>, std::shared_ptr<XskSocket>>>;
+
+static void parseLocalBindVars(boost::optional<localbind_t>& vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus, int& tcpListenQueueSize, uint64_t& maxInFlightQueriesPerConnection, uint64_t& tcpMaxConcurrentConnections, bool& enableProxyProtocol)
+{
+ if (vars) {
+ LuaArray<int> setCpus;
+
+ getOptionalValue<bool>(vars, "reusePort", reusePort);
+ getOptionalValue<bool>(vars, "enableProxyProtocol", enableProxyProtocol);
+ getOptionalValue<int>(vars, "tcpFastOpenQueueSize", tcpFastOpenQueueSize);
+ getOptionalValue<int>(vars, "tcpListenQueueSize", tcpListenQueueSize);
+ getOptionalValue<int>(vars, "maxConcurrentTCPConnections", tcpMaxConcurrentConnections);
+ getOptionalValue<int>(vars, "maxInFlight", maxInFlightQueriesPerConnection);
+ getOptionalValue<std::string>(vars, "interface", interface);
+ if (getOptionalValue<decltype(setCpus)>(vars, "cpus", setCpus) > 0) {
+ for (const auto& cpu : setCpus) {
+ cpus.insert(cpu.second);
+ }
+ }
+ }
+}
+#ifdef HAVE_XSK
+static void parseXskVars(boost::optional<localbind_t>& vars, std::shared_ptr<XskSocket>& socket)
+{
+ if (!vars) {
+ return;
+ }
+
+ getOptionalValue<std::shared_ptr<XskSocket>>(vars, "xskSocket", socket);
+}
+#endif /* HAVE_XSK */
+
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) || defined(HAVE_DNS_OVER_QUIC)
+static bool loadTLSCertificateAndKeys(const std::string& context, std::vector<TLSCertKeyPair>& pairs, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const LuaTypeOrArrayOf<std::string>& keyFiles)
+{
+ if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
+ auto certFile = boost::get<std::string>(certFiles);
+ auto keyFile = boost::get<std::string>(keyFiles);
+ pairs.clear();
+ pairs.emplace_back(certFile, keyFile);
+ }
+ else if (certFiles.type() == typeid(std::shared_ptr<TLSCertKeyPair>)) {
+ auto cert = boost::get<std::shared_ptr<TLSCertKeyPair>>(certFiles);
+ pairs.clear();
+ pairs.emplace_back(*cert);
+ }
+ else if (certFiles.type() == typeid(LuaArray<std::shared_ptr<TLSCertKeyPair>>)) {
+ auto certs = boost::get<LuaArray<std::shared_ptr<TLSCertKeyPair>>>(certFiles);
+ pairs.clear();
+ for (const auto& cert : certs) {
+ pairs.emplace_back(*(cert.second));
+ }
+ }
+ else if (certFiles.type() == typeid(LuaArray<std::string>) && keyFiles.type() == typeid(LuaArray<std::string>)) {
+ auto certFilesVect = boost::get<LuaArray<std::string>>(certFiles);
+ auto keyFilesVect = boost::get<LuaArray<std::string>>(keyFiles);
+ if (certFilesVect.size() == keyFilesVect.size()) {
+ pairs.clear();
+ for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
+ pairs.emplace_back(certFilesVect.at(idx).second, keyFilesVect.at(idx).second);
+ }
+ }
+ else {
+ errlog("Error, mismatching number of certificates and keys in call to %s()!", context);
+ g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!";
+ return false;
+ }
+ }
+ else {
+ errlog("Error, mismatching number of certificates and keys in call to %s()!", context);
+ g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!";
+ return false;
+ }
+
+ return true;
+}
+
+static void parseTLSConfig(TLSConfig& config, const std::string& context, boost::optional<localbind_t>& vars)
+{
+ getOptionalValue<std::string>(vars, "ciphers", config.d_ciphers);
+ getOptionalValue<std::string>(vars, "ciphersTLS13", config.d_ciphers13);
+
+#ifdef HAVE_LIBSSL
+ std::string minVersion;
+ if (getOptionalValue<std::string>(vars, "minTLSVersion", minVersion) > 0) {
+ config.d_minTLSVersion = libssl_tls_version_from_string(minVersion);
+ }
+#else /* HAVE_LIBSSL */
+ if (vars->erase("minTLSVersion") > 0)
+ warnlog("minTLSVersion has no effect with chosen TLS library");
+#endif /* HAVE_LIBSSL */
+
+ getOptionalValue<std::string>(vars, "ticketKeyFile", config.d_ticketKeyFile);
+ getOptionalValue<int>(vars, "ticketsKeysRotationDelay", config.d_ticketsKeyRotationDelay);
+ getOptionalValue<int>(vars, "numberOfTicketsKeys", config.d_numberOfTicketsKeys);
+ getOptionalValue<bool>(vars, "preferServerCiphers", config.d_preferServerCiphers);
+ getOptionalValue<int>(vars, "sessionTimeout", config.d_sessionTimeout);
+ getOptionalValue<bool>(vars, "sessionTickets", config.d_enableTickets);
+ int numberOfStoredSessions{0};
+ if (getOptionalValue<int>(vars, "numberOfStoredSessions", numberOfStoredSessions) > 0) {
+ if (numberOfStoredSessions < 0) {
+ errlog("Invalid value '%d' for %s() parameter 'numberOfStoredSessions', should be >= 0, dismissing", numberOfStoredSessions, context);
+ g_outputBuffer = "Invalid value '" + std::to_string(numberOfStoredSessions) + "' for " + context + "() parameter 'numberOfStoredSessions', should be >= 0, dimissing";
+ }
+ else {
+ config.d_maxStoredSessions = numberOfStoredSessions;
+ }
+ }
+
+ LuaArray<std::string> files;
+ if (getOptionalValue<decltype(files)>(vars, "ocspResponses", files) > 0) {
+ for (const auto& file : files) {
+ config.d_ocspFiles.push_back(file.second);
+ }
+ }
+
+ if (vars->count("keyLogFile") > 0) {
+#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
+ getOptionalValue<std::string>(vars, "keyLogFile", config.d_keyLogFile);
+#else
+ errlog("TLS Key logging has been enabled using the 'keyLogFile' parameter to %s(), but this version of OpenSSL does not support it", context);
+ g_outputBuffer = "TLS Key logging has been enabled using the 'keyLogFile' parameter to " + context + "(), but this version of OpenSSL does not support it";
+#endif
+ }
+
+ getOptionalValue<bool>(vars, "releaseBuffers", config.d_releaseBuffers);
+ getOptionalValue<bool>(vars, "enableRenegotiation", config.d_enableRenegotiation);
+ getOptionalValue<bool>(vars, "tlsAsyncMode", config.d_asyncMode);
+ getOptionalValue<bool>(vars, "ktls", config.d_ktls);
+ getOptionalValue<bool>(vars, "readAhead", config.d_readAhead);
+}
+
+#endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
+
+void checkParameterBound(const std::string& parameter, uint64_t value, size_t max)
+{
+ if (value > max) {
+ throw std::runtime_error("The value (" + std::to_string(value) + ") passed to " + parameter + " is too large, the maximum is " + std::to_string(max));
+ }
+}
+
+static void LuaThread(const std::string& code)
+{
+ setThreadName("dnsdist/lua-bg");
+ LuaContext context;
+
+ // mask SIGTERM on threads so the signal always comes to dnsdist itself
+ sigset_t blockSignals;
+
+ sigemptyset(&blockSignals);
+ sigaddset(&blockSignals, SIGTERM);
+
+ pthread_sigmask(SIG_BLOCK, &blockSignals, nullptr);
+
+ // submitToMainThread is camelcased, threadmessage is not.
+ // This follows our tradition of hooks we call being lowercased but functions the user can call being camelcased.
+ context.writeFunction("submitToMainThread", [](std::string cmd, LuaAssociativeTable<std::string> data) {
+ auto lua = g_lua.lock();
+ // maybe offer more than `void`
+ auto func = lua->readVariable<boost::optional<std::function<void(std::string cmd, LuaAssociativeTable<std::string> data)>>>("threadmessage");
+ if (func) {
+ func.get()(std::move(cmd), std::move(data));
+ }
+ else {
+ errlog("Lua thread called submitToMainThread but no threadmessage receiver is defined");
+ }
+ });
+
+ // function threadmessage(cmd, data) print("got thread data:", cmd) for k,v in pairs(data) do print(k,v) end end
+
+ for (;;) {
+ try {
+ context.executeCode(code);
+ errlog("Lua thread exited, restarting in 5 seconds");
+ }
+ catch (const std::exception& e) {
+ errlog("Lua thread crashed, restarting in 5 seconds: %s", e.what());
+ }
+ catch (...) {
+ errlog("Lua thread crashed, restarting in 5 seconds");
+ }
+ std::this_thread::sleep_for(std::chrono::seconds(5));
+ }
+}
+
+static bool checkConfigurationTime(const std::string& name)
+{
+ if (!g_configurationDone) {
+ return true;
+ }
+ g_outputBuffer = name + " cannot be used at runtime!\n";
+ errlog("%s cannot be used at runtime!", name);
+ return false;
+}
+
+using newserver_t = LuaAssociativeTable<boost::variant<bool, std::string, LuaArray<std::string>, LuaArray<std::shared_ptr<XskSocket>>, DownstreamState::checkfunc_t>>;
+
+static void handleNewServerHealthCheckParameters(boost::optional<newserver_t>& vars, DownstreamState::Config& config)
+{
+ std::string valueStr;
+
+ if (getOptionalValue<std::string>(vars, "checkInterval", valueStr) > 0) {
+ config.checkInterval = static_cast<unsigned int>(std::stoul(valueStr));
+ }
+
+ if (getOptionalValue<std::string>(vars, "healthCheckMode", valueStr) > 0) {
+ const auto& mode = valueStr;
+ if (pdns_iequals(mode, "auto")) {
+ config.availability = DownstreamState::Availability::Auto;
+ }
+ else if (pdns_iequals(mode, "lazy")) {
+ config.availability = DownstreamState::Availability::Lazy;
+ }
+ else if (pdns_iequals(mode, "up")) {
+ config.availability = DownstreamState::Availability::Up;
+ }
+ else if (pdns_iequals(mode, "down")) {
+ config.availability = DownstreamState::Availability::Down;
+ }
+ else {
+ warnlog("Ignoring unknown value '%s' for 'healthCheckMode' on 'newServer'", mode);
+ }
+ }
+
+ if (getOptionalValue<std::string>(vars, "checkName", valueStr) > 0) {
+ config.checkName = DNSName(valueStr);
+ }
+
+ getOptionalValue<std::string>(vars, "checkType", config.checkType);
+ getOptionalIntegerValue("newServer", vars, "checkClass", config.checkClass);
+ getOptionalValue<DownstreamState::checkfunc_t>(vars, "checkFunction", config.checkFunction);
+ getOptionalIntegerValue("newServer", vars, "checkTimeout", config.checkTimeout);
+ getOptionalValue<bool>(vars, "checkTCP", config.d_tcpCheck);
+ getOptionalValue<bool>(vars, "setCD", config.setCD);
+ getOptionalValue<bool>(vars, "mustResolve", config.mustResolve);
+
+ if (getOptionalValue<std::string>(vars, "lazyHealthCheckSampleSize", valueStr) > 0) {
+ const auto& value = std::stoi(valueStr);
+ checkParameterBound("lazyHealthCheckSampleSize", value);
+ config.d_lazyHealthCheckSampleSize = value;
+ }
+
+ if (getOptionalValue<std::string>(vars, "lazyHealthCheckMinSampleCount", valueStr) > 0) {
+ const auto& value = std::stoi(valueStr);
+ checkParameterBound("lazyHealthCheckMinSampleCount", value);
+ config.d_lazyHealthCheckMinSampleCount = value;
+ }
+
+ if (getOptionalValue<std::string>(vars, "lazyHealthCheckThreshold", valueStr) > 0) {
+ const auto& value = std::stoi(valueStr);
+ checkParameterBound("lazyHealthCheckThreshold", value, std::numeric_limits<uint8_t>::max());
+ config.d_lazyHealthCheckThreshold = value;
+ }
+
+ if (getOptionalValue<std::string>(vars, "lazyHealthCheckFailedInterval", valueStr) > 0) {
+ const auto& value = std::stoi(valueStr);
+ checkParameterBound("lazyHealthCheckFailedInterval", value);
+ config.d_lazyHealthCheckFailedInterval = value;
+ }
+
+ getOptionalValue<bool>(vars, "lazyHealthCheckUseExponentialBackOff", config.d_lazyHealthCheckUseExponentialBackOff);
+
+ if (getOptionalValue<std::string>(vars, "lazyHealthCheckMaxBackOff", valueStr) > 0) {
+ const auto& value = std::stoi(valueStr);
+ checkParameterBound("lazyHealthCheckMaxBackOff", value);
+ config.d_lazyHealthCheckMaxBackOff = value;
+ }
+
+ if (getOptionalValue<std::string>(vars, "lazyHealthCheckMode", valueStr) > 0) {
+ const auto& mode = valueStr;
+ if (pdns_iequals(mode, "TimeoutOnly")) {
+ config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOnly;
+ }
+ else if (pdns_iequals(mode, "TimeoutOrServFail")) {
+ config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOrServFail;
+ }
+ else {
+ warnlog("Ignoring unknown value '%s' for 'lazyHealthCheckMode' on 'newServer'", mode);
+ }
+ }
+
+ getOptionalValue<bool>(vars, "lazyHealthCheckWhenUpgraded", config.d_upgradeToLazyHealthChecks);
+
+ getOptionalIntegerValue("newServer", vars, "maxCheckFailures", config.maxCheckFailures);
+ getOptionalIntegerValue("newServer", vars, "rise", config.minRiseSuccesses);
+}
+
+static void handleNewServerSourceParameter(boost::optional<newserver_t>& vars, DownstreamState::Config& config)
+{
+ std::string source;
+ if (getOptionalValue<std::string>(vars, "source", source) > 0) {
+ /* handle source in the following forms:
+ - v4 address ("192.0.2.1")
+ - v6 address ("2001:DB8::1")
+ - interface name ("eth0")
+ - v4 address and interface name ("192.0.2.1@eth0")
+ - v6 address and interface name ("2001:DB8::1@eth0")
+ */
+ bool parsed = false;
+ std::string::size_type pos = source.find('@');
+ if (pos == std::string::npos) {
+ /* no '@', try to parse that as a valid v4/v6 address */
+ try {
+ config.sourceAddr = ComboAddress(source);
+ parsed = true;
+ }
+ catch (...) {
+ }
+ }
+
+ if (!parsed) {
+ /* try to parse as interface name, or v4/v6@itf */
+ config.sourceItfName = source.substr(pos == std::string::npos ? 0 : pos + 1);
+ unsigned int itfIdx = if_nametoindex(config.sourceItfName.c_str());
+ if (itfIdx != 0) {
+ if (pos == 0 || pos == std::string::npos) {
+ /* "eth0" or "@eth0" */
+ config.sourceItf = itfIdx;
+ }
+ else {
+ /* "192.0.2.1@eth0" */
+ config.sourceAddr = ComboAddress(source.substr(0, pos));
+ config.sourceItf = itfIdx;
+ }
+#ifdef SO_BINDTODEVICE
+ /* we need to retain CAP_NET_RAW to be able to set SO_BINDTODEVICE in the health checks */
+ g_capabilitiesToRetain.insert("CAP_NET_RAW");
+#endif
+ }
+ else {
+ warnlog("Dismissing source %s because '%s' is not a valid interface name", source, config.sourceItfName);
+ }
+ }
+ }
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity,readability-function-size): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
+{
+ luaCtx.writeFunction("inClientStartup", [client]() {
+ return client && !g_configurationDone;
+ });
+
+ luaCtx.writeFunction("inConfigCheck", [configCheck]() {
+ return configCheck;
+ });
+
+ luaCtx.writeFunction("newServer",
+ [client, configCheck](boost::variant<string, newserver_t> pvars, boost::optional<int> qps) {
+ setLuaSideEffect();
+
+ boost::optional<newserver_t> vars = newserver_t();
+ DownstreamState::Config config;
+
+ std::string serverAddressStr;
+ if (auto* addrStr = boost::get<string>(&pvars)) {
+ serverAddressStr = *addrStr;
+ if (qps) {
+ (*vars)["qps"] = std::to_string(*qps);
+ }
+ }
+ else {
+ vars = boost::get<newserver_t>(pvars);
+ getOptionalValue<std::string>(vars, "address", serverAddressStr);
+ }
+
+ handleNewServerSourceParameter(vars, config);
+
+ std::string valueStr;
+ if (getOptionalValue<std::string>(vars, "sockets", valueStr) > 0) {
+ config.d_numberOfSockets = std::stoul(valueStr);
+ if (config.d_numberOfSockets == 0) {
+ warnlog("Dismissing invalid number of sockets '%s', using 1 instead", valueStr);
+ config.d_numberOfSockets = 1;
+ }
+ }
+
+ getOptionalIntegerValue("newServer", vars, "qps", config.d_qpsLimit);
+ getOptionalIntegerValue("newServer", vars, "order", config.order);
+ getOptionalIntegerValue("newServer", vars, "weight", config.d_weight);
+ if (config.d_weight < 1) {
+ errlog("Error creating new server: downstream weight value must be greater than 0.");
+ return std::shared_ptr<DownstreamState>();
+ }
+
+ getOptionalIntegerValue("newServer", vars, "retries", config.d_retries);
+ getOptionalIntegerValue("newServer", vars, "tcpConnectTimeout", config.tcpConnectTimeout);
+ getOptionalIntegerValue("newServer", vars, "tcpSendTimeout", config.tcpSendTimeout);
+ getOptionalIntegerValue("newServer", vars, "tcpRecvTimeout", config.tcpRecvTimeout);
+
+ handleNewServerHealthCheckParameters(vars, config);
+
+ bool fastOpen{false};
+ if (getOptionalValue<bool>(vars, "tcpFastOpen", fastOpen) > 0) {
+ if (fastOpen) {
+#ifdef MSG_FASTOPEN
+ config.tcpFastOpen = true;
+#else
+ warnlog("TCP Fast Open has been configured on downstream server %s but is not supported", serverAddressStr);
+#endif
+ }
+ }
+
+ getOptionalIntegerValue("newServer", vars, "maxInFlight", config.d_maxInFlightQueriesPerConn);
+ getOptionalIntegerValue("newServer", vars, "maxConcurrentTCPConnections", config.d_tcpConcurrentConnectionsLimit);
+
+ getOptionalValue<std::string>(vars, "name", config.name);
+
+ if (getOptionalValue<std::string>(vars, "id", valueStr) > 0) {
+ config.id = boost::uuids::string_generator()(valueStr);
+ }
+
+ getOptionalValue<bool>(vars, "useClientSubnet", config.useECS);
+ getOptionalValue<bool>(vars, "useProxyProtocol", config.useProxyProtocol);
+ getOptionalValue<bool>(vars, "proxyProtocolAdvertiseTLS", config.d_proxyProtocolAdvertiseTLS);
+ getOptionalValue<bool>(vars, "disableZeroScope", config.disableZeroScope);
+ getOptionalValue<bool>(vars, "ipBindAddrNoPort", config.ipBindAddrNoPort);
+
+ getOptionalIntegerValue("newServer", vars, "addXPF", config.xpfRRCode);
+
+ getOptionalValue<bool>(vars, "reconnectOnUp", config.reconnectOnUp);
+
+ LuaArray<string> cpuMap;
+ if (getOptionalValue<decltype(cpuMap)>(vars, "cpus", cpuMap) > 0) {
+ for (const auto& cpu : cpuMap) {
+ config.d_cpus.insert(std::stoi(cpu.second));
+ }
+ }
+
+ getOptionalValue<bool>(vars, "tcpOnly", config.d_tcpOnly);
+
+ std::shared_ptr<TLSCtx> tlsCtx;
+ getOptionalValue<std::string>(vars, "ciphers", config.d_tlsParams.d_ciphers);
+ getOptionalValue<std::string>(vars, "ciphers13", config.d_tlsParams.d_ciphers13);
+ getOptionalValue<std::string>(vars, "caStore", config.d_tlsParams.d_caStore);
+ getOptionalValue<bool>(vars, "validateCertificates", config.d_tlsParams.d_validateCertificates);
+ getOptionalValue<bool>(vars, "releaseBuffers", config.d_tlsParams.d_releaseBuffers);
+ getOptionalValue<bool>(vars, "enableRenegotiation", config.d_tlsParams.d_enableRenegotiation);
+ getOptionalValue<bool>(vars, "ktls", config.d_tlsParams.d_ktls);
+ getOptionalValue<std::string>(vars, "subjectName", config.d_tlsSubjectName);
+
+ if (getOptionalValue<std::string>(vars, "subjectAddr", valueStr) > 0) {
+ try {
+ ComboAddress addr(valueStr);
+ config.d_tlsSubjectName = addr.toString();
+ config.d_tlsSubjectIsAddr = true;
+ }
+ catch (const std::exception&) {
+ errlog("Error creating new server: downstream subjectAddr value must be a valid IP address");
+ return std::shared_ptr<DownstreamState>();
+ }
+ }
+
+ uint16_t serverPort = 53;
+
+ if (getOptionalValue<std::string>(vars, "tls", valueStr) > 0) {
+ serverPort = 853;
+ config.d_tlsParams.d_provider = valueStr;
+ tlsCtx = getTLSContext(config.d_tlsParams);
+
+ if (getOptionalValue<std::string>(vars, "dohPath", valueStr) > 0) {
+#if !defined(HAVE_DNS_OVER_HTTPS) || !defined(HAVE_NGHTTP2)
+ throw std::runtime_error("Outgoing DNS over HTTPS support requested (via 'dohPath' on newServer()) but it is not available");
+#endif
+
+ serverPort = 443;
+ config.d_dohPath = valueStr;
+
+ getOptionalValue<bool>(vars, "addXForwardedHeaders", config.d_addXForwardedHeaders);
+ }
+ }
+
+ try {
+ config.remote = ComboAddress(serverAddressStr, serverPort);
+ }
+ catch (const PDNSException& e) {
+ g_outputBuffer = "Error creating new server: " + string(e.reason);
+ errlog("Error creating new server with address %s: %s", serverAddressStr, e.reason);
+ return std::shared_ptr<DownstreamState>();
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Error creating new server: " + string(e.what());
+ errlog("Error creating new server with address %s: %s", serverAddressStr, e.what());
+ return std::shared_ptr<DownstreamState>();
+ }
+
+ if (IsAnyAddress(config.remote)) {
+ g_outputBuffer = "Error creating new server: invalid address for a downstream server.";
+ errlog("Error creating new server: %s is not a valid address for a downstream server", serverAddressStr);
+ return std::shared_ptr<DownstreamState>();
+ }
+
+ LuaArray<std::string> pools;
+ if (getOptionalValue<std::string>(vars, "pool", valueStr, false) > 0) {
+ config.pools.insert(valueStr);
+ }
+ else if (getOptionalValue<decltype(pools)>(vars, "pool", pools) > 0) {
+ for (auto& pool : pools) {
+ config.pools.insert(pool.second);
+ }
+ }
+
+ bool autoUpgrade = false;
+ bool keepAfterUpgrade = false;
+ uint32_t upgradeInterval = 3600;
+ uint16_t upgradeDoHKey = dnsdist::ServiceDiscovery::s_defaultDoHSVCKey;
+ std::string upgradePool;
+
+ getOptionalValue<bool>(vars, "autoUpgrade", autoUpgrade);
+ if (autoUpgrade) {
+ if (getOptionalValue<std::string>(vars, "autoUpgradeInterval", valueStr) > 0) {
+ try {
+ upgradeInterval = static_cast<uint32_t>(std::stoul(valueStr));
+ }
+ catch (const std::exception& e) {
+ warnlog("Error parsing 'autoUpgradeInterval' value: %s", e.what());
+ }
+ }
+ getOptionalValue<bool>(vars, "autoUpgradeKeep", keepAfterUpgrade);
+ getOptionalValue<std::string>(vars, "autoUpgradePool", upgradePool);
+ if (getOptionalValue<std::string>(vars, "autoUpgradeDoHKey", valueStr) > 0) {
+ try {
+ upgradeDoHKey = static_cast<uint16_t>(std::stoul(valueStr));
+ }
+ catch (const std::exception& e) {
+ warnlog("Error parsing 'autoUpgradeDoHKey' value: %s", e.what());
+ }
+ }
+ }
+
+ // create but don't connect the socket in client or check-config modes
+ auto ret = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), !(client || configCheck));
+#ifdef HAVE_XSK
+ LuaArray<std::shared_ptr<XskSocket>> luaXskSockets;
+ if (getOptionalValue<LuaArray<std::shared_ptr<XskSocket>>>(vars, "xskSockets", luaXskSockets) > 0 && !luaXskSockets.empty()) {
+ if (g_configurationDone) {
+ throw std::runtime_error("Adding a server with xsk at runtime is not supported");
+ }
+ std::vector<std::shared_ptr<XskSocket>> xskSockets;
+ for (auto& socket : luaXskSockets) {
+ xskSockets.push_back(socket.second);
+ }
+ ret->registerXsk(xskSockets);
+ std::string mac;
+ if (getOptionalValue<std::string>(vars, "MACAddr", mac) > 0) {
+ auto* addr = ret->d_config.destMACAddr.data();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ sscanf(mac.c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", addr, addr + 1, addr + 2, addr + 3, addr + 4, addr + 5);
+ }
+ else {
+ mac = getMACAddress(ret->d_config.remote);
+ if (mac.size() != ret->d_config.destMACAddr.size()) {
+ throw runtime_error("Field 'MACAddr' is not set on 'newServer' directive for '" + ret->d_config.remote.toStringWithPort() + "' and cannot be retrieved from the system either!");
+ }
+ memcpy(ret->d_config.destMACAddr.data(), mac.data(), ret->d_config.destMACAddr.size());
+ }
+ infolog("Added downstream server %s via XSK in %s mode", ret->d_config.remote.toStringWithPort(), xskSockets.at(0)->getXDPMode());
+ }
+ else if (!(client || configCheck)) {
+ infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort());
+ }
+#else /* HAVE_XSK */
+ if (!(client || configCheck)) {
+ infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort());
+ }
+#endif /* HAVE_XSK */
+ if (autoUpgrade && ret->getProtocol() != dnsdist::Protocol::DoT && ret->getProtocol() != dnsdist::Protocol::DoH) {
+ dnsdist::ServiceDiscovery::addUpgradeableServer(ret, upgradeInterval, upgradePool, upgradeDoHKey, keepAfterUpgrade);
+ }
+
+ /* this needs to be done _AFTER_ the order has been set,
+ since the server are kept ordered inside the pool */
+ auto localPools = g_pools.getCopy();
+ if (!ret->d_config.pools.empty()) {
+ for (const auto& poolName : ret->d_config.pools) {
+ addServerToPool(localPools, poolName, ret);
+ }
+ }
+ else {
+ addServerToPool(localPools, "", ret);
+ }
+ g_pools.setState(localPools);
+
+ if (ret->connected) {
+ if (g_launchWork) {
+ g_launchWork->push_back([ret]() {
+ ret->start();
+ });
+ }
+ else {
+ ret->start();
+ }
+ }
+
+ auto states = g_dstates.getCopy();
+ states.push_back(ret);
+ std::stable_sort(states.begin(), states.end(), [](const decltype(ret)& lhs, const decltype(ret)& rhs) {
+ return lhs->d_config.order < rhs->d_config.order;
+ });
+ g_dstates.setState(states);
+ checkAllParametersConsumed("newServer", vars);
+ return ret;
+ });
+
+ luaCtx.writeFunction("rmServer",
+ [](boost::variant<std::shared_ptr<DownstreamState>, int, std::string> var) {
+ setLuaSideEffect();
+ shared_ptr<DownstreamState> server = nullptr;
+ auto states = g_dstates.getCopy();
+ if (auto* rem = boost::get<shared_ptr<DownstreamState>>(&var)) {
+ server = *rem;
+ }
+ else if (auto* str = boost::get<std::string>(&var)) {
+ const auto uuid = getUniqueID(*str);
+ for (auto& state : states) {
+ if (*state->d_config.id == uuid) {
+ server = state;
+ }
+ }
+ }
+ else {
+ int idx = boost::get<int>(var);
+ server = states.at(idx);
+ }
+ if (!server) {
+ throw std::runtime_error("unable to locate the requested server");
+ }
+ auto localPools = g_pools.getCopy();
+ for (const string& poolName : server->d_config.pools) {
+ removeServerFromPool(localPools, poolName, server);
+ }
+ /* the server might also be in the default pool */
+ removeServerFromPool(localPools, "", server);
+ g_pools.setState(localPools);
+ states.erase(remove(states.begin(), states.end(), server), states.end());
+ g_dstates.setState(states);
+ server->stop();
+ });
+
+ luaCtx.writeFunction("truncateTC", [](bool value) { setLuaSideEffect(); g_truncateTC = value; });
+ luaCtx.writeFunction("fixupCase", [](bool value) { setLuaSideEffect(); g_fixupCase = value; });
+
+ luaCtx.writeFunction("addACL", [](const std::string& domain) {
+ setLuaSideEffect();
+ g_ACL.modify([domain](NetmaskGroup& nmg) { nmg.addMask(domain); });
+ });
+
+ luaCtx.writeFunction("rmACL", [](const std::string& netmask) {
+ setLuaSideEffect();
+ g_ACL.modify([netmask](NetmaskGroup& nmg) { nmg.deleteMask(netmask); });
+ });
+
+ luaCtx.writeFunction("setLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
+ setLuaSideEffect();
+ if (client) {
+ return;
+ }
+
+ if (!checkConfigurationTime("setLocal")) {
+ return;
+ }
+
+ bool reusePort = false;
+ int tcpFastOpenQueueSize = 0;
+ int tcpListenQueueSize = 0;
+ uint64_t maxInFlightQueriesPerConn = 0;
+ uint64_t tcpMaxConcurrentConnections = 0;
+ std::string interface;
+ std::set<int> cpus;
+ bool enableProxyProtocol = true;
+
+ parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+
+ try {
+ ComboAddress loc(addr, 53);
+ for (auto it = g_frontends.begin(); it != g_frontends.end();) {
+ /* DoH, DoT and DNSCrypt frontends are separate */
+ if ((*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->dohFrontend == nullptr) {
+ it = g_frontends.erase(it);
+ }
+ else {
+ ++it;
+ }
+ }
+
+ // only works pre-startup, so no sync necessary
+ auto udpCS = std::make_unique<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ auto tcpCS = std::make_unique<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ if (tcpListenQueueSize > 0) {
+ tcpCS->tcpListenQueueSize = tcpListenQueueSize;
+ }
+ if (maxInFlightQueriesPerConn > 0) {
+ tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+ }
+ if (tcpMaxConcurrentConnections > 0) {
+ tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+ }
+
+#ifdef HAVE_XSK
+ std::shared_ptr<XskSocket> socket;
+ parseXskVars(vars, socket);
+ if (socket) {
+ udpCS->xskInfo = XskWorker::create();
+ udpCS->xskInfo->sharedEmptyFrameOffset = socket->sharedEmptyFrameOffset;
+ socket->addWorker(udpCS->xskInfo);
+ socket->addWorkerRoute(udpCS->xskInfo, loc);
+ vinfolog("Enabling XSK in %s mode for incoming UDP packets to %s", socket->getXDPMode(), loc.toStringWithPort());
+ }
+#endif /* HAVE_XSK */
+ g_frontends.push_back(std::move(udpCS));
+ g_frontends.push_back(std::move(tcpCS));
+
+ checkAllParametersConsumed("setLocal", vars);
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Error: " + string(e.what()) + "\n";
+ }
+ });
+
+ luaCtx.writeFunction("addLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
+ setLuaSideEffect();
+ if (client) {
+ return;
+ }
+
+ if (!checkConfigurationTime("addLocal")) {
+ return;
+ }
+ bool reusePort = false;
+ int tcpFastOpenQueueSize = 0;
+ int tcpListenQueueSize = 0;
+ uint64_t maxInFlightQueriesPerConn = 0;
+ uint64_t tcpMaxConcurrentConnections = 0;
+ std::string interface;
+ std::set<int> cpus;
+ bool enableProxyProtocol = true;
+
+ parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+
+ try {
+ ComboAddress loc(addr, 53);
+ // only works pre-startup, so no sync necessary
+ auto udpCS = std::make_unique<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ auto tcpCS = std::make_unique<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ if (tcpListenQueueSize > 0) {
+ tcpCS->tcpListenQueueSize = tcpListenQueueSize;
+ }
+ if (maxInFlightQueriesPerConn > 0) {
+ tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+ }
+ if (tcpMaxConcurrentConnections > 0) {
+ tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+ }
+#ifdef HAVE_XSK
+ std::shared_ptr<XskSocket> socket;
+ parseXskVars(vars, socket);
+ if (socket) {
+ udpCS->xskInfo = XskWorker::create();
+ udpCS->xskInfo->sharedEmptyFrameOffset = socket->sharedEmptyFrameOffset;
+ socket->addWorker(udpCS->xskInfo);
+ socket->addWorkerRoute(udpCS->xskInfo, loc);
+ vinfolog("Enabling XSK in %s mode for incoming UDP packets to %s", socket->getXDPMode(), loc.toStringWithPort());
+ }
+#endif /* HAVE_XSK */
+ g_frontends.push_back(std::move(udpCS));
+ g_frontends.push_back(std::move(tcpCS));
+
+ checkAllParametersConsumed("addLocal", vars);
+ }
+ catch (std::exception& e) {
+ g_outputBuffer = "Error: " + string(e.what()) + "\n";
+ errlog("Error while trying to listen on %s: %s\n", addr, string(e.what()));
+ }
+ });
+
+ luaCtx.writeFunction("setACL", [](LuaTypeOrArrayOf<std::string> inp) {
+ setLuaSideEffect();
+ NetmaskGroup nmg;
+ if (auto* str = boost::get<string>(&inp)) {
+ nmg.addMask(*str);
+ }
+ else {
+ for (const auto& entry : boost::get<LuaArray<std::string>>(inp)) {
+ nmg.addMask(entry.second);
+ }
+ }
+ g_ACL.setState(nmg);
+ });
+
+ luaCtx.writeFunction("setACLFromFile", [](const std::string& file) {
+ setLuaSideEffect();
+ NetmaskGroup nmg;
+
+ ifstream ifs(file);
+ if (!ifs) {
+ throw std::runtime_error("Could not open '" + file + "': " + stringerror());
+ }
+
+ string::size_type pos = 0;
+ string line;
+ while (getline(ifs, line)) {
+ pos = line.find('#');
+ if (pos != string::npos) {
+ line.resize(pos);
+ }
+ boost::trim(line);
+ if (line.empty()) {
+ continue;
+ }
+
+ nmg.addMask(line);
+ }
+
+ g_ACL.setState(nmg);
+ });
+
+ luaCtx.writeFunction("showACL", []() {
+ setLuaNoSideEffect();
+ auto aclEntries = g_ACL.getLocal()->toStringVector();
+
+ for (const auto& entry : aclEntries) {
+ g_outputBuffer += entry + "\n";
+ }
+ });
+
+ luaCtx.writeFunction("shutdown", []() {
+#ifdef HAVE_SYSTEMD
+ sd_notify(0, "STOPPING=1");
+#endif /* HAVE_SYSTEMD */
+#if 0
+ // Useful for debugging leaks, but might lead to race under load
+ // since other threads are still running.
+ for (auto& frontend : g_tlslocals) {
+ frontend->cleanup();
+ }
+ g_tlslocals.clear();
+ g_rings.clear();
+#endif /* 0 */
+ pdns::coverage::dumpCoverageData();
+ _exit(0);
+ });
+
+ typedef LuaAssociativeTable<boost::variant<bool, std::string>> showserversopts_t;
+
+ luaCtx.writeFunction("showServers", [](boost::optional<showserversopts_t> vars) {
+ setLuaNoSideEffect();
+ bool showUUIDs = false;
+ getOptionalValue<bool>(vars, "showUUIDs", showUUIDs);
+ checkAllParametersConsumed("showServers", vars);
+
+ try {
+ ostringstream ret;
+ boost::format fmt;
+
+ auto latFmt = boost::format("%5.1f");
+ if (showUUIDs) {
+ fmt = boost::format("%1$-3d %15$-36s %2$-20.20s %|62t|%3% %|107t|%4$5s %|88t|%5$7.1f %|103t|%6$7d %|106t|%7$10d %|115t|%8$10d %|117t|%9$10d %|123t|%10$7d %|128t|%11$5.1f %|146t|%12$5s %|152t|%16$5s %|158t|%13$11d %14%");
+ // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (tcp latency)
+ ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "UUID" % "TCP") << endl;
+ }
+ else {
+ fmt = boost::format("%1$-3d %2$-20.20s %|25t|%3% %|70t|%4$5s %|51t|%5$7.1f %|66t|%6$7d %|69t|%7$10d %|78t|%8$10d %|80t|%9$10d %|86t|%10$7d %|91t|%11$5.1f %|109t|%12$5s %|115t|%15$5s %|121t|%13$11d %14%");
+ ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "TCP") << endl;
+ }
+
+ uint64_t totQPS{0};
+ uint64_t totQueries{0};
+ uint64_t totDrops{0};
+ int counter = 0;
+ auto states = g_dstates.getLocal();
+ for (const auto& backend : *states) {
+ string status = backend->getStatus();
+ string pools;
+ for (const auto& pool : backend->d_config.pools) {
+ if (!pools.empty()) {
+ pools += " ";
+ }
+ pools += pool;
+ }
+ const std::string latency = (backend->latencyUsec == 0.0 ? "-" : boost::str(latFmt % (backend->latencyUsec / 1000.0)));
+ const std::string latencytcp = (backend->latencyUsecTCP == 0.0 ? "-" : boost::str(latFmt % (backend->latencyUsecTCP / 1000.0)));
+ if (showUUIDs) {
+ ret << (fmt % counter % backend->getName() % backend->d_config.remote.toStringWithPort() % status % backend->queryLoad % backend->qps.getRate() % backend->d_config.order % backend->d_config.d_weight % backend->queries.load() % backend->reuseds.load() % (backend->dropRate) % latency % backend->outstanding.load() % pools % *backend->d_config.id % latencytcp) << endl;
+ }
+ else {
+ ret << (fmt % counter % backend->getName() % backend->d_config.remote.toStringWithPort() % status % backend->queryLoad % backend->qps.getRate() % backend->d_config.order % backend->d_config.d_weight % backend->queries.load() % backend->reuseds.load() % (backend->dropRate) % latency % backend->outstanding.load() % pools % latencytcp) << endl;
+ }
+ totQPS += static_cast<uint64_t>(backend->queryLoad);
+ totQueries += backend->queries.load();
+ totDrops += backend->reuseds.load();
+ ++counter;
+ }
+ if (showUUIDs) {
+ ret << (fmt % "All" % "" % "" % ""
+ % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "" % "")
+ << endl;
+ }
+ else {
+ ret << (fmt % "All" % "" % "" % ""
+ % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "")
+ << endl;
+ }
+
+ g_outputBuffer = ret.str();
+ }
+ catch (std::exception& e) {
+ g_outputBuffer = e.what();
+ throw;
+ }
+ });
+
+ luaCtx.writeFunction("getServers", []() {
+ setLuaNoSideEffect();
+ LuaArray<std::shared_ptr<DownstreamState>> ret;
+ int count = 1;
+ for (const auto& backend : g_dstates.getCopy()) {
+ ret.emplace_back(count++, backend);
+ }
+ return ret;
+ });
+
+ luaCtx.writeFunction("getPoolServers", [](const string& pool) {
+ const auto poolServers = getDownstreamCandidates(g_pools.getCopy(), pool);
+ return *poolServers;
+ });
+
+ luaCtx.writeFunction("getServer", [client](boost::variant<int, std::string> identifier) {
+ if (client) {
+ return std::make_shared<DownstreamState>(ComboAddress());
+ }
+ auto states = g_dstates.getCopy();
+ if (auto* str = boost::get<std::string>(&identifier)) {
+ const auto uuid = getUniqueID(*str);
+ for (auto& state : states) {
+ if (*state->d_config.id == uuid) {
+ return state;
+ }
+ }
+ }
+ else if (auto* pos = boost::get<int>(&identifier)) {
+ return states.at(*pos);
+ }
+
+ g_outputBuffer = "Error: no rule matched\n";
+ return std::shared_ptr<DownstreamState>(nullptr);
+ });
+
+#ifndef DISABLE_CARBON
+ luaCtx.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName, boost::optional<uint64_t> interval, boost::optional<string> namespace_name, boost::optional<string> instance_name) {
+ setLuaSideEffect();
+ dnsdist::Carbon::Endpoint endpoint{ComboAddress(address, 2003),
+ (namespace_name && !namespace_name->empty()) ? *namespace_name : "dnsdist",
+ ourName ? *ourName : "",
+ (instance_name && !instance_name->empty()) ? *instance_name : "main",
+ (interval && *interval < std::numeric_limits<unsigned int>::max()) ? static_cast<unsigned int>(*interval) : 30};
+ dnsdist::Carbon::addEndpoint(std::move(endpoint));
+ });
+#endif /* DISABLE_CARBON */
+
+ luaCtx.writeFunction("webserver", [client, configCheck](const std::string& address) {
+ setLuaSideEffect();
+ ComboAddress local;
+ try {
+ local = ComboAddress(address);
+ }
+ catch (const PDNSException& e) {
+ throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason);
+ }
+
+ if (client || configCheck) {
+ return;
+ }
+
+ try {
+ int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0);
+ SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
+ SBind(sock, local);
+ SListen(sock, 5);
+ auto launch = [sock, local]() {
+ thread thr(dnsdistWebserverThread, sock, local);
+ thr.detach();
+ };
+ if (g_launchWork) {
+ g_launchWork->push_back(launch);
+ }
+ else {
+ launch();
+ }
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what();
+ errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what());
+ }
+ });
+
+ typedef LuaAssociativeTable<boost::variant<bool, std::string, LuaAssociativeTable<std::string>>> webserveropts_t;
+
+ luaCtx.writeFunction("setWebserverConfig", [](boost::optional<webserveropts_t> vars) {
+ setLuaSideEffect();
+
+ if (!vars) {
+ return;
+ }
+
+ bool hashPlaintextCredentials = false;
+ getOptionalValue<bool>(vars, "hashPlaintextCredentials", hashPlaintextCredentials);
+
+ std::string password;
+ std::string apiKey;
+ std::string acl;
+ LuaAssociativeTable<std::string> headers;
+ bool statsRequireAuthentication{true};
+ bool apiRequiresAuthentication{true};
+ bool dashboardRequiresAuthentication{true};
+ int maxConcurrentConnections = 0;
+
+ if (getOptionalValue<std::string>(vars, "password", password) > 0) {
+ auto holder = make_unique<CredentialsHolder>(std::move(password), hashPlaintextCredentials);
+ if (!holder->wasHashed() && holder->isHashingAvailable()) {
+ infolog("Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead.");
+ }
+
+ setWebserverPassword(std::move(holder));
+ }
+
+ if (getOptionalValue<std::string>(vars, "apiKey", apiKey) > 0) {
+ auto holder = make_unique<CredentialsHolder>(std::move(apiKey), hashPlaintextCredentials);
+ if (!holder->wasHashed() && holder->isHashingAvailable()) {
+ infolog("Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead.");
+ }
+
+ setWebserverAPIKey(std::move(holder));
+ }
+
+ if (getOptionalValue<std::string>(vars, "acl", acl) > 0) {
+ setWebserverACL(acl);
+ }
+
+ if (getOptionalValue<decltype(headers)>(vars, "customHeaders", headers) > 0) {
+ setWebserverCustomHeaders(headers);
+ }
+
+ if (getOptionalValue<bool>(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) {
+ setWebserverStatsRequireAuthentication(statsRequireAuthentication);
+ }
+
+ if (getOptionalValue<bool>(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) {
+ setWebserverAPIRequiresAuthentication(apiRequiresAuthentication);
+ }
+
+ if (getOptionalValue<bool>(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) {
+ setWebserverDashboardRequiresAuthentication(dashboardRequiresAuthentication);
+ }
+
+ if (getOptionalIntegerValue("setWebserverConfig", vars, "maxConcurrentConnections", maxConcurrentConnections) > 0) {
+ setWebserverMaxConcurrentConnections(maxConcurrentConnections);
+ }
+ });
+
+ luaCtx.writeFunction("showWebserverConfig", []() {
+ setLuaNoSideEffect();
+ return getWebserverConfig();
+ });
+
+ luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional<uint64_t> workFactor) {
+ if (workFactor) {
+ return hashPassword(password, *workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
+ }
+ return hashPassword(password);
+ });
+
+ luaCtx.writeFunction("controlSocket", [client, configCheck](const std::string& str) {
+ setLuaSideEffect();
+ ComboAddress local(str, 5199);
+
+ if (client || configCheck) {
+ g_serverControl = local;
+ return;
+ }
+
+ g_consoleEnabled = true;
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+ if (g_configurationDone && g_consoleKey.empty()) {
+ warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
+ }
+#endif
+
+ try {
+ auto sock = std::make_shared<Socket>(local.sin4.sin_family, SOCK_STREAM, 0);
+ sock->bind(local, true);
+ sock->listen(5);
+ auto launch = [sock = std::move(sock), local]() {
+ std::thread consoleControlThread(controlThread, sock, local);
+ consoleControlThread.detach();
+ };
+ if (g_launchWork) {
+ g_launchWork->emplace_back(std::move(launch));
+ }
+ else {
+ launch();
+ }
+ }
+ catch (std::exception& e) {
+ g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what();
+ errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what());
+ }
+ });
+
+ luaCtx.writeFunction("addConsoleACL", [](const std::string& netmask) {
+ setLuaSideEffect();
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+ warnlog("Allowing remote access to the console while neither libsodium not libcrypto support has been enabled is not secure, and will result in cleartext communications");
+#endif
+
+ g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); });
+ });
+
+ luaCtx.writeFunction("setConsoleACL", [](LuaTypeOrArrayOf<std::string> inp) {
+ setLuaSideEffect();
+
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+ warnlog("Allowing remote access to the console while neither libsodium nor libcrypto support has not been enabled is not secure, and will result in cleartext communications");
+#endif
+
+ NetmaskGroup nmg;
+ if (auto* str = boost::get<string>(&inp)) {
+ nmg.addMask(*str);
+ }
+ else {
+ for (const auto& entry : boost::get<LuaArray<std::string>>(inp)) {
+ nmg.addMask(entry.second);
+ }
+ }
+ g_consoleACL.setState(nmg);
+ });
+
+ luaCtx.writeFunction("showConsoleACL", []() {
+ setLuaNoSideEffect();
+
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+ warnlog("Allowing remote access to the console while neither libsodium nor libcrypto support has not been enabled is not secure, and will result in cleartext communications");
+#endif
+
+ auto aclEntries = g_consoleACL.getLocal()->toStringVector();
+
+ for (const auto& entry : aclEntries) {
+ g_outputBuffer += entry + "\n";
+ }
+ });
+
+ luaCtx.writeFunction("setConsoleMaximumConcurrentConnections", [](uint64_t max) {
+ setLuaSideEffect();
+ setConsoleMaximumConcurrentConnections(max);
+ });
+
+ luaCtx.writeFunction("clearQueryCounters", []() {
+ unsigned int size{0};
+ {
+ auto records = g_qcount.records.write_lock();
+ size = records->size();
+ records->clear();
+ }
+
+ boost::format fmt("%d records cleared from query counter buffer\n");
+ g_outputBuffer = (fmt % size).str();
+ });
+
+ luaCtx.writeFunction("getQueryCounters", [](boost::optional<uint64_t> optMax) {
+ setLuaNoSideEffect();
+ auto records = g_qcount.records.read_lock();
+ g_outputBuffer = "query counting is currently: ";
+ g_outputBuffer += g_qcount.enabled ? "enabled" : "disabled";
+ g_outputBuffer += (boost::format(" (%d records in buffer)\n") % records->size()).str();
+
+ boost::format fmt("%-3d %s: %d request(s)\n");
+ uint64_t max = optMax ? *optMax : 10U;
+ uint64_t index{1};
+ for (auto it = records->begin(); it != records->end() && index <= max; ++it, ++index) {
+ g_outputBuffer += (fmt % index % it->first % it->second).str();
+ }
+ });
+
+ luaCtx.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled = enabled; });
+
+ luaCtx.writeFunction("setQueryCountFilter", [](QueryCountFilter func) {
+ g_qcount.filter = std::move(func);
+ });
+
+ luaCtx.writeFunction("makeKey", []() {
+ setLuaNoSideEffect();
+ g_outputBuffer = "setKey(" + dnsdist::crypto::authenticated::newKey() + ")\n";
+ });
+
+ luaCtx.writeFunction("setKey", [](const std::string& key) {
+ if (!g_configurationDone && !g_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
+ return; // but later setKeys() trump the -k value again
+ }
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+ warnlog("Calling setKey() while neither libsodium nor libcrypto support has been enabled is not secure, and will result in cleartext communications");
+#endif
+
+ setLuaSideEffect();
+ string newkey;
+ if (B64Decode(key, newkey) < 0) {
+ g_outputBuffer = string("Unable to decode ") + key + " as Base64";
+ errlog("%s", g_outputBuffer);
+ }
+ else {
+ g_consoleKey = std::move(newkey);
+ }
+ });
+
+ luaCtx.writeFunction("clearConsoleHistory", []() {
+ clearConsoleHistory();
+ });
+
+ luaCtx.writeFunction("testCrypto", [](boost::optional<string> optTestMsg) {
+ setLuaNoSideEffect();
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+ try {
+ string testmsg;
+
+ if (optTestMsg) {
+ testmsg = *optTestMsg;
+ }
+ else {
+ testmsg = "testStringForCryptoTests";
+ }
+
+ dnsdist::crypto::authenticated::Nonce nonce1;
+ dnsdist::crypto::authenticated::Nonce nonce2;
+ nonce1.init();
+ nonce2 = nonce1;
+ string encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, nonce1);
+ string decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, nonce2);
+
+ nonce1.increment();
+ nonce2.increment();
+
+ encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, nonce1);
+ decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, nonce2);
+
+ if (testmsg == decrypted) {
+ g_outputBuffer = "Everything is ok!\n";
+ }
+ else {
+ g_outputBuffer = "Crypto failed.. (the decoded value does not match the cleartext one)\n";
+ }
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Crypto failed: " + std::string(e.what()) + "\n";
+ }
+ catch (...) {
+ g_outputBuffer = "Crypto failed..\n";
+ }
+#else
+ g_outputBuffer = "Crypto not available.\n";
+#endif
+ });
+
+ luaCtx.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout = timeout; });
+
+ luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout = timeout; });
+
+ luaCtx.writeFunction("setUDPTimeout", [](int timeout) { DownstreamState::s_udpTimeout = timeout; });
+
+ luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) {
+ if (!checkConfigurationTime("setMaxUDPOutstanding")) {
+ return;
+ }
+
+ checkParameterBound("setMaxUDPOutstanding", max);
+ g_maxOutstanding = max;
+ });
+
+ luaCtx.writeFunction("setMaxTCPClientThreads", [](uint64_t max) {
+ if (!checkConfigurationTime("setMaxTCPClientThreads")) {
+ return;
+ }
+ g_maxTCPClientThreads = max;
+ });
+
+ luaCtx.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) {
+ if (!checkConfigurationTime("setMaxTCPQueuedConnections")) {
+ return;
+ }
+ g_maxTCPQueuedConnections = max;
+ });
+
+ luaCtx.writeFunction("setMaxTCPQueriesPerConnection", [](uint64_t max) {
+ if (!checkConfigurationTime("setMaxTCPQueriesPerConnection")) {
+ return;
+ }
+ g_maxTCPQueriesPerConn = max;
+ });
+
+ luaCtx.writeFunction("setMaxTCPConnectionsPerClient", [](uint64_t max) {
+ if (!checkConfigurationTime("setMaxTCPConnectionsPerClient")) {
+ return;
+ }
+ dnsdist::IncomingConcurrentTCPConnectionsManager::setMaxTCPConnectionsPerClient(max);
+ });
+
+ luaCtx.writeFunction("setMaxTCPConnectionDuration", [](uint64_t max) {
+ if (!checkConfigurationTime("setMaxTCPConnectionDuration")) {
+ return;
+ }
+ g_maxTCPConnectionDuration = max;
+ });
+
+ luaCtx.writeFunction("setMaxCachedTCPConnectionsPerDownstream", [](uint64_t max) {
+ setTCPDownstreamMaxIdleConnectionsPerBackend(max);
+ });
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ luaCtx.writeFunction("setMaxIdleDoHConnectionsPerDownstream", [](uint64_t max) {
+ setDoHDownstreamMaxIdleConnectionsPerBackend(max);
+ });
+
+ luaCtx.writeFunction("setOutgoingDoHWorkerThreads", [](uint64_t workers) {
+ if (!checkConfigurationTime("setOutgoingDoHWorkerThreads")) {
+ return;
+ }
+ g_outgoingDoHWorkerThreads = workers;
+ });
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+
+ luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketsPerBackend", [](uint64_t max) {
+ if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketsPerBackend")) {
+ return;
+ }
+ TLSSessionCache::setMaxTicketsPerBackend(max);
+ });
+
+ luaCtx.writeFunction("setOutgoingTLSSessionsCacheCleanupDelay", [](time_t delay) {
+ if (!checkConfigurationTime("setOutgoingTLSSessionsCacheCleanupDelay")) {
+ return;
+ }
+ TLSSessionCache::setCleanupDelay(delay);
+ });
+
+ luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketValidity", [](time_t validity) {
+ if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketValidity")) {
+ return;
+ }
+ TLSSessionCache::setSessionValidity(validity);
+ });
+
+ luaCtx.writeFunction("getOutgoingTLSSessionCacheSize", []() {
+ setLuaNoSideEffect();
+ return g_sessionCache.getSize();
+ });
+
+ luaCtx.writeFunction("setCacheCleaningDelay", [](uint64_t delay) {
+ checkParameterBound("setCacheCleaningDelay", delay, std::numeric_limits<uint32_t>::max());
+ g_cacheCleaningDelay = delay;
+ });
+
+ luaCtx.writeFunction("setCacheCleaningPercentage", [](uint64_t percentage) {
+ if (percentage < 100) {
+ g_cacheCleaningPercentage = percentage;
+ }
+ else {
+ g_cacheCleaningPercentage = 100;
+ }
+ });
+
+ luaCtx.writeFunction("setECSSourcePrefixV4", [](uint64_t prefix) {
+ checkParameterBound("setECSSourcePrefixV4", prefix, std::numeric_limits<uint16_t>::max());
+ g_ECSSourcePrefixV4 = prefix;
+ });
+
+ luaCtx.writeFunction("setECSSourcePrefixV6", [](uint64_t prefix) {
+ checkParameterBound("setECSSourcePrefixV6", prefix, std::numeric_limits<uint16_t>::max());
+ g_ECSSourcePrefixV6 = prefix;
+ });
+
+ luaCtx.writeFunction("setECSOverride", [](bool override) { g_ECSOverride = override; });
+
+#ifndef DISABLE_DYNBLOCKS
+ luaCtx.writeFunction("showDynBlocks", []() {
+ setLuaNoSideEffect();
+ auto slow = g_dynblockNMG.getCopy();
+ timespec now{};
+ gettime(&now);
+ boost::format fmt("%-24s %8d %8d %-10s %-20s %-10s %s\n");
+ g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "eBPF" % "Reason").str();
+ for (const auto& entry : slow) {
+ if (now < entry.second.until) {
+ uint64_t counter = entry.second.blocks;
+ if (g_defaultBPFFilter && entry.second.bpf) {
+ counter += g_defaultBPFFilter->getHits(entry.first.getNetwork());
+ }
+ g_outputBuffer += (fmt % entry.first.toString() % (entry.second.until.tv_sec - now.tv_sec) % counter % (entry.second.warning ? "true" : "false") % DNSAction::typeToString(entry.second.action != DNSAction::Action::None ? entry.second.action : g_dynBlockAction) % (g_defaultBPFFilter && entry.second.bpf ? "*" : "") % entry.second.reason).str();
+ }
+ }
+ auto slow2 = g_dynblockSMT.getCopy();
+ slow2.visit([&now, &fmt](const SuffixMatchTree<DynBlock>& node) {
+ if (now < node.d_value.until) {
+ string dom("empty");
+ if (!node.d_value.domain.empty()) {
+ dom = node.d_value.domain.toString();
+ }
+ g_outputBuffer += (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % (node.d_value.warning ? "true" : "false") % DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) % "" % node.d_value.reason).str();
+ }
+ });
+ });
+
+ luaCtx.writeFunction("getDynamicBlocks", []() {
+ setLuaNoSideEffect();
+ timespec now{};
+ gettime(&now);
+
+ LuaAssociativeTable<DynBlock> entries;
+ auto fullCopy = g_dynblockNMG.getCopy();
+ for (const auto& blockPair : fullCopy) {
+ const auto& requestor = blockPair.first;
+ if (!(now < blockPair.second.until)) {
+ continue;
+ }
+ auto entry = blockPair.second;
+ if (g_defaultBPFFilter && entry.bpf) {
+ entry.blocks += g_defaultBPFFilter->getHits(requestor.getNetwork());
+ }
+ if (entry.action == DNSAction::Action::None) {
+ entry.action = g_dynBlockAction;
+ }
+ entries.emplace(requestor.toString(), std::move(entry));
+ }
+ return entries;
+ });
+
+ luaCtx.writeFunction("getDynamicBlocksSMT", []() {
+ setLuaNoSideEffect();
+ timespec now{};
+ gettime(&now);
+
+ LuaAssociativeTable<DynBlock> entries;
+ auto fullCopy = g_dynblockSMT.getCopy();
+ fullCopy.visit([&now, &entries](const SuffixMatchTree<DynBlock>& node) {
+ if (!(now < node.d_value.until)) {
+ return;
+ }
+ auto entry = node.d_value;
+ string key("empty");
+ if (!entry.domain.empty()) {
+ key = entry.domain.toString();
+ }
+ if (entry.action == DNSAction::Action::None) {
+ entry.action = g_dynBlockAction;
+ }
+ entries.emplace(std::move(key), std::move(entry));
+ });
+ return entries;
+ });
+
+ luaCtx.writeFunction("clearDynBlocks", []() {
+ setLuaSideEffect();
+ nmts_t nmg;
+ g_dynblockNMG.setState(nmg);
+ SuffixMatchTree<DynBlock> smt;
+ g_dynblockSMT.setState(smt);
+ });
+
+#ifndef DISABLE_DEPRECATED_DYNBLOCK
+ luaCtx.writeFunction("addDynBlocks",
+ [](const std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& addrs, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
+ if (addrs.empty()) {
+ return;
+ }
+ setLuaSideEffect();
+ auto slow = g_dynblockNMG.getCopy();
+ timespec now{};
+ gettime(&now);
+ timespec until{now};
+ int actualSeconds = seconds ? *seconds : 10;
+ until.tv_sec += actualSeconds;
+ for (const auto& capair : addrs) {
+ unsigned int count = 0;
+ /* this legacy interface does not support ranges or ports, use DynBlockRulesGroup instead */
+ AddressAndPortRange requestor(capair.first, capair.first.isIPv4() ? 32 : 128, 0);
+ auto* got = slow.lookup(requestor);
+ bool expired = false;
+ if (got != nullptr) {
+ if (until < got->second.until) {
+ // had a longer policy
+ continue;
+ }
+ if (now < got->second.until) {
+ // only inherit count on fresh query we are extending
+ count = got->second.blocks;
+ }
+ else {
+ expired = true;
+ }
+ }
+ DynBlock dblock{msg, until, DNSName(), (action ? *action : DNSAction::Action::None)};
+ dblock.blocks = count;
+ if (got == nullptr || expired) {
+ warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg);
+ }
+ slow.insert(requestor).second = std::move(dblock);
+ }
+ g_dynblockNMG.setState(slow);
+ });
+
+ luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
+ if (!checkConfigurationTime("setDynBlocksAction")) {
+ return;
+ }
+ if (action == DNSAction::Action::Drop || action == DNSAction::Action::NoOp || action == DNSAction::Action::Nxdomain || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate || action == DNSAction::Action::NoRecurse) {
+ g_dynBlockAction = action;
+ }
+ else {
+ errlog("Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!");
+ g_outputBuffer = "Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!\n";
+ }
+ });
+#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+
+ luaCtx.writeFunction("addDynBlockSMT",
+ [](const LuaArray<std::string>& names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
+ if (names.empty()) {
+ return;
+ }
+ setLuaSideEffect();
+ timespec now{};
+ gettime(&now);
+ unsigned int actualSeconds = seconds ? *seconds : 10;
+
+ bool needUpdate = false;
+ auto slow = g_dynblockSMT.getCopy();
+ for (const auto& capair : names) {
+ DNSName domain(capair.second);
+ domain.makeUsLowerCase();
+
+ if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(slow, now, domain, msg, actualSeconds, action ? *action : DNSAction::Action::None, false)) {
+ needUpdate = true;
+ }
+ }
+
+ if (needUpdate) {
+ g_dynblockSMT.setState(slow);
+ }
+ });
+
+ luaCtx.writeFunction("addDynamicBlock",
+ [](const boost::variant<ComboAddress, std::string>& clientIP, const std::string& msg, const boost::optional<DNSAction::Action> action, const boost::optional<int> seconds, boost::optional<uint8_t> clientIPMask, boost::optional<uint8_t> clientIPPortMask) {
+ setLuaSideEffect();
+
+ ComboAddress clientIPCA;
+ if (clientIP.type() == typeid(ComboAddress)) {
+ clientIPCA = boost::get<ComboAddress>(clientIP);
+ }
+ else {
+ const auto& clientIPStr = boost::get<std::string>(clientIP);
+ try {
+ clientIPCA = ComboAddress(clientIPStr);
+ }
+ catch (const std::exception& exp) {
+ errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.what());
+ return;
+ }
+ catch (const PDNSException& exp) {
+ errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.reason);
+ return;
+ }
+ }
+ AddressAndPortRange target(clientIPCA, clientIPMask ? *clientIPMask : (clientIPCA.isIPv4() ? 32 : 128), clientIPPortMask ? *clientIPPortMask : 0);
+ unsigned int actualSeconds = seconds ? *seconds : 10;
+
+ timespec now{};
+ gettime(&now);
+ auto slow = g_dynblockNMG.getCopy();
+ if (dnsdist::DynamicBlocks::addOrRefreshBlock(slow, now, target, msg, actualSeconds, action ? *action : DNSAction::Action::None, false, false)) {
+ g_dynblockNMG.setState(slow);
+ }
+ });
+
+ luaCtx.writeFunction("setDynBlocksPurgeInterval", [](uint64_t interval) {
+ DynBlockMaintenance::s_expiredDynBlocksPurgeInterval = static_cast<time_t>(interval);
+ });
+#endif /* DISABLE_DYNBLOCKS */
+
+#ifdef HAVE_DNSCRYPT
+ luaCtx.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, LuaTypeOrArrayOf<std::string> certFiles, LuaTypeOrArrayOf<std::string> keyFiles, boost::optional<localbind_t> vars) {
+ if (!checkConfigurationTime("addDNSCryptBind")) {
+ return;
+ }
+ bool reusePort = false;
+ int tcpFastOpenQueueSize = 0;
+ int tcpListenQueueSize = 0;
+ uint64_t maxInFlightQueriesPerConn = 0;
+ uint64_t tcpMaxConcurrentConnections = 0;
+ std::string interface;
+ std::set<int> cpus;
+ std::vector<DNSCryptContext::CertKeyPaths> certKeys;
+ bool enableProxyProtocol = true;
+
+ parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+ checkAllParametersConsumed("addDNSCryptBind", vars);
+
+ if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
+ auto certFile = boost::get<std::string>(certFiles);
+ auto keyFile = boost::get<std::string>(keyFiles);
+ certKeys.push_back({std::move(certFile), std::move(keyFile)});
+ }
+ else if (certFiles.type() == typeid(LuaArray<std::string>) && keyFiles.type() == typeid(LuaArray<std::string>)) {
+ auto certFilesVect = boost::get<LuaArray<std::string>>(certFiles);
+ auto keyFilesVect = boost::get<LuaArray<std::string>>(keyFiles);
+ if (certFilesVect.size() == keyFilesVect.size()) {
+ for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
+ certKeys.push_back({certFilesVect.at(idx).second, keyFilesVect.at(idx).second});
+ }
+ }
+ else {
+ errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind!");
+ g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!";
+ return;
+ }
+ }
+ else {
+ errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind()!");
+ g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!";
+ return;
+ }
+
+ try {
+ auto ctx = std::make_shared<DNSCryptContext>(providerName, certKeys);
+
+ /* UDP */
+ auto clientState = std::make_unique<ClientState>(ComboAddress(addr, 443), false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ clientState->dnscryptCtx = ctx;
+ g_dnsCryptLocals.push_back(ctx);
+ g_frontends.push_back(std::move(clientState));
+
+ /* TCP */
+ clientState = std::make_unique<ClientState>(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ clientState->dnscryptCtx = std::move(ctx);
+ if (tcpListenQueueSize > 0) {
+ clientState->tcpListenQueueSize = tcpListenQueueSize;
+ }
+ if (maxInFlightQueriesPerConn > 0) {
+ clientState->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+ }
+ if (tcpMaxConcurrentConnections > 0) {
+ clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+ }
+
+ g_frontends.push_back(std::move(clientState));
+ }
+ catch (const std::exception& e) {
+ errlog("Error during addDNSCryptBind() processing: %s", e.what());
+ g_outputBuffer = "Error during addDNSCryptBind() processing: " + string(e.what()) + "\n";
+ }
+ });
+
+ luaCtx.writeFunction("showDNSCryptBinds", []() {
+ setLuaNoSideEffect();
+ ostringstream ret;
+ boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s");
+ ret << (fmt % "#" % "Address" % "Provider Name") << endl;
+ size_t idx = 0;
+
+ std::unordered_set<std::shared_ptr<DNSCryptContext>> contexts;
+ for (const auto& frontend : g_frontends) {
+ const std::shared_ptr<DNSCryptContext> ctx = frontend->dnscryptCtx;
+ if (!ctx || contexts.count(ctx) != 0) {
+ continue;
+ }
+ contexts.insert(ctx);
+ ret << (fmt % idx % frontend->local.toStringWithPort() % ctx->getProviderName()) << endl;
+ idx++;
+ }
+
+ g_outputBuffer = ret.str();
+ });
+
+ luaCtx.writeFunction("getDNSCryptBind", [](uint64_t idx) {
+ setLuaNoSideEffect();
+ std::shared_ptr<DNSCryptContext> ret = nullptr;
+ if (idx < g_dnsCryptLocals.size()) {
+ ret = g_dnsCryptLocals.at(idx);
+ }
+ return ret;
+ });
+
+ luaCtx.writeFunction("getDNSCryptBindCount", []() {
+ setLuaNoSideEffect();
+ return g_dnsCryptLocals.size();
+ });
+#endif /* HAVE_DNSCRYPT */
+
+ luaCtx.writeFunction("showPools", []() {
+ setLuaNoSideEffect();
+ try {
+ ostringstream ret;
+ boost::format fmt("%1$-20.20s %|25t|%2$20s %|25t|%3$20s %|50t|%4%");
+ // 1 2 3 4
+ ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers") << endl;
+
+ const auto localPools = g_pools.getCopy();
+ for (const auto& entry : localPools) {
+ const string& name = entry.first;
+ const std::shared_ptr<ServerPool> pool = entry.second;
+ string cache = pool->packetCache != nullptr ? pool->packetCache->toString() : "";
+ string policy = g_policy.getLocal()->getName();
+ if (pool->policy != nullptr) {
+ policy = pool->policy->getName();
+ }
+ string servers;
+
+ const auto poolServers = pool->getServers();
+ for (const auto& server : *poolServers) {
+ if (!servers.empty()) {
+ servers += ", ";
+ }
+ if (!server.second->getName().empty()) {
+ servers += server.second->getName();
+ servers += " ";
+ }
+ servers += server.second->d_config.remote.toStringWithPort();
+ }
+
+ ret << (fmt % name % cache % policy % servers) << endl;
+ }
+ g_outputBuffer = ret.str();
+ }
+ catch (std::exception& e) {
+ g_outputBuffer = e.what();
+ throw;
+ }
+ });
+
+ luaCtx.writeFunction("getPoolNames", []() {
+ setLuaNoSideEffect();
+ LuaArray<std::string> ret;
+ int count = 1;
+ const auto localPools = g_pools.getCopy();
+ for (const auto& entry : localPools) {
+ const string& name = entry.first;
+ ret.emplace_back(count++, name);
+ }
+ return ret;
+ });
+
+ luaCtx.writeFunction("getPool", [client](const string& poolName) {
+ if (client) {
+ return std::make_shared<ServerPool>();
+ }
+ auto localPools = g_pools.getCopy();
+ std::shared_ptr<ServerPool> pool = createPoolIfNotExists(localPools, poolName);
+ g_pools.setState(localPools);
+ return pool;
+ });
+
+ luaCtx.writeFunction("setVerbose", [](bool verbose) { g_verbose = verbose; });
+ luaCtx.writeFunction("getVerbose", []() { return g_verbose; });
+ luaCtx.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks = verbose; });
+ luaCtx.writeFunction("setVerboseLogDestination", [](const std::string& dest) {
+ if (!checkConfigurationTime("setVerboseLogDestination")) {
+ return;
+ }
+ try {
+ auto stream = std::ofstream(dest.c_str());
+ dnsdist::logging::LoggingConfiguration::setVerboseStream(std::move(stream));
+ }
+ catch (const std::exception& e) {
+ errlog("Error while opening the verbose logging destination file %s: %s", dest, e.what());
+ }
+ });
+ luaCtx.writeFunction("setStructuredLogging", [](bool enable, boost::optional<LuaAssociativeTable<std::string>> options) {
+ std::string levelPrefix;
+ std::string timeFormat;
+ if (options) {
+ getOptionalValue<std::string>(options, "levelPrefix", levelPrefix);
+ if (getOptionalValue<std::string>(options, "timeFormat", timeFormat) == 1) {
+ if (timeFormat == "numeric") {
+ dnsdist::logging::LoggingConfiguration::setStructuredTimeFormat(dnsdist::logging::LoggingConfiguration::TimeFormat::Numeric);
+ }
+ else if (timeFormat == "ISO8601") {
+ dnsdist::logging::LoggingConfiguration::setStructuredTimeFormat(dnsdist::logging::LoggingConfiguration::TimeFormat::ISO8601);
+ }
+ else {
+ warnlog("Unknown value '%s' to setStructuredLogging's 'timeFormat' parameter", timeFormat);
+ }
+ }
+ checkAllParametersConsumed("setStructuredLogging", options);
+ }
+
+ dnsdist::logging::LoggingConfiguration::setStructuredLogging(enable, levelPrefix);
+ });
+
+ luaCtx.writeFunction("setStaleCacheEntriesTTL", [](uint64_t ttl) {
+ checkParameterBound("setStaleCacheEntriesTTL", ttl, std::numeric_limits<uint32_t>::max());
+ g_staleCacheEntriesTTL = ttl;
+ });
+
+ luaCtx.writeFunction("showBinds", []() {
+ setLuaNoSideEffect();
+ try {
+ ostringstream ret;
+ boost::format fmt("%1$-3d %2$-20.20s %|35t|%3$-20.20s %|57t|%4%");
+ // 1 2 3 4
+ ret << (fmt % "#" % "Address" % "Protocol" % "Queries") << endl;
+
+ size_t counter = 0;
+ for (const auto& front : g_frontends) {
+ ret << (fmt % counter % front->local.toStringWithPort() % front->getType() % front->queries) << endl;
+ counter++;
+ }
+ g_outputBuffer = ret.str();
+ }
+ catch (std::exception& e) {
+ g_outputBuffer = e.what();
+ throw;
+ }
+ });
+
+ luaCtx.writeFunction("getBind", [](uint64_t num) {
+ setLuaNoSideEffect();
+ ClientState* ret = nullptr;
+ if (num < g_frontends.size()) {
+ ret = g_frontends[num].get();
+ }
+ return ret;
+ });
+
+ luaCtx.writeFunction("getBindCount", []() {
+ setLuaNoSideEffect();
+ return g_frontends.size();
+ });
+
+ luaCtx.writeFunction("help", [](boost::optional<std::string> command) {
+ setLuaNoSideEffect();
+ g_outputBuffer = "";
+#ifndef DISABLE_COMPLETION
+ for (const auto& keyword : g_consoleKeywords) {
+ if (!command) {
+ g_outputBuffer += keyword.toString() + "\n";
+ }
+ else if (keyword.name == command) {
+ g_outputBuffer = keyword.toString() + "\n";
+ return;
+ }
+ }
+#endif /* DISABLE_COMPLETION */
+ if (command) {
+ g_outputBuffer = "Nothing found for " + *command + "\n";
+ }
+ });
+
+ luaCtx.writeFunction("showVersion", []() {
+ setLuaNoSideEffect();
+ g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n";
+ });
+
+#ifdef HAVE_EBPF
+ luaCtx.writeFunction("setDefaultBPFFilter", [](std::shared_ptr<BPFFilter> bpf) {
+ if (!checkConfigurationTime("setDefaultBPFFilter")) {
+ return;
+ }
+ g_defaultBPFFilter = std::move(bpf);
+ });
+
+ luaCtx.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
+ if (dbpf) {
+ g_dynBPFFilters.push_back(std::move(dbpf));
+ }
+ });
+
+ luaCtx.writeFunction("unregisterDynBPFFilter", [](const std::shared_ptr<DynBPFFilter>& dbpf) {
+ if (dbpf) {
+ for (auto filterIt = g_dynBPFFilters.begin(); filterIt != g_dynBPFFilters.end(); filterIt++) {
+ if (*filterIt == dbpf) {
+ g_dynBPFFilters.erase(filterIt);
+ break;
+ }
+ }
+ }
+ });
+
+#ifndef DISABLE_DYNBLOCKS
+#ifndef DISABLE_DEPRECATED_DYNBLOCK
+ luaCtx.writeFunction("addBPFFilterDynBlocks", [](const std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& addrs, const std::shared_ptr<DynBPFFilter>& dynbpf, boost::optional<int> seconds, boost::optional<std::string> msg) {
+ if (!dynbpf) {
+ return;
+ }
+ setLuaSideEffect();
+ timespec now{};
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ timespec until{now};
+ int actualSeconds = seconds ? *seconds : 10;
+ until.tv_sec += actualSeconds;
+ for (const auto& capair : addrs) {
+ if (dynbpf->block(capair.first, until)) {
+ warnlog("Inserting eBPF dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg ? *msg : "");
+ }
+ }
+ });
+#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+#endif /* DISABLE_DYNBLOCKS */
+
+#endif /* HAVE_EBPF */
+
+ luaCtx.writeFunction<LuaAssociativeTable<uint64_t>()>("getStatisticsCounters", []() {
+ setLuaNoSideEffect();
+ std::unordered_map<string, uint64_t> res;
+ {
+ auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+ res.reserve(entries->size());
+ for (const auto& entry : *entries) {
+ if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+ res[entry.d_name] = (*val)->load();
+ }
+ }
+ }
+ return res;
+ });
+
+ luaCtx.writeFunction("includeDirectory", [&luaCtx](const std::string& dirname) {
+ if (!checkConfigurationTime("includeDirectory")) {
+ return;
+ }
+ if (g_included) {
+ errlog("includeDirectory() cannot be used recursively!");
+ g_outputBuffer = "includeDirectory() cannot be used recursively!\n";
+ return;
+ }
+
+ struct stat dirStat
+ {
+ };
+ if (stat(dirname.c_str(), &dirStat) != 0) {
+ errlog("The included directory %s does not exist!", dirname.c_str());
+ g_outputBuffer = "The included directory " + dirname + " does not exist!";
+ return;
+ }
+
+ if (!S_ISDIR(dirStat.st_mode)) {
+ errlog("The included directory %s is not a directory!", dirname.c_str());
+ g_outputBuffer = "The included directory " + dirname + " is not a directory!";
+ return;
+ }
+
+ std::vector<std::string> files;
+ auto directoryError = pdns::visit_directory(dirname, [&dirname, &files]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+ if (boost::starts_with(name, ".")) {
+ return true;
+ }
+ if (boost::ends_with(name, ".conf")) {
+ std::ostringstream namebuf;
+ namebuf << dirname << "/" << name;
+ struct stat fileStat
+ {
+ };
+ if (stat(namebuf.str().c_str(), &fileStat) == 0 && S_ISREG(fileStat.st_mode)) {
+ files.push_back(namebuf.str());
+ }
+ }
+ return true;
+ });
+
+ if (directoryError) {
+ errlog("Error opening included directory: %s!", *directoryError);
+ g_outputBuffer = "Error opening included directory: " + *directoryError + "!";
+ return;
+ }
+
+ std::sort(files.begin(), files.end());
+
+ g_included = true;
+
+ for (const auto& file : files) {
+ std::ifstream ifs(file);
+ if (!ifs) {
+ warnlog("Unable to read configuration from '%s'", file);
+ }
+ else {
+ vinfolog("Read configuration from '%s'", file);
+ }
+
+ try {
+ luaCtx.executeCode(ifs);
+ }
+ catch (...) {
+ g_included = false;
+ throw;
+ }
+
+ luaCtx.executeCode(ifs);
+ }
+
+ g_included = false;
+ });
+
+ luaCtx.writeFunction("setAPIWritable", [](bool writable, boost::optional<std::string> apiConfigDir) {
+ setLuaSideEffect();
+ g_apiReadWrite = writable;
+ if (apiConfigDir) {
+ if (!(*apiConfigDir).empty()) {
+ g_apiConfigDirectory = *apiConfigDir;
+ }
+ else {
+ errlog("The API configuration directory value cannot be empty!");
+ g_outputBuffer = "The API configuration directory value cannot be empty!";
+ }
+ }
+ });
+
+ luaCtx.writeFunction("setServFailWhenNoServer", [](bool servfail) {
+ setLuaSideEffect();
+ g_servFailOnNoPolicy = servfail;
+ });
+
+ luaCtx.writeFunction("setRoundRobinFailOnNoServer", [](bool fail) {
+ setLuaSideEffect();
+ g_roundrobinFailOnNoServer = fail;
+ });
+
+ luaCtx.writeFunction("setConsistentHashingBalancingFactor", [](double factor) {
+ setLuaSideEffect();
+ if (factor >= 1.0) {
+ g_consistentHashBalancingFactor = factor;
+ }
+ else {
+ errlog("Invalid value passed to setConsistentHashingBalancingFactor()!");
+ g_outputBuffer = "Invalid value passed to setConsistentHashingBalancingFactor()!\n";
+ return;
+ }
+ });
+
+ luaCtx.writeFunction("setWeightedBalancingFactor", [](double factor) {
+ setLuaSideEffect();
+ if (factor >= 1.0) {
+ g_weightedBalancingFactor = factor;
+ }
+ else {
+ errlog("Invalid value passed to setWeightedBalancingFactor()!");
+ g_outputBuffer = "Invalid value passed to setWeightedBalancingFactor()!\n";
+ return;
+ }
+ });
+
+ luaCtx.writeFunction("setRingBuffersSize", [client](uint64_t capacity, boost::optional<uint64_t> numberOfShards) {
+ setLuaSideEffect();
+ if (!checkConfigurationTime("setRingBuffersSize")) {
+ return;
+ }
+ if (!client) {
+ g_rings.setCapacity(capacity, numberOfShards ? *numberOfShards : 10);
+ }
+ else {
+ g_rings.setCapacity(0, 1);
+ }
+ });
+
+ luaCtx.writeFunction("setRingBuffersLockRetries", [](uint64_t retries) {
+ setLuaSideEffect();
+ g_rings.setNumberOfLockRetries(retries);
+ });
+
+ luaCtx.writeFunction("setRingBuffersOptions", [](const LuaAssociativeTable<boost::variant<bool, uint64_t>>& options) {
+ setLuaSideEffect();
+ if (!checkConfigurationTime("setRingBuffersOptions")) {
+ return;
+ }
+ if (options.count("lockRetries") > 0) {
+ auto retries = boost::get<uint64_t>(options.at("lockRetries"));
+ g_rings.setNumberOfLockRetries(retries);
+ }
+ if (options.count("recordQueries") > 0) {
+ auto record = boost::get<bool>(options.at("recordQueries"));
+ g_rings.setRecordQueries(record);
+ }
+ if (options.count("recordResponses") > 0) {
+ auto record = boost::get<bool>(options.at("recordResponses"));
+ g_rings.setRecordResponses(record);
+ }
+ });
+
+ luaCtx.writeFunction("setWHashedPertubation", [](uint64_t perturb) {
+ setLuaSideEffect();
+ checkParameterBound("setWHashedPertubation", perturb, std::numeric_limits<uint32_t>::max());
+ g_hashperturb = perturb;
+ });
+
+ luaCtx.writeFunction("setTCPInternalPipeBufferSize", [](uint64_t size) { g_tcpInternalPipeBufferSize = size; });
+ luaCtx.writeFunction("setTCPFastOpenKey", [](const std::string& keyString) {
+ setLuaSideEffect();
+ std::array<uint32_t, 4> key{};
+ // NOLINTNEXTLINE(readability-container-data-pointer)
+ auto ret = sscanf(keyString.c_str(), "%" SCNx32 "-%" SCNx32 "-%" SCNx32 "-%" SCNx32, &key[0], &key[1], &key[2], &key[3]);
+ if (ret != 4) {
+ g_outputBuffer = "Invalid value passed to setTCPFastOpenKey()!\n";
+ return;
+ }
+ extern vector<uint32_t> g_TCPFastOpenKey;
+ for (const auto byte : key) {
+ g_TCPFastOpenKey.push_back(byte);
+ }
+ });
+
+#ifdef HAVE_NET_SNMP
+ luaCtx.writeFunction("snmpAgent", [client, configCheck](bool enableTraps, boost::optional<std::string> daemonSocket) {
+ if (client || configCheck) {
+ return;
+ }
+ if (!checkConfigurationTime("snmpAgent")) {
+ return;
+ }
+ if (g_snmpEnabled) {
+ errlog("snmpAgent() cannot be used twice!");
+ g_outputBuffer = "snmpAgent() cannot be used twice!\n";
+ return;
+ }
+
+ g_snmpEnabled = true;
+ g_snmpTrapsEnabled = enableTraps;
+ g_snmpAgent = std::make_unique<DNSDistSNMPAgent>("dnsdist", daemonSocket ? *daemonSocket : std::string());
+ });
+
+ luaCtx.writeFunction("sendCustomTrap", [](const std::string& str) {
+ if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendCustomTrap(str);
+ }
+ });
+#endif /* HAVE_NET_SNMP */
+
+#ifndef DISABLE_POLICIES_BINDINGS
+ luaCtx.writeFunction("setServerPolicy", [](const std::shared_ptr<ServerPolicy>& policy) {
+ setLuaSideEffect();
+ g_policy.setState(*policy);
+ });
+
+ luaCtx.writeFunction("setServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy) {
+ setLuaSideEffect();
+ g_policy.setState(ServerPolicy{name, std::move(policy), true});
+ });
+
+ luaCtx.writeFunction("setServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy) {
+ setLuaSideEffect();
+ auto pol = ServerPolicy(name, std::move(policy));
+ g_policy.setState(std::move(pol));
+ });
+
+ luaCtx.writeFunction("setServerPolicyLuaFFIPerThread", [](const string& name, const std::string& policyCode) {
+ setLuaSideEffect();
+ auto pol = ServerPolicy(name, policyCode);
+ g_policy.setState(std::move(pol));
+ });
+
+ luaCtx.writeFunction("showServerPolicy", []() {
+ setLuaSideEffect();
+ g_outputBuffer = g_policy.getLocal()->getName() + "\n";
+ });
+
+ luaCtx.writeFunction("setPoolServerPolicy", [](const std::shared_ptr<ServerPolicy>& policy, const string& pool) {
+ setLuaSideEffect();
+ auto localPools = g_pools.getCopy();
+ setPoolPolicy(localPools, pool, policy);
+ g_pools.setState(localPools);
+ });
+
+ luaCtx.writeFunction("setPoolServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy, const string& pool) {
+ setLuaSideEffect();
+ auto localPools = g_pools.getCopy();
+ setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, std::move(policy), true}));
+ g_pools.setState(localPools);
+ });
+
+ luaCtx.writeFunction("setPoolServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy, const string& pool) {
+ setLuaSideEffect();
+ auto localPools = g_pools.getCopy();
+ setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, std::move(policy)}));
+ g_pools.setState(localPools);
+ });
+
+ luaCtx.writeFunction("setPoolServerPolicyLuaFFIPerThread", [](const string& name, const std::string& policyCode, const std::string& pool) {
+ setLuaSideEffect();
+ auto localPools = g_pools.getCopy();
+ setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, policyCode}));
+ g_pools.setState(localPools);
+ });
+
+ luaCtx.writeFunction("showPoolServerPolicy", [](const std::string& pool) {
+ setLuaSideEffect();
+ auto localPools = g_pools.getCopy();
+ auto poolObj = getPool(localPools, pool);
+ if (poolObj->policy == nullptr) {
+ g_outputBuffer = g_policy.getLocal()->getName() + "\n";
+ }
+ else {
+ g_outputBuffer = poolObj->policy->getName() + "\n";
+ }
+ });
+#endif /* DISABLE_POLICIES_BINDINGS */
+
+ luaCtx.writeFunction("setTCPDownstreamCleanupInterval", [](uint64_t interval) {
+ setLuaSideEffect();
+ checkParameterBound("setTCPDownstreamCleanupInterval", interval);
+ setTCPDownstreamCleanupInterval(interval);
+ });
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ luaCtx.writeFunction("setDoHDownstreamCleanupInterval", [](uint64_t interval) {
+ setLuaSideEffect();
+ checkParameterBound("setDoHDownstreamCleanupInterval", interval);
+ setDoHDownstreamCleanupInterval(interval);
+ });
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+
+ luaCtx.writeFunction("setTCPDownstreamMaxIdleTime", [](uint64_t max) {
+ setLuaSideEffect();
+ checkParameterBound("setTCPDownstreamMaxIdleTime", max);
+ setTCPDownstreamMaxIdleTime(max);
+ });
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ luaCtx.writeFunction("setDoHDownstreamMaxIdleTime", [](uint64_t max) {
+ setLuaSideEffect();
+ checkParameterBound("setDoHDownstreamMaxIdleTime", max);
+ setDoHDownstreamMaxIdleTime(max);
+ });
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+
+ luaCtx.writeFunction("setConsoleConnectionsLogging", [](bool enabled) {
+ g_logConsoleConnections = enabled;
+ });
+
+ luaCtx.writeFunction("setConsoleOutputMaxMsgSize", [](uint64_t size) {
+ checkParameterBound("setConsoleOutputMaxMsgSize", size, std::numeric_limits<uint32_t>::max());
+ g_consoleOutputMsgMaxSize = size;
+ });
+
+ luaCtx.writeFunction("setProxyProtocolACL", [](LuaTypeOrArrayOf<std::string> inp) {
+ if (!checkConfigurationTime("setProxyProtocolACL")) {
+ return;
+ }
+ setLuaSideEffect();
+ NetmaskGroup nmg;
+ if (auto* str = boost::get<string>(&inp)) {
+ nmg.addMask(*str);
+ }
+ else {
+ for (const auto& entry : boost::get<LuaArray<std::string>>(inp)) {
+ nmg.addMask(entry.second);
+ }
+ }
+ g_proxyProtocolACL = std::move(nmg);
+ });
+
+ luaCtx.writeFunction("setProxyProtocolApplyACLToProxiedClients", [](bool apply) {
+ if (!checkConfigurationTime("setProxyProtocolApplyACLToProxiedClients")) {
+ return;
+ }
+ setLuaSideEffect();
+ g_applyACLToProxiedClients = apply;
+ });
+
+ luaCtx.writeFunction("setProxyProtocolMaximumPayloadSize", [](uint64_t size) {
+ if (!checkConfigurationTime("setProxyProtocolMaximumPayloadSize")) {
+ return;
+ }
+ setLuaSideEffect();
+ g_proxyProtocolMaximumSize = std::max(static_cast<uint64_t>(16), size);
+ });
+
+#ifndef DISABLE_RECVMMSG
+ luaCtx.writeFunction("setUDPMultipleMessagesVectorSize", [](uint64_t vSize) {
+ if (!checkConfigurationTime("setUDPMultipleMessagesVectorSize")) {
+ return;
+ }
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+ setLuaSideEffect();
+ g_udpVectorSize = vSize;
+#else
+ errlog("recvmmsg() support is not available!");
+ g_outputBuffer = "recvmmsg support is not available!\n";
+#endif
+ });
+#endif /* DISABLE_RECVMMSG */
+
+ luaCtx.writeFunction("setAddEDNSToSelfGeneratedResponses", [](bool add) {
+ g_addEDNSToSelfGeneratedResponses = add;
+ });
+
+ luaCtx.writeFunction("setPayloadSizeOnSelfGeneratedAnswers", [](uint64_t payloadSize) {
+ if (payloadSize < 512) {
+ warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!");
+ g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!";
+ payloadSize = 512;
+ }
+ if (payloadSize > s_udpIncomingBufferSize) {
+ warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to %d instead!", s_udpIncomingBufferSize);
+ g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to " + std::to_string(s_udpIncomingBufferSize) + " instead";
+ payloadSize = s_udpIncomingBufferSize;
+ }
+ g_PayloadSizeSelfGenAnswers = payloadSize;
+ });
+
+#ifndef DISABLE_SECPOLL
+ luaCtx.writeFunction("showSecurityStatus", []() {
+ setLuaNoSideEffect();
+ g_outputBuffer = std::to_string(dnsdist::metrics::g_stats.securityStatus) + "\n";
+ });
+
+ luaCtx.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) {
+ if (!checkConfigurationTime("setSecurityPollSuffix")) {
+ return;
+ }
+ g_secPollSuffix = suffix;
+ });
+
+ luaCtx.writeFunction("setSecurityPollInterval", [](time_t newInterval) {
+ if (newInterval <= 0) {
+ warnlog("setSecurityPollInterval() should be > 0, skipping");
+ g_outputBuffer = "setSecurityPollInterval() should be > 0, skipping";
+ }
+
+ g_secPollInterval = newInterval;
+ });
+#endif /* DISABLE_SECPOLL */
+
+ luaCtx.writeFunction("setSyslogFacility", [](boost::variant<int, std::string> facility) {
+ if (!checkConfigurationTime("setSyslogFacility")) {
+ return;
+ }
+ setLuaSideEffect();
+ if (facility.type() == typeid(std::string)) {
+ static std::map<std::string, int> const facilities = {
+ {"local0", LOG_LOCAL0},
+ {"log_local0", LOG_LOCAL0},
+ {"local1", LOG_LOCAL1},
+ {"log_local1", LOG_LOCAL1},
+ {"local2", LOG_LOCAL2},
+ {"log_local2", LOG_LOCAL2},
+ {"local3", LOG_LOCAL3},
+ {"log_local3", LOG_LOCAL3},
+ {"local4", LOG_LOCAL4},
+ {"log_local4", LOG_LOCAL4},
+ {"local5", LOG_LOCAL5},
+ {"log_local5", LOG_LOCAL5},
+ {"local6", LOG_LOCAL6},
+ {"log_local6", LOG_LOCAL6},
+ {"local7", LOG_LOCAL7},
+ {"log_local7", LOG_LOCAL7},
+ /* most of these likely make very little sense
+ for dnsdist, but why not? */
+ {"kern", LOG_KERN},
+ {"log_kern", LOG_KERN},
+ {"user", LOG_USER},
+ {"log_user", LOG_USER},
+ {"mail", LOG_MAIL},
+ {"log_mail", LOG_MAIL},
+ {"daemon", LOG_DAEMON},
+ {"log_daemon", LOG_DAEMON},
+ {"auth", LOG_AUTH},
+ {"log_auth", LOG_AUTH},
+ {"syslog", LOG_SYSLOG},
+ {"log_syslog", LOG_SYSLOG},
+ {"lpr", LOG_LPR},
+ {"log_lpr", LOG_LPR},
+ {"news", LOG_NEWS},
+ {"log_news", LOG_NEWS},
+ {"uucp", LOG_UUCP},
+ {"log_uucp", LOG_UUCP},
+ {"cron", LOG_CRON},
+ {"log_cron", LOG_CRON},
+ {"authpriv", LOG_AUTHPRIV},
+ {"log_authpriv", LOG_AUTHPRIV},
+ {"ftp", LOG_FTP},
+ {"log_ftp", LOG_FTP}};
+ auto facilityStr = boost::get<std::string>(facility);
+ toLowerInPlace(facilityStr);
+ auto facilityIt = facilities.find(facilityStr);
+ if (facilityIt == facilities.end()) {
+ g_outputBuffer = "Unknown facility '" + facilityStr + "' passed to setSyslogFacility()!\n";
+ return;
+ }
+ setSyslogFacility(facilityIt->second);
+ }
+ else {
+ setSyslogFacility(boost::get<int>(facility));
+ }
+ });
+
+ typedef std::unordered_map<std::string, std::string> tlscertificateopts_t;
+ luaCtx.writeFunction("newTLSCertificate", [client](const std::string& cert, boost::optional<tlscertificateopts_t> opts) {
+ std::shared_ptr<TLSCertKeyPair> result = nullptr;
+ if (client) {
+ return result;
+ }
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
+ std::optional<std::string> key;
+ std::optional<std::string> password;
+ if (opts) {
+ if (opts->count("key") != 0) {
+ key = boost::get<const string>((*opts)["key"]);
+ }
+ if (opts->count("password") != 0) {
+ password = boost::get<const string>((*opts)["password"]);
+ }
+ }
+ result = std::make_shared<TLSCertKeyPair>(cert, std::move(key), std::move(password));
+#endif
+ return result;
+ });
+
+ luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::optional<boost::variant<std::string, LuaArray<std::string>>> keyFiles, boost::optional<LuaTypeOrArrayOf<std::string>> urls, boost::optional<localbind_t> vars) {
+ if (client) {
+ return;
+ }
+#ifdef HAVE_DNS_OVER_HTTPS
+ if (!checkConfigurationTime("addDOHLocal")) {
+ return;
+ }
+ setLuaSideEffect();
+
+ auto frontend = std::make_shared<DOHFrontend>();
+ if (getOptionalValue<std::string>(vars, "library", frontend->d_library) == 0) {
+#ifdef HAVE_NGHTTP2
+ frontend->d_library = "nghttp2";
+#else /* HAVE_NGHTTP2 */
+ frontend->d_library = "h2o";
+#endif /* HAVE_NGHTTP2 */
+ }
+ if (frontend->d_library == "h2o") {
+#ifdef HAVE_LIBH2OEVLOOP
+ frontend = std::make_shared<H2ODOHFrontend>();
+ // we _really_ need to set it again, as we just replaced the generic frontend by a new one
+ frontend->d_library = "h2o";
+#else /* HAVE_LIBH2OEVLOOP */
+ errlog("DOH bind %s is configured to use libh2o but the library is not available", addr);
+ return;
+#endif /* HAVE_LIBH2OEVLOOP */
+ }
+ else if (frontend->d_library == "nghttp2") {
+#ifndef HAVE_NGHTTP2
+ errlog("DOH bind %s is configured to use nghttp2 but the library is not available", addr);
+ return;
+#endif /* HAVE_NGHTTP2 */
+ }
+ else {
+ errlog("DOH bind %s is configured to use an unknown library ('%s')", addr, frontend->d_library);
+ return;
+ }
+
+ bool useTLS = true;
+ if (certFiles && !certFiles->empty()) {
+ if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) {
+ return;
+ }
+
+ frontend->d_tlsContext.d_addr = ComboAddress(addr, 443);
+ }
+ else {
+ frontend->d_tlsContext.d_addr = ComboAddress(addr, 80);
+ infolog("No certificate provided for DoH endpoint %s, running in DNS over HTTP mode instead of DNS over HTTPS", frontend->d_tlsContext.d_addr.toStringWithPort());
+ useTLS = false;
+ }
+
+ if (urls) {
+ if (urls->type() == typeid(std::string)) {
+ frontend->d_urls.insert(boost::get<std::string>(*urls));
+ }
+ else if (urls->type() == typeid(LuaArray<std::string>)) {
+ auto urlsVect = boost::get<LuaArray<std::string>>(*urls);
+ for (const auto& url : urlsVect) {
+ frontend->d_urls.insert(url.second);
+ }
+ }
+ }
+ else {
+ frontend->d_urls.insert("/dns-query");
+ }
+
+ bool reusePort = false;
+ int tcpFastOpenQueueSize = 0;
+ int tcpListenQueueSize = 0;
+ uint64_t maxInFlightQueriesPerConn = 0;
+ uint64_t tcpMaxConcurrentConnections = 0;
+ std::string interface;
+ std::set<int> cpus;
+ std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+ bool enableProxyProtocol = true;
+
+ if (vars) {
+ parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+ getOptionalValue<int>(vars, "idleTimeout", frontend->d_idleTimeout);
+ getOptionalValue<std::string>(vars, "serverTokens", frontend->d_serverTokens);
+ getOptionalValue<std::string>(vars, "provider", frontend->d_tlsContext.d_provider);
+ boost::algorithm::to_lower(frontend->d_tlsContext.d_provider);
+ getOptionalValue<bool>(vars, "proxyProtocolOutsideTLS", frontend->d_tlsContext.d_proxyProtocolOutsideTLS);
+
+ LuaAssociativeTable<std::string> customResponseHeaders;
+ if (getOptionalValue<decltype(customResponseHeaders)>(vars, "customResponseHeaders", customResponseHeaders) > 0) {
+ for (auto const& headerMap : customResponseHeaders) {
+ auto headerResponse = std::pair(boost::to_lower_copy(headerMap.first), headerMap.second);
+ frontend->d_customResponseHeaders.insert(headerResponse);
+ }
+ }
+
+ getOptionalValue<bool>(vars, "sendCacheControlHeaders", frontend->d_sendCacheControlHeaders);
+ getOptionalValue<bool>(vars, "keepIncomingHeaders", frontend->d_keepIncomingHeaders);
+ getOptionalValue<bool>(vars, "trustForwardedForHeader", frontend->d_trustForwardedForHeader);
+ getOptionalValue<bool>(vars, "earlyACLDrop", frontend->d_earlyACLDrop);
+ getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
+ getOptionalValue<bool>(vars, "exactPathMatching", frontend->d_exactPathMatching);
+
+ LuaArray<std::string> addresses;
+ if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
+ for (const auto& [_, add] : addresses) {
+ try {
+ ComboAddress address(add);
+ additionalAddresses.emplace_back(address, -1);
+ }
+ catch (const PDNSException& e) {
+ errlog("Unable to parse additional address %s for DOH bind: %s", add, e.reason);
+ return;
+ }
+ }
+ }
+
+ parseTLSConfig(frontend->d_tlsContext.d_tlsConfig, "addDOHLocal", vars);
+
+ bool ignoreTLSConfigurationErrors = false;
+ if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+ // we are asked to try to load the certificates so we can return a potential error
+ // and properly ignore the frontend before actually launching it
+ try {
+ std::map<int, std::string> ocspResponses = {};
+ auto ctx = libssl_init_server_context(frontend->d_tlsContext.d_tlsConfig, ocspResponses);
+ }
+ catch (const std::runtime_error& e) {
+ errlog("Ignoring DoH frontend: '%s'", e.what());
+ return;
+ }
+ }
+
+ checkAllParametersConsumed("addDOHLocal", vars);
+ }
+
+ if (useTLS && frontend->d_library == "nghttp2") {
+ if (!frontend->d_tlsContext.d_provider.empty()) {
+ vinfolog("Loading TLS provider '%s'", frontend->d_tlsContext.d_provider);
+ }
+ else {
+#ifdef HAVE_LIBSSL
+ const std::string provider("openssl");
+#else
+ const std::string provider("gnutls");
+#endif
+ vinfolog("Loading default TLS provider '%s'", provider);
+ }
+ }
+
+ g_dohlocals.push_back(frontend);
+ auto clientState = std::make_unique<ClientState>(frontend->d_tlsContext.d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ clientState->dohFrontend = std::move(frontend);
+ clientState->d_additionalAddresses = std::move(additionalAddresses);
+
+ if (tcpListenQueueSize > 0) {
+ clientState->tcpListenQueueSize = tcpListenQueueSize;
+ }
+ if (tcpMaxConcurrentConnections > 0) {
+ clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+ }
+ g_frontends.push_back(std::move(clientState));
+#else /* HAVE_DNS_OVER_HTTPS */
+ throw std::runtime_error("addDOHLocal() called but DNS over HTTPS support is not present!");
+#endif /* HAVE_DNS_OVER_HTTPS */
+ });
+
+ // NOLINTNEXTLINE(performance-unnecessary-value-param): somehow clang-tidy gets confused about the fact vars could be const while it cannot
+ luaCtx.writeFunction("addDOH3Local", [client](const std::string& addr, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const boost::variant<std::string, LuaArray<std::string>>& keyFiles, boost::optional<localbind_t> vars) {
+ if (client) {
+ return;
+ }
+#ifdef HAVE_DNS_OVER_HTTP3
+ if (!checkConfigurationTime("addDOH3Local")) {
+ return;
+ }
+ setLuaSideEffect();
+
+ auto frontend = std::make_shared<DOH3Frontend>();
+ if (!loadTLSCertificateAndKeys("addDOH3Local", frontend->d_quicheParams.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+ return;
+ }
+ frontend->d_local = ComboAddress(addr, 443);
+
+ bool reusePort = false;
+ int tcpFastOpenQueueSize = 0;
+ int tcpListenQueueSize = 0;
+ uint64_t maxInFlightQueriesPerConn = 0;
+ uint64_t tcpMaxConcurrentConnections = 0;
+ std::string interface;
+ std::set<int> cpus;
+ std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+ bool enableProxyProtocol = true;
+
+ if (vars) {
+ parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+ if (maxInFlightQueriesPerConn > 0) {
+ frontend->d_quicheParams.d_maxInFlight = maxInFlightQueriesPerConn;
+ }
+ getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
+ getOptionalValue<int>(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout);
+ getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile);
+ {
+ std::string valueStr;
+ if (getOptionalValue<std::string>(vars, "congestionControlAlgo", valueStr) > 0) {
+ if (dnsdist::doq::s_available_cc_algorithms.count(valueStr) > 0) {
+ frontend->d_quicheParams.d_ccAlgo = valueStr;
+ }
+ else {
+ warnlog("Ignoring unknown value '%s' for 'congestionControlAlgo' on 'addDOH3Local'", valueStr);
+ }
+ }
+ }
+ parseTLSConfig(frontend->d_quicheParams.d_tlsConfig, "addDOH3Local", vars);
+
+ bool ignoreTLSConfigurationErrors = false;
+ if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+ // we are asked to try to load the certificates so we can return a potential error
+ // and properly ignore the frontend before actually launching it
+ try {
+ std::map<int, std::string> ocspResponses = {};
+ auto ctx = libssl_init_server_context(frontend->d_quicheParams.d_tlsConfig, ocspResponses);
+ }
+ catch (const std::runtime_error& e) {
+ errlog("Ignoring DoH3 frontend: '%s'", e.what());
+ return;
+ }
+ }
+
+ checkAllParametersConsumed("addDOH3Local", vars);
+ }
+ g_doh3locals.push_back(frontend);
+ auto clientState = std::make_unique<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ clientState->doh3Frontend = frontend;
+ clientState->d_additionalAddresses = std::move(additionalAddresses);
+
+ g_frontends.push_back(std::move(clientState));
+#else
+ throw std::runtime_error("addDOH3Local() called but DNS over HTTP/3 support is not present!");
+#endif
+ });
+
+ // NOLINTNEXTLINE(performance-unnecessary-value-param): somehow clang-tidy gets confused about the fact vars could be const while it cannot
+ luaCtx.writeFunction("addDOQLocal", [client](const std::string& addr, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const boost::variant<std::string, LuaArray<std::string>>& keyFiles, boost::optional<localbind_t> vars) {
+ if (client) {
+ return;
+ }
+#ifdef HAVE_DNS_OVER_QUIC
+ if (!checkConfigurationTime("addDOQLocal")) {
+ return;
+ }
+ setLuaSideEffect();
+
+ auto frontend = std::make_shared<DOQFrontend>();
+ if (!loadTLSCertificateAndKeys("addDOQLocal", frontend->d_quicheParams.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+ return;
+ }
+ frontend->d_local = ComboAddress(addr, 853);
+
+ bool reusePort = false;
+ int tcpFastOpenQueueSize = 0;
+ int tcpListenQueueSize = 0;
+ uint64_t maxInFlightQueriesPerConn = 0;
+ uint64_t tcpMaxConcurrentConnections = 0;
+ std::string interface;
+ std::set<int> cpus;
+ std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+ bool enableProxyProtocol = true;
+
+ if (vars) {
+ parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+ if (maxInFlightQueriesPerConn > 0) {
+ frontend->d_quicheParams.d_maxInFlight = maxInFlightQueriesPerConn;
+ }
+ getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
+ getOptionalValue<int>(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout);
+ getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile);
+ {
+ std::string valueStr;
+ if (getOptionalValue<std::string>(vars, "congestionControlAlgo", valueStr) > 0) {
+ if (dnsdist::doq::s_available_cc_algorithms.count(valueStr) > 0) {
+ frontend->d_quicheParams.d_ccAlgo = std::move(valueStr);
+ }
+ else {
+ warnlog("Ignoring unknown value '%s' for 'congestionControlAlgo' on 'addDOQLocal'", valueStr);
+ }
+ }
+ }
+ parseTLSConfig(frontend->d_quicheParams.d_tlsConfig, "addDOQLocal", vars);
+
+ bool ignoreTLSConfigurationErrors = false;
+ if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+ // we are asked to try to load the certificates so we can return a potential error
+ // and properly ignore the frontend before actually launching it
+ try {
+ std::map<int, std::string> ocspResponses = {};
+ auto ctx = libssl_init_server_context(frontend->d_quicheParams.d_tlsConfig, ocspResponses);
+ }
+ catch (const std::runtime_error& e) {
+ errlog("Ignoring DoQ frontend: '%s'", e.what());
+ return;
+ }
+ }
+
+ checkAllParametersConsumed("addDOQLocal", vars);
+ }
+ g_doqlocals.push_back(frontend);
+ auto clientState = std::make_unique<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ clientState->doqFrontend = std::move(frontend);
+ clientState->d_additionalAddresses = std::move(additionalAddresses);
+
+ g_frontends.push_back(std::move(clientState));
+#else
+ throw std::runtime_error("addDOQLocal() called but DNS over QUIC support is not present!");
+#endif
+ });
+
+ luaCtx.writeFunction("showDOQFrontends", []() {
+#ifdef HAVE_DNS_OVER_QUIC
+ setLuaNoSideEffect();
+ try {
+ ostringstream ret;
+ boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d");
+ ret << (fmt % "#" % "Address" % "Bad Version" % "Invalid Token" % "Errors" % "Valid") << endl;
+ size_t counter = 0;
+ for (const auto& ctx : g_doqlocals) {
+ ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_doqUnsupportedVersionErrors % ctx->d_doqInvalidTokensReceived % ctx->d_errorResponses % ctx->d_validResponses) << endl;
+ counter++;
+ }
+ g_outputBuffer = ret.str();
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = e.what();
+ throw;
+ }
+#else
+ g_outputBuffer = "DNS over QUIC support is not present!\n";
+#endif
+ });
+
+#ifdef HAVE_DNS_OVER_QUIC
+ luaCtx.writeFunction("getDOQFrontend", [client](uint64_t index) {
+ std::shared_ptr<DOQFrontend> result = nullptr;
+ if (client) {
+ return result;
+ }
+ setLuaNoSideEffect();
+ try {
+ if (index < g_doqlocals.size()) {
+ result = g_doqlocals.at(index);
+ }
+ else {
+ errlog("Error: trying to get DOQ frontend with index %d but we only have %d frontend(s)\n", index, g_doqlocals.size());
+ g_outputBuffer = "Error: trying to get DOQ frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_doqlocals.size()) + " frontend(s)\n";
+ }
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Error while trying to get DOQ frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+ errlog("Error while trying to get DOQ frontend with index %d: %s\n", index, string(e.what()));
+ }
+ return result;
+ });
+
+ luaCtx.writeFunction("getDOQFrontendCount", []() {
+ setLuaNoSideEffect();
+ return g_doqlocals.size();
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DOQFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<DOQFrontend>& frontend) {
+ if (frontend != nullptr) {
+ frontend->reloadCertificates();
+ }
+ });
+#endif
+
+ luaCtx.writeFunction("showDOHFrontends", []() {
+#ifdef HAVE_DNS_OVER_HTTPS
+ setLuaNoSideEffect();
+ try {
+ ostringstream ret;
+ boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d");
+ ret << (fmt % "#" % "Address" % "HTTP" % "HTTP/1" % "HTTP/2" % "GET" % "POST" % "Bad" % "Errors" % "Redirects" % "Valid" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
+ size_t counter = 0;
+ for (const auto& ctx : g_dohlocals) {
+ ret << (fmt % counter % ctx->d_tlsContext.d_addr.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_http2Stats.d_nbQueries % ctx->d_getqueries % ctx->d_postqueries % ctx->d_badrequests % ctx->d_errorresponses % ctx->d_redirectresponses % ctx->d_validresponses % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
+ counter++;
+ }
+ g_outputBuffer = ret.str();
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = e.what();
+ throw;
+ }
+#else
+ g_outputBuffer = "DNS over HTTPS support is not present!\n";
+#endif
+ });
+
+ luaCtx.writeFunction("showDOH3Frontends", []() {
+#ifdef HAVE_DNS_OVER_HTTP3
+ setLuaNoSideEffect();
+ try {
+ ostringstream ret;
+ boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d");
+ ret << (fmt % "#" % "Address" % "Bad Version" % "Invalid Token" % "Errors" % "Valid") << endl;
+ size_t counter = 0;
+ for (const auto& ctx : g_doh3locals) {
+ ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_doh3UnsupportedVersionErrors % ctx->d_doh3InvalidTokensReceived % ctx->d_errorResponses % ctx->d_validResponses) << endl;
+ counter++;
+ }
+ g_outputBuffer = ret.str();
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = e.what();
+ throw;
+ }
+#else
+ g_outputBuffer = "DNS over HTTP3 support is not present!\n";
+#endif
+ });
+
+#ifdef HAVE_DNS_OVER_HTTP3
+ luaCtx.writeFunction("getDOH3Frontend", [client](uint64_t index) {
+ std::shared_ptr<DOH3Frontend> result = nullptr;
+ if (client) {
+ return result;
+ }
+ setLuaNoSideEffect();
+ try {
+ if (index < g_doh3locals.size()) {
+ result = g_doh3locals.at(index);
+ }
+ else {
+ errlog("Error: trying to get DOH3 frontend with index %d but we only have %d frontend(s)\n", index, g_doh3locals.size());
+ g_outputBuffer = "Error: trying to get DOH3 frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_doh3locals.size()) + " frontend(s)\n";
+ }
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Error while trying to get DOH3 frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+ errlog("Error while trying to get DOH3 frontend with index %d: %s\n", index, string(e.what()));
+ }
+ return result;
+ });
+
+ luaCtx.writeFunction("getDOH3FrontendCount", []() {
+ setLuaNoSideEffect();
+ return g_doh3locals.size();
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DOH3Frontend>::*)()>("reloadCertificates", [](const std::shared_ptr<DOH3Frontend>& frontend) {
+ if (frontend != nullptr) {
+ frontend->reloadCertificates();
+ }
+ });
+#endif
+
+ luaCtx.writeFunction("showDOHResponseCodes", []() {
+#ifdef HAVE_DNS_OVER_HTTPS
+ setLuaNoSideEffect();
+ try {
+ ostringstream ret;
+ boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d");
+ g_outputBuffer = "\n- HTTP/1:\n\n";
+ ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl;
+ size_t counter = 0;
+ for (const auto& ctx : g_dohlocals) {
+ ret << (fmt % counter % ctx->d_tlsContext.d_addr.toStringWithPort() % ctx->d_http1Stats.d_nb200Responses % ctx->d_http1Stats.d_nb400Responses % ctx->d_http1Stats.d_nb403Responses % ctx->d_http1Stats.d_nb500Responses % ctx->d_http1Stats.d_nb502Responses % ctx->d_http1Stats.d_nbOtherResponses) << endl;
+ counter++;
+ }
+ g_outputBuffer += ret.str();
+ ret.str("");
+
+ g_outputBuffer += "\n- HTTP/2:\n\n";
+ ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl;
+ counter = 0;
+ for (const auto& ctx : g_dohlocals) {
+ ret << (fmt % counter % ctx->d_tlsContext.d_addr.toStringWithPort() % ctx->d_http2Stats.d_nb200Responses % ctx->d_http2Stats.d_nb400Responses % ctx->d_http2Stats.d_nb403Responses % ctx->d_http2Stats.d_nb500Responses % ctx->d_http2Stats.d_nb502Responses % ctx->d_http2Stats.d_nbOtherResponses) << endl;
+ counter++;
+ }
+ g_outputBuffer += ret.str();
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = e.what();
+ throw;
+ }
+#else
+ g_outputBuffer = "DNS over HTTPS support is not present!\n";
+#endif
+ });
+
+ luaCtx.writeFunction("getDOHFrontend", [client](uint64_t index) {
+ std::shared_ptr<DOHFrontend> result = nullptr;
+ if (client) {
+ return result;
+ }
+#ifdef HAVE_DNS_OVER_HTTPS
+ setLuaNoSideEffect();
+ try {
+ if (index < g_dohlocals.size()) {
+ result = g_dohlocals.at(index);
+ }
+ else {
+ errlog("Error: trying to get DOH frontend with index %d but we only have %d frontend(s)\n", index, g_dohlocals.size());
+ g_outputBuffer = "Error: trying to get DOH frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_dohlocals.size()) + " frontend(s)\n";
+ }
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Error while trying to get DOH frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+ errlog("Error while trying to get DOH frontend with index %d: %s\n", index, string(e.what()));
+ }
+#else
+ g_outputBuffer="DNS over HTTPS support is not present!\n";
+#endif
+ return result;
+ });
+
+ luaCtx.writeFunction("getDOHFrontendCount", []() {
+ setLuaNoSideEffect();
+ return g_dohlocals.size();
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<DOHFrontend>& frontend) {
+ if (frontend != nullptr) {
+ frontend->reloadCertificates();
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles)>("loadNewCertificatesAndKeys", [](const std::shared_ptr<DOHFrontend>& frontend, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const boost::variant<std::string, LuaArray<std::string>>& keyFiles) {
+#ifdef HAVE_DNS_OVER_HTTPS
+ if (frontend != nullptr) {
+ if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+ frontend->reloadCertificates();
+ }
+ }
+#endif
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](const std::shared_ptr<DOHFrontend>& frontend) {
+ if (frontend != nullptr) {
+ frontend->rotateTicketsKey(time(nullptr));
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKeys", [](const std::shared_ptr<DOHFrontend>& frontend, const std::string& file) {
+ if (frontend != nullptr) {
+ frontend->loadTicketsKeys(file);
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const LuaArray<std::shared_ptr<DOHResponseMapEntry>>&)>("setResponsesMap", [](const std::shared_ptr<DOHFrontend>& frontend, const LuaArray<std::shared_ptr<DOHResponseMapEntry>>& map) {
+ if (frontend != nullptr) {
+ auto newMap = std::make_shared<std::vector<std::shared_ptr<DOHResponseMapEntry>>>();
+ newMap->reserve(map.size());
+
+ for (const auto& entry : map) {
+ newMap->push_back(entry.second);
+ }
+
+ frontend->d_responsesMap = std::move(newMap);
+ }
+ });
+
+ luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const LuaTypeOrArrayOf<std::string>& keyFiles, boost::optional<localbind_t> vars) {
+ if (client) {
+ return;
+ }
+#ifdef HAVE_DNS_OVER_TLS
+ if (!checkConfigurationTime("addTLSLocal")) {
+ return;
+ }
+ setLuaSideEffect();
+
+ auto frontend = std::make_shared<TLSFrontend>(TLSFrontend::ALPN::DoT);
+ if (!loadTLSCertificateAndKeys("addTLSLocal", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+ return;
+ }
+
+ bool reusePort = false;
+ int tcpFastOpenQueueSize = 0;
+ int tcpListenQueueSize = 0;
+ uint64_t maxInFlightQueriesPerConn = 0;
+ uint64_t tcpMaxConcurrentConns = 0;
+ std::string interface;
+ std::set<int> cpus;
+ std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+ bool enableProxyProtocol = true;
+
+ if (vars) {
+ parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConns, enableProxyProtocol);
+
+ getOptionalValue<std::string>(vars, "provider", frontend->d_provider);
+ boost::algorithm::to_lower(frontend->d_provider);
+ getOptionalValue<bool>(vars, "proxyProtocolOutsideTLS", frontend->d_proxyProtocolOutsideTLS);
+
+ LuaArray<std::string> addresses;
+ if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
+ for (const auto& [_, add] : addresses) {
+ try {
+ ComboAddress address(add);
+ additionalAddresses.emplace_back(address, -1);
+ }
+ catch (const PDNSException& e) {
+ errlog("Unable to parse additional address %s for DoT bind: %s", add, e.reason);
+ return;
+ }
+ }
+ }
+
+ parseTLSConfig(frontend->d_tlsConfig, "addTLSLocal", vars);
+
+ bool ignoreTLSConfigurationErrors = false;
+ if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+ // we are asked to try to load the certificates so we can return a potential error
+ // and properly ignore the frontend before actually launching it
+ try {
+ std::map<int, std::string> ocspResponses = {};
+ auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
+ }
+ catch (const std::runtime_error& e) {
+ errlog("Ignoring TLS frontend: '%s'", e.what());
+ return;
+ }
+ }
+
+ checkAllParametersConsumed("addTLSLocal", vars);
+ }
+
+ try {
+ frontend->d_addr = ComboAddress(addr, 853);
+ if (!frontend->d_provider.empty()) {
+ vinfolog("Loading TLS provider '%s'", frontend->d_provider);
+ }
+ else {
+#ifdef HAVE_LIBSSL
+ const std::string provider("openssl");
+#else
+ const std::string provider("gnutls");
+#endif
+ vinfolog("Loading default TLS provider '%s'", provider);
+ }
+ // only works pre-startup, so no sync necessary
+ auto clientState = std::make_unique<ClientState>(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+ clientState->tlsFrontend = frontend;
+ clientState->d_additionalAddresses = std::move(additionalAddresses);
+ if (tcpListenQueueSize > 0) {
+ clientState->tcpListenQueueSize = tcpListenQueueSize;
+ }
+ if (maxInFlightQueriesPerConn > 0) {
+ clientState->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+ }
+ if (tcpMaxConcurrentConns > 0) {
+ clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConns;
+ }
+
+ g_tlslocals.push_back(clientState->tlsFrontend);
+ g_frontends.push_back(std::move(clientState));
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Error: " + string(e.what()) + "\n";
+ }
+#else
+ throw std::runtime_error("addTLSLocal() called but DNS over TLS support is not present!");
+#endif
+ });
+
+ luaCtx.writeFunction("showTLSContexts", []() {
+#ifdef HAVE_DNS_OVER_TLS
+ setLuaNoSideEffect();
+ try {
+ ostringstream ret;
+ boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-14d %|40t|%4$-14d %|54t|%5$-21.21s");
+ // 1 2 3 4 5
+ ret << (fmt % "#" % "Address" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
+ size_t counter = 0;
+ for (const auto& ctx : g_tlslocals) {
+ ret << (fmt % counter % ctx->d_addr.toStringWithPort() % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
+ counter++;
+ }
+ g_outputBuffer = ret.str();
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = e.what();
+ throw;
+ }
+#else
+ g_outputBuffer = "DNS over TLS support is not present!\n";
+#endif
+ });
+
+ luaCtx.writeFunction("getTLSContext", [](uint64_t index) {
+ std::shared_ptr<TLSCtx> result = nullptr;
+#ifdef HAVE_DNS_OVER_TLS
+ setLuaNoSideEffect();
+ try {
+ if (index < g_tlslocals.size()) {
+ result = g_tlslocals.at(index)->getContext();
+ }
+ else {
+ errlog("Error: trying to get TLS context with index %d but we only have %d context(s)\n", index, g_tlslocals.size());
+ g_outputBuffer = "Error: trying to get TLS context with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " context(s)\n";
+ }
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Error while trying to get TLS context with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+ errlog("Error while trying to get TLS context with index %d: %s\n", index, string(e.what()));
+ }
+#else
+ g_outputBuffer="DNS over TLS support is not present!\n";
+#endif
+ return result;
+ });
+
+ luaCtx.writeFunction("getTLSFrontend", [](uint64_t index) {
+ std::shared_ptr<TLSFrontend> result = nullptr;
+#ifdef HAVE_DNS_OVER_TLS
+ setLuaNoSideEffect();
+ try {
+ if (index < g_tlslocals.size()) {
+ result = g_tlslocals.at(index);
+ }
+ else {
+ errlog("Error: trying to get TLS frontend with index %d but we only have %d frontends\n", index, g_tlslocals.size());
+ g_outputBuffer = "Error: trying to get TLS frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " frontend(s)\n";
+ }
+ }
+ catch (const std::exception& e) {
+ g_outputBuffer = "Error while trying to get TLS frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+ errlog("Error while trying to get TLS frontend with index %d: %s\n", index, string(e.what()));
+ }
+#else
+ g_outputBuffer="DNS over TLS support is not present!\n";
+#endif
+ return result;
+ });
+
+ luaCtx.writeFunction("getTLSFrontendCount", []() {
+ setLuaNoSideEffect();
+ return g_tlslocals.size();
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TLSCtx>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSCtx>& ctx) {
+ if (ctx != nullptr) {
+ ctx->rotateTicketsKey(time(nullptr));
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TLSCtx>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSCtx>& ctx, const std::string& file) {
+ if (ctx != nullptr) {
+ ctx->loadTicketsKeys(file);
+ }
+ });
+
+ luaCtx.registerFunction<std::string (std::shared_ptr<TLSFrontend>::*)() const>("getAddressAndPort", [](const std::shared_ptr<TLSFrontend>& frontend) {
+ if (frontend == nullptr) {
+ return std::string();
+ }
+ return frontend->d_addr.toStringWithPort();
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSFrontend>& frontend) {
+ if (frontend == nullptr) {
+ return;
+ }
+ auto ctx = frontend->getContext();
+ if (ctx) {
+ ctx->rotateTicketsKey(time(nullptr));
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSFrontend>& frontend, const std::string& file) {
+ if (frontend == nullptr) {
+ return;
+ }
+ auto ctx = frontend->getContext();
+ if (ctx) {
+ ctx->loadTicketsKeys(file);
+ }
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<TLSFrontend>& frontend) {
+ if (frontend == nullptr) {
+ return;
+ }
+ frontend->setupTLS();
+ });
+
+ luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>&, const LuaTypeOrArrayOf<std::string>&)>("loadNewCertificatesAndKeys", [](std::shared_ptr<TLSFrontend>& frontend, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const LuaTypeOrArrayOf<std::string>& keyFiles) {
+#ifdef HAVE_DNS_OVER_TLS
+ if (loadTLSCertificateAndKeys("TLSFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+ frontend->setupTLS();
+ }
+#endif
+ });
+
+ luaCtx.writeFunction("reloadAllCertificates", []() {
+ for (auto& frontend : g_frontends) {
+ if (!frontend) {
+ continue;
+ }
+ try {
+#ifdef HAVE_DNSCRYPT
+ if (frontend->dnscryptCtx) {
+ frontend->dnscryptCtx->reloadCertificates();
+ }
+#endif /* HAVE_DNSCRYPT */
+#ifdef HAVE_DNS_OVER_TLS
+ if (frontend->tlsFrontend) {
+ frontend->tlsFrontend->setupTLS();
+ }
+#endif /* HAVE_DNS_OVER_TLS */
+#ifdef HAVE_DNS_OVER_HTTPS
+ if (frontend->dohFrontend) {
+ frontend->dohFrontend->reloadCertificates();
+ }
+#endif /* HAVE_DNS_OVER_HTTPS */
+#ifdef HAVE_DNS_OVER_QUIC
+ if (frontend->doqFrontend) {
+ frontend->doqFrontend->reloadCertificates();
+ }
+#endif /* HAVE_DNS_OVER_QUIC */
+#ifdef HAVE_DNS_OVER_HTTP3
+ if (frontend->doh3Frontend) {
+ frontend->doh3Frontend->reloadCertificates();
+ }
+#endif /* HAVE_DNS_OVER_HTTP3 */
+ }
+ catch (const std::exception& e) {
+ errlog("Error reloading certificates for frontend %s: %s", frontend->local.toStringWithPort(), e.what());
+ }
+ }
+ });
+
+ luaCtx.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse = allow; });
+ luaCtx.writeFunction("setDropEmptyQueries", [](bool drop) { extern bool g_dropEmptyQueries; g_dropEmptyQueries = drop; });
+
+#if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN) && !defined(DISABLE_OCSP_STAPLING)
+ luaCtx.writeFunction("generateOCSPResponse", [client](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
+ if (client) {
+ return;
+ }
+
+ libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
+ });
+#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN && !DISABLE_OCSP_STAPLING */
+
+ luaCtx.writeFunction("addCapabilitiesToRetain", [](LuaTypeOrArrayOf<std::string> caps) {
+ if (!checkConfigurationTime("addCapabilitiesToRetain")) {
+ return;
+ }
+ setLuaSideEffect();
+ if (caps.type() == typeid(std::string)) {
+ g_capabilitiesToRetain.insert(boost::get<std::string>(caps));
+ }
+ else if (caps.type() == typeid(LuaArray<std::string>)) {
+ for (const auto& cap : boost::get<LuaArray<std::string>>(caps)) {
+ g_capabilitiesToRetain.insert(cap.second);
+ }
+ }
+ });
+
+ luaCtx.writeFunction("setUDPSocketBufferSizes", [client](uint64_t recv, uint64_t snd) {
+ if (client) {
+ return;
+ }
+ if (!checkConfigurationTime("setUDPSocketBufferSizes")) {
+ return;
+ }
+ checkParameterBound("setUDPSocketBufferSizes", recv, std::numeric_limits<uint32_t>::max());
+ checkParameterBound("setUDPSocketBufferSizes", snd, std::numeric_limits<uint32_t>::max());
+ setLuaSideEffect();
+
+ g_socketUDPSendBuffer = snd;
+ g_socketUDPRecvBuffer = recv;
+ });
+
+ luaCtx.writeFunction("setRandomizedOutgoingSockets", [](bool randomized) {
+ DownstreamState::s_randomizeSockets = randomized;
+ });
+
+ luaCtx.writeFunction("setRandomizedIdsOverUDP", [](bool randomized) {
+ DownstreamState::s_randomizeIDs = randomized;
+ });
+
+#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
+ luaCtx.writeFunction("loadTLSEngine", [client](const std::string& engineName, boost::optional<std::string> defaultString) {
+ if (client) {
+ return;
+ }
+
+ auto [success, error] = libssl_load_engine(engineName, defaultString ? std::optional<std::string>(*defaultString) : std::nullopt);
+ if (!success) {
+ g_outputBuffer = "Error while trying to load TLS engine '" + engineName + "': " + error + "\n";
+ errlog("Error while trying to load TLS engine '%s': %s", engineName, error);
+ }
+ });
+#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */
+
+#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+ luaCtx.writeFunction("loadTLSProvider", [client](const std::string& providerName) {
+ if (client) {
+ return;
+ }
+
+ auto [success, error] = libssl_load_provider(providerName);
+ if (!success) {
+ g_outputBuffer = "Error while trying to load TLS provider '" + providerName + "': " + error + "\n";
+ errlog("Error while trying to load TLS provider '%s': %s", providerName, error);
+ }
+ });
+#endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */
+
+ luaCtx.writeFunction("newThread", [client, configCheck](const std::string& code) {
+ if (client || configCheck) {
+ return;
+ }
+ std::thread newThread(LuaThread, code);
+
+ newThread.detach();
+ });
+
+ luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional<std::string> customName) {
+ auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName ? std::optional<std::string>(*customName) : std::nullopt);
+ if (result) {
+ g_outputBuffer += *result + "\n";
+ errlog("Error in declareMetric: %s", *result);
+ return false;
+ }
+ return true;
+ });
+ luaCtx.writeFunction("incMetric", [](const std::string& name, boost::optional<uint64_t> step) {
+ auto result = dnsdist::metrics::incrementCustomCounter(name, step ? *step : 1);
+ if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+ g_outputBuffer = *errorStr + "'\n";
+ errlog("Error in incMetric: %s", *errorStr);
+ return static_cast<uint64_t>(0);
+ }
+ return std::get<uint64_t>(result);
+ });
+ luaCtx.writeFunction("decMetric", [](const std::string& name, boost::optional<uint64_t> step) {
+ auto result = dnsdist::metrics::decrementCustomCounter(name, step ? *step : 1);
+ if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+ g_outputBuffer = *errorStr + "'\n";
+ errlog("Error in decMetric: %s", *errorStr);
+ return static_cast<uint64_t>(0);
+ }
+ return std::get<uint64_t>(result);
+ });
+ luaCtx.writeFunction("setMetric", [](const std::string& name, const double value) -> double {
+ auto result = dnsdist::metrics::setCustomGauge(name, value);
+ if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+ g_outputBuffer = *errorStr + "'\n";
+ errlog("Error in setMetric: %s", *errorStr);
+ return 0.;
+ }
+ return std::get<double>(result);
+ });
+ luaCtx.writeFunction("getMetric", [](const std::string& name) {
+ auto result = dnsdist::metrics::getCustomMetric(name);
+ if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+ g_outputBuffer = *errorStr + "'\n";
+ errlog("Error in getMetric: %s", *errorStr);
+ return 0.;
+ }
+ return std::get<double>(result);
+ });
+}
+
+vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config)
+{
+ // this needs to exist only during the parsing of the configuration
+ // and cannot be captured by lambdas
+ g_launchWork = std::vector<std::function<void(void)>>();
+
+ setupLuaActions(luaCtx);
+ setupLuaConfig(luaCtx, client, configCheck);
+ setupLuaBindings(luaCtx, client, configCheck);
+ setupLuaBindingsDNSCrypt(luaCtx, client);
+ setupLuaBindingsDNSParser(luaCtx);
+ setupLuaBindingsDNSQuestion(luaCtx);
+ setupLuaBindingsKVS(luaCtx, client);
+ setupLuaBindingsNetwork(luaCtx, client);
+ setupLuaBindingsPacketCache(luaCtx, client);
+ setupLuaBindingsProtoBuf(luaCtx, client, configCheck);
+ setupLuaBindingsRings(luaCtx, client);
+ dnsdist::lua::hooks::setupLuaHooks(luaCtx);
+ setupLuaInspection(luaCtx);
+ setupLuaRules(luaCtx);
+ setupLuaVars(luaCtx);
+ setupLuaWeb(luaCtx);
+
+#ifdef LUAJIT_VERSION
+ luaCtx.executeCode(getLuaFFIWrappers());
+#endif
+
+ std::ifstream ifs(config);
+ if (!ifs) {
+ if (configCheck) {
+ throw std::runtime_error("Unable to read configuration file from " + config);
+ }
+ warnlog("Unable to read configuration from '%s'", config);
+ }
+ else {
+ vinfolog("Read configuration from '%s'", config);
+ }
+
+ luaCtx.executeCode(ifs);
+
+ auto ret = *g_launchWork;
+ g_launchWork = boost::none;
+ return ret;
+}
+++ /dev/null
-../dnsdist-lua.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsparser.hh"
+#include <random>
+
+struct ResponseConfig
+{
+ boost::optional<bool> setAA{boost::none};
+ boost::optional<bool> setAD{boost::none};
+ boost::optional<bool> setRA{boost::none};
+ uint32_t ttl{60};
+};
+void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config);
+
+class SpoofAction : public DNSAction
+{
+public:
+ SpoofAction(const vector<ComboAddress>& addrs) :
+ d_addrs(addrs)
+ {
+ for (const auto& addr : d_addrs) {
+ if (addr.isIPv4()) {
+ d_types.insert(QType::A);
+ }
+ else if (addr.isIPv6()) {
+ d_types.insert(QType::AAAA);
+ }
+ }
+
+ if (!d_addrs.empty()) {
+ d_types.insert(QType::ANY);
+ }
+ }
+
+ SpoofAction(const DNSName& cname) :
+ d_cname(cname)
+ {
+ }
+
+ SpoofAction(const char* rawresponse, size_t len) :
+ d_raw(rawresponse, rawresponse + len)
+ {
+ }
+
+ SpoofAction(const vector<std::string>& raws, std::optional<uint16_t> typeForAny) :
+ d_rawResponses(raws), d_rawTypeForAny(typeForAny)
+ {
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dnsquestion, string* ruleresult) const override;
+
+ string toString() const override
+ {
+ string ret = "spoof in ";
+ if (!d_cname.empty()) {
+ ret += d_cname.toString() + " ";
+ }
+ if (d_rawResponses.size() > 0) {
+ ret += "raw bytes ";
+ }
+ else {
+ for (const auto& a : d_addrs)
+ ret += a.toString() + " ";
+ }
+ return ret;
+ }
+
+ [[nodiscard]] ResponseConfig& getResponseConfig()
+ {
+ return d_responseConfig;
+ }
+
+private:
+ ResponseConfig d_responseConfig;
+ static thread_local std::default_random_engine t_randomEngine;
+ std::vector<ComboAddress> d_addrs;
+ std::unordered_set<uint16_t> d_types;
+ std::vector<std::string> d_rawResponses;
+ PacketBuffer d_raw;
+ DNSName d_cname;
+ std::optional<uint16_t> d_rawTypeForAny{};
+};
+
+class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+ LimitTTLResponseAction() {}
+
+ LimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max(), const std::unordered_set<QType>& types = {}) :
+ d_types(types), d_min(min), d_max(max)
+ {
+ }
+
+ DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+ {
+ auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
+ if (!d_types.empty() && qclass == QClass::IN && d_types.count(qtype) == 0) {
+ return ttl;
+ }
+
+ if (d_min > 0) {
+ if (ttl < d_min) {
+ ttl = d_min;
+ }
+ }
+ if (ttl > d_max) {
+ ttl = d_max;
+ }
+ return ttl;
+ };
+ editDNSPacketTTL(reinterpret_cast<char*>(dr->getMutableData().data()), dr->getData().size(), visitor);
+ return DNSResponseAction::Action::None;
+ }
+
+ std::string toString() const override
+ {
+ std::string result = "limit ttl (" + std::to_string(d_min) + " <= ttl <= " + std::to_string(d_max);
+ if (!d_types.empty()) {
+ bool first = true;
+ result += ", types in [";
+ for (const auto& type : d_types) {
+ if (first) {
+ first = false;
+ }
+ else {
+ result += " ";
+ }
+ result += type.toString();
+ }
+ result += "]";
+ }
+ result += +")";
+ return result;
+ }
+
+private:
+ std::unordered_set<QType> d_types;
+ uint32_t d_min{0};
+ uint32_t d_max{std::numeric_limits<uint32_t>::max()};
+};
+
+template <class T>
+using LuaArray = std::vector<std::pair<int, T>>;
+template <class T>
+using LuaAssociativeTable = std::unordered_map<std::string, T>;
+template <class T>
+using LuaTypeOrArrayOf = boost::variant<T, LuaArray<T>>;
+
+using luaruleparams_t = LuaAssociativeTable<std::string>;
+using nmts_t = NetmaskTree<DynBlock, AddressAndPortRange>;
+
+using luadnsrule_t = boost::variant<string, LuaArray<std::string>, std::shared_ptr<DNSRule>, DNSName, LuaArray<DNSName>>;
+std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var, const std::string& calledFrom);
+
+void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder);
+void checkParameterBound(const std::string& parameter, uint64_t value, size_t max = std::numeric_limits<uint16_t>::max());
+
+vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config);
+void setupLuaActions(LuaContext& luaCtx);
+void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck);
+void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client);
+void setupLuaBindingsDNSParser(LuaContext& luaCtx);
+void setupLuaBindingsDNSQuestion(LuaContext& luaCtx);
+void setupLuaBindingsKVS(LuaContext& luaCtx, bool client);
+void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client);
+void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client);
+void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck);
+void setupLuaBindingsRings(LuaContext& luaCtx, bool client);
+void setupLuaRules(LuaContext& luaCtx);
+void setupLuaInspection(LuaContext& luaCtx);
+void setupLuaVars(LuaContext& luaCtx);
+void setupLuaWeb(LuaContext& luaCtx);
+void setupLuaLoadBalancingContext(LuaContext& luaCtx);
+
+/**
+ * getOptionalValue(vars, key, value)
+ *
+ * Attempts to extract value for key in vars.
+ * Erases the key from vars.
+ *
+ * returns: -1 if type wasn't compatible, 0 if not found or number of element(s) found
+ */
+template <class G, class T, class V>
+static inline int getOptionalValue(boost::optional<V>& vars, const std::string& key, T& value, bool warnOnWrongType = true)
+{
+ /* nothing found, nothing to return */
+ if (!vars) {
+ return 0;
+ }
+
+ if (vars->count(key)) {
+ try {
+ value = boost::get<G>((*vars)[key]);
+ }
+ catch (const boost::bad_get& e) {
+ /* key is there but isn't compatible */
+ if (warnOnWrongType) {
+ warnlog("Invalid type for key '%s' - ignored", key);
+ vars->erase(key);
+ }
+ return -1;
+ }
+ }
+ return vars->erase(key);
+}
+
+template <class T, class V>
+static inline int getOptionalIntegerValue(const std::string& func, boost::optional<V>& vars, const std::string& key, T& value)
+{
+ std::string valueStr;
+ auto ret = getOptionalValue<std::string>(vars, key, valueStr, true);
+ if (ret == 1) {
+ try {
+ value = std::stoi(valueStr);
+ }
+ catch (const std::exception& e) {
+ warnlog("Parameter '%s' of '%s' must be integer, not '%s' - ignoring", func, key, valueStr);
+ return -1;
+ }
+ }
+ return ret;
+}
+
+template <class V>
+static inline void checkAllParametersConsumed(const std::string& func, const boost::optional<V>& vars)
+{
+ /* no vars */
+ if (!vars) {
+ return;
+ }
+ for (const auto& [key, value] : *vars) {
+ warnlog("%s: Unknown key '%s' given - ignored", func, key);
+ }
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <regex>
+
+#include "dnsdist-metrics.hh"
+#include "dnsdist.hh"
+#include "dnsdist-web.hh"
+
+namespace dnsdist::metrics
+{
+
+struct MutableCounter
+{
+ MutableCounter() = default;
+ MutableCounter(const MutableCounter&) = delete;
+ MutableCounter(MutableCounter&& rhs) noexcept :
+ d_value(rhs.d_value.load())
+ {
+ }
+ MutableCounter& operator=(const MutableCounter&) = delete;
+ MutableCounter& operator=(MutableCounter&& rhs) noexcept
+ {
+ d_value = rhs.d_value.load();
+ return *this;
+ }
+ ~MutableCounter() = default;
+
+ mutable stat_t d_value{0};
+};
+
+struct MutableGauge
+{
+ MutableGauge() = default;
+ MutableGauge(const MutableGauge&) = delete;
+ MutableGauge(MutableGauge&& rhs) noexcept :
+ d_value(rhs.d_value.load())
+ {
+ }
+ MutableGauge& operator=(const MutableGauge&) = delete;
+ MutableGauge& operator=(MutableGauge&& rhs) noexcept
+ {
+ d_value = rhs.d_value.load();
+ return *this;
+ }
+ ~MutableGauge() = default;
+
+ mutable pdns::stat_t_trait<double> d_value{0};
+};
+
+static SharedLockGuarded<std::map<std::string, MutableCounter, std::less<>>> s_customCounters;
+static SharedLockGuarded<std::map<std::string, MutableGauge, std::less<>>> s_customGauges;
+
+Stats::Stats() :
+ entries{std::vector<EntryPair>{
+ {"responses", &responses},
+ {"servfail-responses", &servfailResponses},
+ {"queries", &queries},
+ {"frontend-nxdomain", &frontendNXDomain},
+ {"frontend-servfail", &frontendServFail},
+ {"frontend-noerror", &frontendNoError},
+ {"acl-drops", &aclDrops},
+ {"rule-drop", &ruleDrop},
+ {"rule-nxdomain", &ruleNXDomain},
+ {"rule-refused", &ruleRefused},
+ {"rule-servfail", &ruleServFail},
+ {"rule-truncated", &ruleTruncated},
+ {"self-answered", &selfAnswered},
+ {"downstream-timeouts", &downstreamTimeouts},
+ {"downstream-send-errors", &downstreamSendErrors},
+ {"trunc-failures", &truncFail},
+ {"no-policy", &noPolicy},
+ {"latency0-1", &latency0_1},
+ {"latency1-10", &latency1_10},
+ {"latency10-50", &latency10_50},
+ {"latency50-100", &latency50_100},
+ {"latency100-1000", &latency100_1000},
+ {"latency-slow", &latencySlow},
+ {"latency-avg100", &latencyAvg100},
+ {"latency-avg1000", &latencyAvg1000},
+ {"latency-avg10000", &latencyAvg10000},
+ {"latency-avg1000000", &latencyAvg1000000},
+ {"latency-tcp-avg100", &latencyTCPAvg100},
+ {"latency-tcp-avg1000", &latencyTCPAvg1000},
+ {"latency-tcp-avg10000", &latencyTCPAvg10000},
+ {"latency-tcp-avg1000000", &latencyTCPAvg1000000},
+ {"latency-dot-avg100", &latencyDoTAvg100},
+ {"latency-dot-avg1000", &latencyDoTAvg1000},
+ {"latency-dot-avg10000", &latencyDoTAvg10000},
+ {"latency-dot-avg1000000", &latencyDoTAvg1000000},
+ {"latency-doh-avg100", &latencyDoHAvg100},
+ {"latency-doh-avg1000", &latencyDoHAvg1000},
+ {"latency-doh-avg10000", &latencyDoHAvg10000},
+ {"latency-doh-avg1000000", &latencyDoHAvg1000000},
+ {"latency-doq-avg100", &latencyDoQAvg100},
+ {"latency-doq-avg1000", &latencyDoQAvg1000},
+ {"latency-doq-avg10000", &latencyDoQAvg10000},
+ {"latency-doq-avg1000000", &latencyDoQAvg1000000},
+ {"latency-doh3-avg100", &latencyDoH3Avg100},
+ {"latency-doh3-avg1000", &latencyDoH3Avg1000},
+ {"latency-doh3-avg10000", &latencyDoH3Avg10000},
+ {"latency-doh3-avg1000000", &latencyDoH3Avg1000000},
+ {"uptime", uptimeOfProcess},
+ {"real-memory-usage", getRealMemoryUsage},
+ {"special-memory-usage", getSpecialMemoryUsage},
+ {"udp-in-errors", [](const std::string&) { return udpErrorStats("udp-in-errors"); }},
+ {"udp-noport-errors", [](const std::string&) { return udpErrorStats("udp-noport-errors"); }},
+ {"udp-recvbuf-errors", [](const std::string&) { return udpErrorStats("udp-recvbuf-errors"); }},
+ {"udp-sndbuf-errors", [](const std::string&) { return udpErrorStats("udp-sndbuf-errors"); }},
+ {"udp-in-csum-errors", [](const std::string&) { return udpErrorStats("udp-in-csum-errors"); }},
+ {"udp6-in-errors", [](const std::string&) { return udp6ErrorStats("udp6-in-errors"); }},
+ {"udp6-recvbuf-errors", [](const std::string&) { return udp6ErrorStats("udp6-recvbuf-errors"); }},
+ {"udp6-sndbuf-errors", [](const std::string&) { return udp6ErrorStats("udp6-sndbuf-errors"); }},
+ {"udp6-noport-errors", [](const std::string&) { return udp6ErrorStats("udp6-noport-errors"); }},
+ {"udp6-in-csum-errors", [](const std::string&) { return udp6ErrorStats("udp6-in-csum-errors"); }},
+ {"tcp-listen-overflows", [](const std::string&) { return tcpErrorStats("ListenOverflows"); }},
+ {"noncompliant-queries", &nonCompliantQueries},
+ {"noncompliant-responses", &nonCompliantResponses},
+ {"proxy-protocol-invalid", &proxyProtocolInvalid},
+ {"rdqueries", &rdQueries},
+ {"empty-queries", &emptyQueries},
+ {"cache-hits", &cacheHits},
+ {"cache-misses", &cacheMisses},
+ {"cpu-iowait", getCPUIOWait},
+ {"cpu-steal", getCPUSteal},
+ {"cpu-sys-msec", getCPUTimeSystem},
+ {"cpu-user-msec", getCPUTimeUser},
+ {"fd-usage", getOpenFileDescriptors},
+ {"dyn-blocked", &dynBlocked},
+ {"dyn-block-nmg-size", [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }},
+ {"security-status", &securityStatus},
+ {"doh-query-pipe-full", &dohQueryPipeFull},
+ {"doh-response-pipe-full", &dohResponsePipeFull},
+ {"doq-response-pipe-full", &doqResponsePipeFull},
+ {"doh3-response-pipe-full", &doh3ResponsePipeFull},
+ {"outgoing-doh-query-pipe-full", &outgoingDoHQueryPipeFull},
+ {"tcp-query-pipe-full", &tcpQueryPipeFull},
+ {"tcp-cross-protocol-query-pipe-full", &tcpCrossProtocolQueryPipeFull},
+ {"tcp-cross-protocol-response-pipe-full", &tcpCrossProtocolResponsePipeFull},
+ // Latency histogram
+ {"latency-sum", &latencySum},
+ {"latency-count", &latencyCount},
+ }}
+{
+}
+
+struct Stats g_stats;
+
+std::optional<std::string> declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional<std::string> customName)
+{
+ if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) {
+ return std::string("Unable to declare metric '") + std::string(name) + std::string("': invalid name\n");
+ }
+
+ const std::string finalCustomName(customName ? *customName : "");
+ if (type == "counter") {
+ auto customCounters = s_customCounters.write_lock();
+ auto itp = customCounters->insert({name, MutableCounter()});
+ if (itp.second) {
+ g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customCounters)[name].d_value});
+ dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName};
+ addMetricDefinition(def);
+ }
+ }
+ else if (type == "gauge") {
+ auto customGauges = s_customGauges.write_lock();
+ auto itp = customGauges->insert({name, MutableGauge()});
+ if (itp.second) {
+ g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customGauges)[name].d_value});
+ dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName};
+ addMetricDefinition(def);
+ }
+ }
+ else {
+ return std::string("Unable to declare metric: unknown type '") + type + "'";
+ }
+ return std::nullopt;
+}
+
+std::variant<uint64_t, Error> incrementCustomCounter(const std::string_view& name, uint64_t step)
+{
+ auto customCounters = s_customCounters.read_lock();
+ auto metric = customCounters->find(name);
+ if (metric != customCounters->end()) {
+ metric->second.d_value += step;
+ return metric->second.d_value.load();
+ }
+ return std::string("Unable to increment custom metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<uint64_t, Error> decrementCustomCounter(const std::string_view& name, uint64_t step)
+{
+ auto customCounters = s_customCounters.read_lock();
+ auto metric = customCounters->find(name);
+ if (metric != customCounters->end()) {
+ metric->second.d_value -= step;
+ return metric->second.d_value.load();
+ }
+ return std::string("Unable to decrement custom metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<double, Error> setCustomGauge(const std::string_view& name, const double value)
+{
+ auto customGauges = s_customGauges.read_lock();
+ auto metric = customGauges->find(name);
+ if (metric != customGauges->end()) {
+ metric->second.d_value = value;
+ return value;
+ }
+
+ return std::string("Unable to set metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<double, Error> getCustomMetric(const std::string_view& name)
+{
+ {
+ auto customCounters = s_customCounters.read_lock();
+ auto counter = customCounters->find(name);
+ if (counter != customCounters->end()) {
+ return static_cast<double>(counter->second.d_value.load());
+ }
+ }
+ {
+ auto customGauges = s_customGauges.read_lock();
+ auto gauge = customGauges->find(name);
+ if (gauge != customGauges->end()) {
+ return gauge->second.d_value.load();
+ }
+ }
+ return std::string("Unable to get metric '") + std::string(name) + "': no such metric";
+}
+
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <cinttypes>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <variant>
+
+#include "lock.hh"
+#include "stat_t.hh"
+
+namespace dnsdist::metrics
+{
+using Error = std::string;
+
+[[nodiscard]] std::optional<Error> declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional<std::string> customName);
+[[nodiscard]] std::variant<uint64_t, Error> incrementCustomCounter(const std::string_view& name, uint64_t step);
+[[nodiscard]] std::variant<uint64_t, Error> decrementCustomCounter(const std::string_view& name, uint64_t step);
+[[nodiscard]] std::variant<double, Error> setCustomGauge(const std::string_view& name, const double value);
+[[nodiscard]] std::variant<double, Error> getCustomMetric(const std::string_view& name);
+
+using pdns::stat_t;
+
+struct Stats
+{
+ Stats();
+
+ stat_t responses{0};
+ stat_t servfailResponses{0};
+ stat_t queries{0};
+ stat_t frontendNXDomain{0};
+ stat_t frontendServFail{0};
+ stat_t frontendNoError{0};
+ stat_t nonCompliantQueries{0};
+ stat_t nonCompliantResponses{0};
+ stat_t rdQueries{0};
+ stat_t emptyQueries{0};
+ stat_t aclDrops{0};
+ stat_t dynBlocked{0};
+ stat_t ruleDrop{0};
+ stat_t ruleNXDomain{0};
+ stat_t ruleRefused{0};
+ stat_t ruleServFail{0};
+ stat_t ruleTruncated{0};
+ stat_t selfAnswered{0};
+ stat_t downstreamTimeouts{0};
+ stat_t downstreamSendErrors{0};
+ stat_t truncFail{0};
+ stat_t noPolicy{0};
+ stat_t cacheHits{0};
+ stat_t cacheMisses{0};
+ stat_t latency0_1{0}, latency1_10{0}, latency10_50{0}, latency50_100{0}, latency100_1000{0}, latencySlow{0}, latencySum{0}, latencyCount{0};
+ stat_t securityStatus{0};
+ stat_t dohQueryPipeFull{0};
+ stat_t dohResponsePipeFull{0};
+ stat_t doqResponsePipeFull{0};
+ stat_t doh3ResponsePipeFull{0};
+ stat_t outgoingDoHQueryPipeFull{0};
+ stat_t proxyProtocolInvalid{0};
+ stat_t tcpQueryPipeFull{0};
+ stat_t tcpCrossProtocolQueryPipeFull{0};
+ stat_t tcpCrossProtocolResponsePipeFull{0};
+ double latencyAvg100{0}, latencyAvg1000{0}, latencyAvg10000{0}, latencyAvg1000000{0};
+ double latencyTCPAvg100{0}, latencyTCPAvg1000{0}, latencyTCPAvg10000{0}, latencyTCPAvg1000000{0};
+ double latencyDoTAvg100{0}, latencyDoTAvg1000{0}, latencyDoTAvg10000{0}, latencyDoTAvg1000000{0};
+ double latencyDoHAvg100{0}, latencyDoHAvg1000{0}, latencyDoHAvg10000{0}, latencyDoHAvg1000000{0};
+ double latencyDoQAvg100{0}, latencyDoQAvg1000{0}, latencyDoQAvg10000{0}, latencyDoQAvg1000000{0};
+ double latencyDoH3Avg100{0}, latencyDoH3Avg1000{0}, latencyDoH3Avg10000{0}, latencyDoH3Avg1000000{0};
+ using statfunction_t = std::function<uint64_t(const std::string&)>;
+ using entry_t = std::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t>;
+ struct EntryPair
+ {
+ std::string d_name;
+ entry_t d_value;
+ };
+
+ SharedLockGuarded<std::vector<EntryPair>> entries;
+};
+
+extern struct Stats g_stats;
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-doh-common.hh"
+#include "dnsdist-nghttp2-in.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsparser.hh"
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+
+#if 0
+class IncomingDoHCrossProtocolContext : public CrossProtocolContext
+{
+public:
+ IncomingDoHCrossProtocolContext(IncomingHTTP2Connection::PendingQuery&& query, std::shared_ptr<IncomingHTTP2Connection> connection, IncomingHTTP2Connection::StreamID streamID): CrossProtocolContext(std::move(query.d_buffer)), d_connection(connection), d_query(std::move(query))
+ {
+ }
+
+ std::optional<std::string> getHTTPPath() const override
+ {
+ return d_query.d_path;
+ }
+
+ std::optional<std::string> getHTTPScheme() const override
+ {
+ return d_query.d_scheme;
+ }
+
+ std::optional<std::string> getHTTPHost() const override
+ {
+ return d_query.d_host;
+ }
+
+ std::optional<std::string> getHTTPQueryString() const override
+ {
+ return d_query.d_queryString;
+ }
+
+ std::optional<HeadersMap> getHTTPHeaders() const override
+ {
+ if (!d_query.d_headers) {
+ return std::nullopt;
+ }
+ return *d_query.d_headers;
+ }
+
+ void handleResponse(PacketBuffer&& response, InternalQueryState&& state) override
+ {
+ auto conn = d_connection.lock();
+ if (!conn) {
+ /* the connection has been closed in the meantime */
+ return;
+ }
+ }
+
+ void handleTimeout() override
+ {
+ auto conn = d_connection.lock();
+ if (!conn) {
+ /* the connection has been closed in the meantime */
+ return;
+ }
+ }
+
+ ~IncomingDoHCrossProtocolContext() override
+ {
+ }
+
+private:
+ std::weak_ptr<IncomingHTTP2Connection> d_connection;
+ IncomingHTTP2Connection::PendingQuery d_query;
+ IncomingHTTP2Connection::StreamID d_streamID{-1};
+};
+#endif
+
+class IncomingDoHCrossProtocolContext : public DOHUnitInterface
+{
+public:
+ IncomingDoHCrossProtocolContext(IncomingHTTP2Connection::PendingQuery&& query, const std::shared_ptr<IncomingHTTP2Connection>& connection, IncomingHTTP2Connection::StreamID streamID) :
+ d_connection(connection), d_query(std::move(query)), d_streamID(streamID)
+ {
+ }
+ IncomingDoHCrossProtocolContext(const IncomingDoHCrossProtocolContext&) = delete;
+ IncomingDoHCrossProtocolContext(IncomingDoHCrossProtocolContext&&) = delete;
+ IncomingDoHCrossProtocolContext& operator=(const IncomingDoHCrossProtocolContext&) = delete;
+ IncomingDoHCrossProtocolContext& operator=(IncomingDoHCrossProtocolContext&&) = delete;
+
+ ~IncomingDoHCrossProtocolContext() override = default;
+
+ [[nodiscard]] std::string getHTTPPath() const override
+ {
+ return d_query.d_path;
+ }
+
+ [[nodiscard]] const std::string& getHTTPScheme() const override
+ {
+ return d_query.d_scheme;
+ }
+
+ [[nodiscard]] const std::string& getHTTPHost() const override
+ {
+ return d_query.d_host;
+ }
+
+ [[nodiscard]] std::string getHTTPQueryString() const override
+ {
+ return d_query.d_queryString;
+ }
+
+ [[nodiscard]] const HeadersMap& getHTTPHeaders() const override
+ {
+ if (!d_query.d_headers) {
+ static const HeadersMap empty{};
+ return empty;
+ }
+ return *d_query.d_headers;
+ }
+
+ void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType = "") override
+ {
+ d_query.d_statusCode = statusCode;
+ d_query.d_response = std::move(body);
+ d_query.d_contentTypeOut = contentType;
+ }
+
+ void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& downstream_) override
+ {
+ std::unique_ptr<DOHUnitInterface> unit(this);
+ auto conn = d_connection.lock();
+ if (!conn) {
+ /* the connection has been closed in the meantime */
+ return;
+ }
+
+ state.du = std::move(unit);
+ TCPResponse resp(std::move(response), std::move(state), nullptr, nullptr);
+ resp.d_ds = downstream_;
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
+ conn->handleResponse(now, std::move(resp));
+ }
+
+ void handleTimeout() override
+ {
+ std::unique_ptr<DOHUnitInterface> unit(this);
+ auto conn = d_connection.lock();
+ if (!conn) {
+ /* the connection has been closed in the meantime */
+ return;
+ }
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
+ TCPResponse resp;
+ resp.d_idstate.d_streamID = d_streamID;
+ conn->notifyIOError(now, std::move(resp));
+ }
+
+ std::weak_ptr<IncomingHTTP2Connection> d_connection;
+ IncomingHTTP2Connection::PendingQuery d_query;
+ IncomingHTTP2Connection::StreamID d_streamID{-1};
+};
+
+void IncomingHTTP2Connection::handleResponse(const struct timeval& now, TCPResponse&& response)
+{
+ if (std::this_thread::get_id() != d_creatorThreadID) {
+ handleCrossProtocolResponse(now, std::move(response));
+ return;
+ }
+
+ auto& state = response.d_idstate;
+ if (state.forwardedOverUDP) {
+ dnsheader_aligned responseDH(response.d_buffer.data());
+
+ if (responseDH.get()->tc && state.d_packet && state.d_packet->size() > state.d_proxyProtocolPayloadSize && state.d_packet->size() - state.d_proxyProtocolPayloadSize > sizeof(dnsheader)) {
+ vinfolog("Response received from backend %s via UDP, for query %d received from %s via DoH, is truncated, retrying over TCP", response.d_ds->getNameWithAddr(), state.d_streamID, state.origRemote.toStringWithPort());
+ auto& query = *state.d_packet;
+ dnsdist::PacketMangling::editDNSHeaderFromRawPacket(&query.at(state.d_proxyProtocolPayloadSize), [origID = state.origID](dnsheader& header) {
+ /* restoring the original ID */
+ header.id = origID;
+ return true;
+ });
+
+ state.forwardedOverUDP = false;
+ bool proxyProtocolPayloadAdded = state.d_proxyProtocolPayloadSize > 0;
+ auto cpq = getCrossProtocolQuery(std::move(query), std::move(state), response.d_ds);
+ cpq->query.d_proxyProtocolPayloadAdded = proxyProtocolPayloadAdded;
+ if (g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq))) {
+ return;
+ }
+ vinfolog("Unable to pass DoH query to a TCP worker thread after getting a TC response over UDP");
+ notifyIOError(now, std::move(response));
+ return;
+ }
+ }
+
+ IncomingTCPConnectionState::handleResponse(now, std::move(response));
+}
+
+std::unique_ptr<DOHUnitInterface> IncomingHTTP2Connection::getDOHUnit(uint32_t streamID)
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
+ assert(streamID <= std::numeric_limits<IncomingHTTP2Connection::StreamID>::max());
+ // NOLINTNEXTLINE(*-narrowing-conversions): generic interface between DNS and DoH with different types
+ auto query = std::move(d_currentStreams.at(static_cast<IncomingHTTP2Connection::StreamID>(streamID)));
+ return std::make_unique<IncomingDoHCrossProtocolContext>(std::move(query), std::dynamic_pointer_cast<IncomingHTTP2Connection>(shared_from_this()), streamID);
+}
+
+void IncomingHTTP2Connection::restoreDOHUnit(std::unique_ptr<DOHUnitInterface>&& unit)
+{
+ auto context = std::unique_ptr<IncomingDoHCrossProtocolContext>(dynamic_cast<IncomingDoHCrossProtocolContext*>(unit.release()));
+ if (context) {
+ d_currentStreams.at(context->d_streamID) = std::move(context->d_query);
+ }
+}
+
+IncomingHTTP2Connection::IncomingHTTP2Connection(ConnectionInfo&& connectionInfo, TCPClientThreadData& threadData, const struct timeval& now) :
+ IncomingTCPConnectionState(std::move(connectionInfo), threadData, now)
+{
+ nghttp2_session_callbacks* cbs = nullptr;
+ if (nghttp2_session_callbacks_new(&cbs) != 0) {
+ throw std::runtime_error("Unable to create a callback object for a new incoming HTTP/2 session");
+ }
+ std::unique_ptr<nghttp2_session_callbacks, void (*)(nghttp2_session_callbacks*)> callbacks(cbs, nghttp2_session_callbacks_del);
+ cbs = nullptr;
+
+ nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback);
+ nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback);
+ nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks.get(), on_begin_headers_callback);
+ nghttp2_session_callbacks_set_on_header_callback(callbacks.get(), on_header_callback);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback);
+ nghttp2_session_callbacks_set_error_callback2(callbacks.get(), on_error_callback);
+
+ nghttp2_session* sess = nullptr;
+ if (nghttp2_session_server_new(&sess, callbacks.get(), this) != 0) {
+ throw std::runtime_error("Coult not allocate a new incoming HTTP/2 session");
+ }
+
+ d_session = std::unique_ptr<nghttp2_session, decltype(&nghttp2_session_del)>(sess, nghttp2_session_del);
+ sess = nullptr;
+}
+
+bool IncomingHTTP2Connection::checkALPN()
+{
+ constexpr std::array<uint8_t, 2> h2ALPN{'h', '2'};
+ const auto protocols = d_handler.getNextProtocol();
+ if (protocols.size() == h2ALPN.size() && memcmp(protocols.data(), h2ALPN.data(), h2ALPN.size()) == 0) {
+ return true;
+ }
+
+ const std::string data("HTTP/1.1 400 Bad Request\r\nConnection: Close\r\n\r\n<html><body>This server implements RFC 8484 - DNS Queries over HTTP, and requires HTTP/2 in accordance with section 5.2 of the RFC.</body></html>\r\n");
+ d_out.insert(d_out.end(), data.begin(), data.end());
+ writeToSocket(false);
+
+ vinfolog("DoH connection from %s expected ALPN value 'h2', got '%s'", d_ci.remote.toStringWithPort(), std::string(protocols.begin(), protocols.end()));
+ return false;
+}
+
+void IncomingHTTP2Connection::handleConnectionReady()
+{
+ constexpr std::array<nghttp2_settings_entry, 1> settings{{{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100U}}};
+ auto ret = nghttp2_submit_settings(d_session.get(), NGHTTP2_FLAG_NONE, settings.data(), settings.size());
+ if (ret != 0) {
+ throw std::runtime_error("Fatal error: " + std::string(nghttp2_strerror(ret)));
+ }
+ d_needFlush = true;
+ ret = nghttp2_session_send(d_session.get());
+ if (ret != 0) {
+ throw std::runtime_error("Fatal error: " + std::string(nghttp2_strerror(ret)));
+ }
+}
+
+bool IncomingHTTP2Connection::hasPendingWrite() const
+{
+ return d_pendingWrite;
+}
+
+IOState IncomingHTTP2Connection::handleHandshake(const struct timeval& now)
+{
+ auto iostate = d_handler.tryHandshake();
+ if (iostate == IOState::Done) {
+ handleHandshakeDone(now);
+ if (d_handler.isTLS()) {
+ if (!checkALPN()) {
+ d_connectionDied = true;
+ stopIO();
+ return iostate;
+ }
+ }
+
+ if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && !isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
+ d_state = State::readingProxyProtocolHeader;
+ d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
+ d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+ }
+ else {
+ d_state = State::waitingForQuery;
+ handleConnectionReady();
+ }
+ }
+ return iostate;
+}
+
+void IncomingHTTP2Connection::handleIO()
+{
+ IOState iostate = IOState::Done;
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
+
+ try {
+ if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
+ vinfolog("Terminating DoH connection from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
+ stopIO();
+ d_connectionClosing = true;
+ return;
+ }
+
+ if (d_state == State::starting) {
+ if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
+ d_state = State::readingProxyProtocolHeader;
+ d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
+ d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+ }
+ else {
+ d_state = State::doingHandshake;
+ }
+ }
+
+ if (d_state == State::doingHandshake) {
+ iostate = handleHandshake(now);
+ if (d_connectionDied) {
+ return;
+ }
+ }
+
+ if (d_state == State::readingProxyProtocolHeader) {
+ auto status = handleProxyProtocolPayload();
+ if (status == ProxyProtocolResult::Done) {
+ if (isProxyPayloadOutsideTLS()) {
+ d_state = State::doingHandshake;
+ iostate = handleHandshake(now);
+ if (d_connectionDied) {
+ return;
+ }
+ }
+ else {
+ d_currentPos = 0;
+ d_proxyProtocolNeed = 0;
+ d_buffer.clear();
+ d_state = State::waitingForQuery;
+ handleConnectionReady();
+ }
+ }
+ else if (status == ProxyProtocolResult::Error) {
+ d_connectionDied = true;
+ stopIO();
+ return;
+ }
+ }
+
+ if (active() && !d_connectionClosing && (d_state == State::waitingForQuery || d_state == State::idle)) {
+ do {
+ iostate = readHTTPData();
+ } while (active() && !d_connectionClosing && iostate == IOState::Done);
+ }
+
+ if (!active()) {
+ stopIO();
+ return;
+ }
+ /*
+ So:
+ - if we have a pending write, we need to wait until the socket becomes writable
+ and then call handleWritableCallback
+ - if we have NeedWrite but no pending write, we need to wait until the socket
+ becomes writable but for handleReadableIOCallback
+ - if we have NeedRead, or nghttp2_session_want_read, wait until the socket
+ becomes readable and call handleReadableIOCallback
+ */
+ if (hasPendingWrite()) {
+ updateIO(IOState::NeedWrite, handleWritableIOCallback);
+ }
+ else if (iostate == IOState::NeedWrite) {
+ updateIO(IOState::NeedWrite, handleReadableIOCallback);
+ }
+ else if (!d_connectionClosing) {
+ if (nghttp2_session_want_read(d_session.get()) != 0) {
+ updateIO(IOState::NeedRead, handleReadableIOCallback);
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Exception when processing IO for incoming DoH connection from %s: %s", d_ci.remote.toStringWithPort(), e.what());
+ d_connectionDied = true;
+ stopIO();
+ }
+}
+
+void IncomingHTTP2Connection::writeToSocket(bool socketReady)
+{
+ try {
+ d_needFlush = false;
+ IOState newState = d_handler.tryWrite(d_out, d_outPos, d_out.size());
+
+ if (newState == IOState::Done) {
+ d_pendingWrite = false;
+ d_out.clear();
+ d_outPos = 0;
+ if (active() && !d_connectionClosing) {
+ updateIO(IOState::NeedRead, handleReadableIOCallback);
+ }
+ else {
+ stopIO();
+ }
+ }
+ else {
+ updateIO(newState, handleWritableIOCallback);
+ d_pendingWrite = true;
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Exception while trying to write (%s) to HTTP client connection to %s: %s", (socketReady ? "ready" : "send"), d_ci.remote.toStringWithPort(), e.what());
+ handleIOError();
+ }
+}
+
+ssize_t IncomingHTTP2Connection::send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
+{
+ auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+ if (conn->d_connectionDied) {
+ return static_cast<ssize_t>(length);
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
+ conn->d_out.insert(conn->d_out.end(), data, data + length);
+
+ if (conn->d_connectionClosing || conn->d_needFlush) {
+ conn->writeToSocket(false);
+ }
+
+ return static_cast<ssize_t>(length);
+}
+
+static const std::array<const std::string, static_cast<size_t>(NGHTTP2Headers::HeaderConstantIndexes::COUNT)> s_headerConstants{
+ "200",
+ ":method",
+ "POST",
+ ":scheme",
+ "https",
+ ":authority",
+ "x-forwarded-for",
+ ":path",
+ "content-length",
+ ":status",
+ "location",
+ "accept",
+ "application/dns-message",
+ "cache-control",
+ "content-type",
+ "application/dns-message",
+ "user-agent",
+ "nghttp2-" NGHTTP2_VERSION "/dnsdist",
+ "x-forwarded-port",
+ "x-forwarded-proto",
+ "dns-over-udp",
+ "dns-over-tcp",
+ "dns-over-tls",
+ "dns-over-http",
+ "dns-over-https"};
+
+static const std::string s_authorityHeaderName(":authority");
+static const std::string s_pathHeaderName(":path");
+static const std::string s_methodHeaderName(":method");
+static const std::string s_schemeHeaderName(":scheme");
+static const std::string s_xForwardedForHeaderName("x-forwarded-for");
+
+void NGHTTP2Headers::addStaticHeader(std::vector<nghttp2_nv>& headers, NGHTTP2Headers::HeaderConstantIndexes nameKey, NGHTTP2Headers::HeaderConstantIndexes valueKey)
+{
+ const auto& name = s_headerConstants.at(static_cast<size_t>(nameKey));
+ const auto& value = s_headerConstants.at(static_cast<size_t>(valueKey));
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+ headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.c_str())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.c_str())), name.size(), value.size(), NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE});
+}
+
+void NGHTTP2Headers::addCustomDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& name, const std::string_view& value)
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+ headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.data())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.data())), name.size(), value.size(), NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE});
+}
+
+void NGHTTP2Headers::addDynamicHeader(std::vector<nghttp2_nv>& headers, NGHTTP2Headers::HeaderConstantIndexes nameKey, const std::string_view& value)
+{
+ const auto& name = s_headerConstants.at(static_cast<size_t>(nameKey));
+ NGHTTP2Headers::addCustomDynamicHeader(headers, name, value);
+}
+
+IOState IncomingHTTP2Connection::sendResponse(const struct timeval& now, TCPResponse&& response)
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
+ assert(response.d_idstate.d_streamID != -1);
+ auto& context = d_currentStreams.at(response.d_idstate.d_streamID);
+
+ uint32_t statusCode = 200U;
+ std::string contentType;
+ bool sendContentType = true;
+ auto& responseBuffer = context.d_buffer;
+ if (context.d_statusCode != 0) {
+ responseBuffer = std::move(context.d_response);
+ statusCode = context.d_statusCode;
+ contentType = std::move(context.d_contentTypeOut);
+ }
+ else {
+ responseBuffer = std::move(response.d_buffer);
+ }
+
+ sendResponse(response.d_idstate.d_streamID, context, statusCode, d_ci.cs->dohFrontend->d_customResponseHeaders, contentType, sendContentType);
+ handleResponseSent(response);
+
+ return hasPendingWrite() ? IOState::NeedWrite : IOState::Done;
+}
+
+void IncomingHTTP2Connection::notifyIOError(const struct timeval& now, TCPResponse&& response)
+{
+ if (std::this_thread::get_id() != d_creatorThreadID) {
+ /* empty buffer will signal an IO error */
+ response.d_buffer.clear();
+ handleCrossProtocolResponse(now, std::move(response));
+ return;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
+ assert(response.d_idstate.d_streamID != -1);
+ auto& context = d_currentStreams.at(response.d_idstate.d_streamID);
+ context.d_buffer = std::move(response.d_buffer);
+ sendResponse(response.d_idstate.d_streamID, context, 502, d_ci.cs->dohFrontend->d_customResponseHeaders);
+}
+
+bool IncomingHTTP2Connection::sendResponse(IncomingHTTP2Connection::StreamID streamID, IncomingHTTP2Connection::PendingQuery& context, uint16_t responseCode, const HeadersMap& customResponseHeaders, const std::string& contentType, bool addContentType)
+{
+ /* if data_prd is not NULL, it provides data which will be sent in subsequent DATA frames. In this case, a method that allows request message bodies (https://tools.ietf.org/html/rfc7231#section-4) must be specified with :method key (e.g. POST). This function does not take ownership of the data_prd. The function copies the members of the data_prd. If data_prd is NULL, HEADERS have END_STREAM set.
+ */
+ nghttp2_data_provider data_provider;
+
+ data_provider.source.ptr = this;
+ data_provider.read_callback = [](nghttp2_session*, IncomingHTTP2Connection::StreamID stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* cb_data) -> ssize_t {
+ auto* connection = static_cast<IncomingHTTP2Connection*>(cb_data);
+ auto& obj = connection->d_currentStreams.at(stream_id);
+ size_t toCopy = 0;
+ if (obj.d_queryPos < obj.d_buffer.size()) {
+ size_t remaining = obj.d_buffer.size() - obj.d_queryPos;
+ toCopy = length > remaining ? remaining : length;
+ memcpy(buf, &obj.d_buffer.at(obj.d_queryPos), toCopy);
+ obj.d_queryPos += toCopy;
+ }
+
+ if (obj.d_queryPos >= obj.d_buffer.size()) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ obj.d_buffer.clear();
+ connection->d_needFlush = true;
+ }
+ return static_cast<ssize_t>(toCopy);
+ };
+
+ const auto& dohFrontend = d_ci.cs->dohFrontend;
+ auto& responseBody = context.d_buffer;
+
+ std::vector<nghttp2_nv> headers;
+ std::string responseCodeStr;
+ std::string cacheControlValue;
+ std::string location;
+ /* remember that dynamic header values should be kept alive
+ until we have called nghttp2_submit_response(), at least */
+ /* status, content-type, cache-control, content-length */
+ headers.reserve(4);
+
+ if (responseCode == 200) {
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::STATUS_NAME, NGHTTP2Headers::HeaderConstantIndexes::OK_200_VALUE);
+ ++dohFrontend->d_validresponses;
+ ++dohFrontend->d_http2Stats.d_nb200Responses;
+
+ if (addContentType) {
+ if (contentType.empty()) {
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_VALUE);
+ }
+ else {
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, contentType);
+ }
+ }
+
+ if (dohFrontend->d_sendCacheControlHeaders && responseBody.size() > sizeof(dnsheader)) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): API
+ uint32_t minTTL = getDNSPacketMinTTL(reinterpret_cast<const char*>(responseBody.data()), responseBody.size());
+ if (minTTL != std::numeric_limits<uint32_t>::max()) {
+ cacheControlValue = "max-age=" + std::to_string(minTTL);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CACHE_CONTROL_NAME, cacheControlValue);
+ }
+ }
+ }
+ else {
+ responseCodeStr = std::to_string(responseCode);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::STATUS_NAME, responseCodeStr);
+
+ if (responseCode >= 300 && responseCode < 400) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ location = std::string(reinterpret_cast<const char*>(responseBody.data()), responseBody.size());
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, "text/html; charset=utf-8");
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::LOCATION_NAME, location);
+ static const std::string s_redirectStart{"<!DOCTYPE html><TITLE>Moved</TITLE><P>The document has moved <A HREF=\""};
+ static const std::string s_redirectEnd{"\">here</A>"};
+ responseBody.reserve(s_redirectStart.size() + responseBody.size() + s_redirectEnd.size());
+ responseBody.insert(responseBody.begin(), s_redirectStart.begin(), s_redirectStart.end());
+ responseBody.insert(responseBody.end(), s_redirectEnd.begin(), s_redirectEnd.end());
+ ++dohFrontend->d_redirectresponses;
+ }
+ else {
+ ++dohFrontend->d_errorresponses;
+ switch (responseCode) {
+ case 400:
+ ++dohFrontend->d_http2Stats.d_nb400Responses;
+ break;
+ case 403:
+ ++dohFrontend->d_http2Stats.d_nb403Responses;
+ break;
+ case 500:
+ ++dohFrontend->d_http2Stats.d_nb500Responses;
+ break;
+ case 502:
+ ++dohFrontend->d_http2Stats.d_nb502Responses;
+ break;
+ default:
+ ++dohFrontend->d_http2Stats.d_nbOtherResponses;
+ break;
+ }
+
+ if (!responseBody.empty()) {
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, "text/plain; charset=utf-8");
+ }
+ else {
+ static const std::string invalid{"invalid DNS query"};
+ static const std::string notAllowed{"dns query not allowed"};
+ static const std::string noDownstream{"no downstream server available"};
+ static const std::string internalServerError{"Internal Server Error"};
+
+ switch (responseCode) {
+ case 400:
+ responseBody.insert(responseBody.begin(), invalid.begin(), invalid.end());
+ break;
+ case 403:
+ responseBody.insert(responseBody.begin(), notAllowed.begin(), notAllowed.end());
+ break;
+ case 502:
+ responseBody.insert(responseBody.begin(), noDownstream.begin(), noDownstream.end());
+ break;
+ case 500:
+ /* fall-through */
+ default:
+ responseBody.insert(responseBody.begin(), internalServerError.begin(), internalServerError.end());
+ break;
+ }
+ }
+ }
+ }
+
+ const std::string contentLength = std::to_string(responseBody.size());
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_LENGTH_NAME, contentLength);
+
+ for (const auto& [key, value] : customResponseHeaders) {
+ NGHTTP2Headers::addCustomDynamicHeader(headers, key, value);
+ }
+
+ auto ret = nghttp2_submit_response(d_session.get(), streamID, headers.data(), headers.size(), &data_provider);
+ if (ret != 0) {
+ d_currentStreams.erase(streamID);
+ vinfolog("Error submitting HTTP response for stream %d: %s", streamID, nghttp2_strerror(ret));
+ return false;
+ }
+
+ ret = nghttp2_session_send(d_session.get());
+ if (ret != 0) {
+ d_currentStreams.erase(streamID);
+ vinfolog("Error flushing HTTP response for stream %d: %s", streamID, nghttp2_strerror(ret));
+ return false;
+ }
+
+ return true;
+}
+
+static void processForwardedForHeader(const std::unique_ptr<HeadersMap>& headers, ComboAddress& remote)
+{
+ if (!headers) {
+ return;
+ }
+
+ auto headerIt = headers->find(s_xForwardedForHeaderName);
+ if (headerIt == headers->end()) {
+ return;
+ }
+
+ std::string_view value = headerIt->second;
+ try {
+ auto pos = value.rfind(',');
+ if (pos != std::string_view::npos) {
+ ++pos;
+ for (; pos < value.size() && value[pos] == ' '; ++pos) {
+ }
+
+ if (pos < value.size()) {
+ value = value.substr(pos);
+ }
+ }
+ auto newRemote = ComboAddress(std::string(value));
+ remote = newRemote;
+ }
+ catch (const std::exception& e) {
+ vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.what());
+ }
+ catch (const PDNSException& e) {
+ vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.reason);
+ }
+}
+
+void IncomingHTTP2Connection::handleIncomingQuery(IncomingHTTP2Connection::PendingQuery&& query, IncomingHTTP2Connection::StreamID streamID)
+{
+ const auto handleImmediateResponse = [this, &query, streamID](uint16_t code, const std::string& reason, PacketBuffer&& response = PacketBuffer()) {
+ if (response.empty()) {
+ query.d_buffer.clear();
+ query.d_buffer.insert(query.d_buffer.begin(), reason.begin(), reason.end());
+ }
+ else {
+ query.d_buffer = std::move(response);
+ }
+ vinfolog("Sending an immediate %d response to incoming DoH query: %s", code, reason);
+ sendResponse(streamID, query, code, d_ci.cs->dohFrontend->d_customResponseHeaders);
+ };
+
+ if (query.d_method == PendingQuery::Method::Unknown || query.d_method == PendingQuery::Method::Unsupported) {
+ handleImmediateResponse(400, "DoH query not allowed because of unsupported HTTP method");
+ return;
+ }
+
+ ++d_ci.cs->dohFrontend->d_http2Stats.d_nbQueries;
+
+ if (d_ci.cs->dohFrontend->d_trustForwardedForHeader) {
+ processForwardedForHeader(query.d_headers, d_proxiedRemote);
+
+ /* second ACL lookup based on the updated address */
+ auto& holders = d_threadData.holders;
+ if (!holders.acl->match(d_proxiedRemote)) {
+ ++dnsdist::metrics::g_stats.aclDrops;
+ vinfolog("Query from %s (%s) (DoH) dropped because of ACL", d_ci.remote.toStringWithPort(), d_proxiedRemote.toStringWithPort());
+ handleImmediateResponse(403, "DoH query not allowed because of ACL");
+ return;
+ }
+
+ if (!d_ci.cs->dohFrontend->d_keepIncomingHeaders) {
+ query.d_headers.reset();
+ }
+ }
+
+ if (d_ci.cs->dohFrontend->d_exactPathMatching) {
+ if (d_ci.cs->dohFrontend->d_urls.count(query.d_path) == 0) {
+ handleImmediateResponse(404, "there is no endpoint configured for this path");
+ return;
+ }
+ }
+ else {
+ bool found = false;
+ for (const auto& path : d_ci.cs->dohFrontend->d_urls) {
+ if (boost::starts_with(query.d_path, path)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ handleImmediateResponse(404, "there is no endpoint configured for this path");
+ return;
+ }
+ }
+
+ /* the responses map can be updated at runtime, so we need to take a copy of
+ the shared pointer, increasing the reference counter */
+ auto responsesMap = d_ci.cs->dohFrontend->d_responsesMap;
+ if (responsesMap) {
+ for (const auto& entry : *responsesMap) {
+ if (entry->matches(query.d_path)) {
+ const auto& customHeaders = entry->getHeaders();
+ query.d_buffer = entry->getContent();
+ if (entry->getStatusCode() >= 400 && !query.d_buffer.empty()) {
+ // legacy trailing 0 from the h2o era
+ query.d_buffer.pop_back();
+ }
+
+ sendResponse(streamID, query, entry->getStatusCode(), customHeaders ? *customHeaders : d_ci.cs->dohFrontend->d_customResponseHeaders, std::string(), false);
+ return;
+ }
+ }
+ }
+
+ if (query.d_buffer.empty() && query.d_method == PendingQuery::Method::Get && !query.d_queryString.empty()) {
+ auto payload = dnsdist::doh::getPayloadFromPath(query.d_queryString);
+ if (payload) {
+ query.d_buffer = std::move(*payload);
+ }
+ else {
+ ++d_ci.cs->dohFrontend->d_badrequests;
+ handleImmediateResponse(400, "DoH unable to decode BASE64-URL");
+ return;
+ }
+ }
+
+ if (query.d_method == PendingQuery::Method::Get) {
+ ++d_ci.cs->dohFrontend->d_getqueries;
+ }
+ else if (query.d_method == PendingQuery::Method::Post) {
+ ++d_ci.cs->dohFrontend->d_postqueries;
+ }
+
+ try {
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
+ auto processingResult = handleQuery(std::move(query.d_buffer), now, streamID);
+
+ switch (processingResult) {
+ case QueryProcessingResult::TooSmall:
+ handleImmediateResponse(400, "DoH non-compliant query");
+ break;
+ case QueryProcessingResult::InvalidHeaders:
+ handleImmediateResponse(400, "DoH invalid headers");
+ break;
+ case QueryProcessingResult::Dropped:
+ handleImmediateResponse(403, "DoH dropped query");
+ break;
+ case QueryProcessingResult::NoBackend:
+ handleImmediateResponse(502, "DoH no backend available");
+ return;
+ case QueryProcessingResult::Forwarded:
+ case QueryProcessingResult::Asynchronous:
+ case QueryProcessingResult::SelfAnswered:
+ break;
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Exception while processing DoH query: %s", e.what());
+ handleImmediateResponse(400, "DoH non-compliant query");
+ return;
+ }
+}
+
+int IncomingHTTP2Connection::on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
+{
+ auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+ /* is this the last frame for this stream? */
+ if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) {
+ auto streamID = frame->hd.stream_id;
+ auto stream = conn->d_currentStreams.find(streamID);
+ if (stream != conn->d_currentStreams.end()) {
+ conn->handleIncomingQuery(std::move(stream->second), streamID);
+ }
+ else {
+ vinfolog("Stream %d NOT FOUND", streamID);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ return 0;
+}
+
+int IncomingHTTP2Connection::on_stream_close_callback(nghttp2_session* session, IncomingHTTP2Connection::StreamID stream_id, uint32_t error_code, void* user_data)
+{
+ auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+
+ conn->d_currentStreams.erase(stream_id);
+ return 0;
+}
+
+int IncomingHTTP2Connection::on_begin_headers_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
+{
+ if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+
+ auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+ auto insertPair = conn->d_currentStreams.emplace(frame->hd.stream_id, PendingQuery());
+ if (!insertPair.second) {
+ /* there is a stream ID collision, something is very wrong! */
+ vinfolog("Stream ID collision (%d) on connection from %d", frame->hd.stream_id, conn->d_ci.remote.toStringWithPort());
+ conn->d_connectionClosing = true;
+ conn->d_needFlush = true;
+ nghttp2_session_terminate_session(conn->d_session.get(), NGHTTP2_NO_ERROR);
+ auto ret = nghttp2_session_send(conn->d_session.get());
+ if (ret != 0) {
+ vinfolog("Error flushing HTTP response for stream %d from %s: %s", frame->hd.stream_id, conn->d_ci.remote.toStringWithPort(), nghttp2_strerror(ret));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+static std::string::size_type getLengthOfPathWithoutParameters(const std::string_view& path)
+{
+ auto pos = path.find('?');
+ if (pos == string::npos) {
+ return path.size();
+ }
+
+ return pos;
+}
+
+int IncomingHTTP2Connection::on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t nameLen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data)
+{
+ auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+
+ if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+ if (nghttp2_check_header_name(name, nameLen) == 0) {
+ vinfolog("Invalid header name");
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+#ifdef HAVE_NGHTTP2_CHECK_HEADER_VALUE_RFC9113
+ if (nghttp2_check_header_value_rfc9113(value, valuelen) == 0) {
+ vinfolog("Invalid header value");
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+#endif /* HAVE_NGHTTP2_CHECK_HEADER_VALUE_RFC9113 */
+
+ auto headerMatches = [name, nameLen](const std::string& expected) -> bool {
+ return nameLen == expected.size() && memcmp(name, expected.data(), expected.size()) == 0;
+ };
+
+ auto stream = conn->d_currentStreams.find(frame->hd.stream_id);
+ if (stream == conn->d_currentStreams.end()) {
+ vinfolog("Unable to match the stream ID %d to a known one!", frame->hd.stream_id);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ auto& query = stream->second;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+ auto valueView = std::string_view(reinterpret_cast<const char*>(value), valuelen);
+ if (headerMatches(s_pathHeaderName)) {
+#ifdef HAVE_NGHTTP2_CHECK_PATH
+ if (nghttp2_check_path(value, valuelen) == 0) {
+ vinfolog("Invalid path value");
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+#endif /* HAVE_NGHTTP2_CHECK_PATH */
+
+ auto pathLen = getLengthOfPathWithoutParameters(valueView);
+ query.d_path = valueView.substr(0, pathLen);
+ if (pathLen < valueView.size()) {
+ query.d_queryString = valueView.substr(pathLen);
+ }
+ }
+ else if (headerMatches(s_authorityHeaderName)) {
+ query.d_host = valueView;
+ }
+ else if (headerMatches(s_schemeHeaderName)) {
+ query.d_scheme = valueView;
+ }
+ else if (headerMatches(s_methodHeaderName)) {
+#if HAVE_NGHTTP2_CHECK_METHOD
+ if (nghttp2_check_method(value, valuelen) == 0) {
+ vinfolog("Invalid method value");
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+#endif /* HAVE_NGHTTP2_CHECK_METHOD */
+
+ if (valueView == "GET") {
+ query.d_method = PendingQuery::Method::Get;
+ }
+ else if (valueView == "POST") {
+ query.d_method = PendingQuery::Method::Post;
+ }
+ else {
+ query.d_method = PendingQuery::Method::Unsupported;
+ vinfolog("Unsupported method value");
+ return 0;
+ }
+ }
+
+ if (conn->d_ci.cs->dohFrontend->d_keepIncomingHeaders || (conn->d_ci.cs->dohFrontend->d_trustForwardedForHeader && headerMatches(s_xForwardedForHeaderName))) {
+ if (!query.d_headers) {
+ query.d_headers = std::make_unique<HeadersMap>();
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+ query.d_headers->insert({std::string(reinterpret_cast<const char*>(name), nameLen), std::string(valueView)});
+ }
+ }
+ return 0;
+}
+
+int IncomingHTTP2Connection::on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, IncomingHTTP2Connection::StreamID stream_id, const uint8_t* data, size_t len, void* user_data)
+{
+ auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+ auto stream = conn->d_currentStreams.find(stream_id);
+ if (stream == conn->d_currentStreams.end()) {
+ vinfolog("Unable to match the stream ID %d to a known one!", stream_id);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ if (len > std::numeric_limits<uint16_t>::max() || (std::numeric_limits<uint16_t>::max() - stream->second.d_buffer.size()) < len) {
+ vinfolog("Data frame of size %d is too large for a DNS query (we already have %d)", len, stream->second.d_buffer.size());
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
+ stream->second.d_buffer.insert(stream->second.d_buffer.end(), data, data + len);
+
+ return 0;
+}
+
+int IncomingHTTP2Connection::on_error_callback(nghttp2_session* session, int lib_error_code, const char* msg, size_t len, void* user_data)
+{
+ auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+
+ vinfolog("Error in HTTP/2 connection from %d: %s", conn->d_ci.remote.toStringWithPort(), std::string(msg, len));
+ conn->d_connectionClosing = true;
+ conn->d_needFlush = true;
+ nghttp2_session_terminate_session(conn->d_session.get(), NGHTTP2_NO_ERROR);
+ auto ret = nghttp2_session_send(conn->d_session.get());
+ if (ret != 0) {
+ vinfolog("Error flushing HTTP response on connection from %s: %s", conn->d_ci.remote.toStringWithPort(), nghttp2_strerror(ret));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+IOState IncomingHTTP2Connection::readHTTPData()
+{
+ IOState newState = IOState::Done;
+ size_t got = 0;
+ if (d_in.size() < s_initialReceiveBufferSize) {
+ d_in.resize(std::max(s_initialReceiveBufferSize, d_in.capacity()));
+ }
+ try {
+ newState = d_handler.tryRead(d_in, got, d_in.size(), true);
+ d_in.resize(got);
+
+ if (got > 0) {
+ /* we got something */
+ auto readlen = nghttp2_session_mem_recv(d_session.get(), d_in.data(), d_in.size());
+ /* as long as we don't require a pause by returning nghttp2_error.NGHTTP2_ERR_PAUSE from a CB,
+ all data should be consumed before returning */
+ if (readlen < 0 || static_cast<size_t>(readlen) < d_in.size()) {
+ throw std::runtime_error("Fatal error while passing received data to nghttp2: " + std::string(nghttp2_strerror((int)readlen)));
+ }
+
+ nghttp2_session_send(d_session.get());
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Exception while trying to read from HTTP client connection to %s: %s", d_ci.remote.toStringWithPort(), e.what());
+ handleIOError();
+ return IOState::Done;
+ }
+ return newState;
+}
+
+void IncomingHTTP2Connection::handleReadableIOCallback([[maybe_unused]] int descriptor, FDMultiplexer::funcparam_t& param)
+{
+ auto conn = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(param);
+ conn->handleIO();
+}
+
+void IncomingHTTP2Connection::handleWritableIOCallback([[maybe_unused]] int descriptor, FDMultiplexer::funcparam_t& param)
+{
+ auto conn = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(param);
+ conn->writeToSocket(true);
+}
+
+void IncomingHTTP2Connection::stopIO()
+{
+ d_ioState->reset();
+}
+
+uint32_t IncomingHTTP2Connection::getConcurrentStreamsCount() const
+{
+ return d_currentStreams.size();
+}
+
+boost::optional<struct timeval> IncomingHTTP2Connection::getIdleClientReadTTD(struct timeval now) const
+{
+ auto idleTimeout = d_ci.cs->dohFrontend->d_idleTimeout;
+ if (g_maxTCPConnectionDuration == 0 && idleTimeout == 0) {
+ return boost::none;
+ }
+
+ if (g_maxTCPConnectionDuration > 0) {
+ auto elapsed = now.tv_sec - d_connectionStartTime.tv_sec;
+ if (elapsed < 0 || (static_cast<size_t>(elapsed) >= g_maxTCPConnectionDuration)) {
+ return now;
+ }
+ auto remaining = g_maxTCPConnectionDuration - elapsed;
+ if (idleTimeout == 0 || remaining <= static_cast<size_t>(idleTimeout)) {
+ now.tv_sec += static_cast<time_t>(remaining);
+ return now;
+ }
+ }
+
+ now.tv_sec += idleTimeout;
+ return now;
+}
+
+void IncomingHTTP2Connection::updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback)
+{
+ boost::optional<struct timeval> ttd{boost::none};
+
+ auto shared = std::dynamic_pointer_cast<IncomingHTTP2Connection>(shared_from_this());
+ if (shared) {
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
+
+ if (newState == IOState::NeedRead) {
+ /* use the idle TTL if the handshake has been completed (and proxy protocol payload received, if any),
+ and we have processed at least one query, otherwise we use the shorter read TTL */
+ if ((d_state == State::waitingForQuery || d_state == State::idle) && (d_queriesCount > 0 || d_currentQueriesCount > 0)) {
+ ttd = getIdleClientReadTTD(now);
+ }
+ else {
+ ttd = getClientReadTTD(now);
+ }
+ d_ioState->update(newState, callback, shared, ttd);
+ }
+ else if (newState == IOState::NeedWrite) {
+ ttd = getClientWriteTTD(now);
+ d_ioState->update(newState, callback, shared, ttd);
+ }
+ }
+}
+
+void IncomingHTTP2Connection::handleIOError()
+{
+ d_connectionDied = true;
+ d_out.clear();
+ d_outPos = 0;
+ nghttp2_session_terminate_session(d_session.get(), NGHTTP2_PROTOCOL_ERROR);
+ d_currentStreams.clear();
+ stopIO();
+}
+
+bool IncomingHTTP2Connection::active() const
+{
+ return !d_connectionDied && d_ioState != nullptr;
+}
+
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "config.h"
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+#include <nghttp2/nghttp2.h>
+
+#include "dnsdist-tcp-upstream.hh"
+
+class IncomingHTTP2Connection : public IncomingTCPConnectionState
+{
+public:
+ using StreamID = int32_t;
+
+ class PendingQuery
+ {
+ public:
+ enum class Method : uint8_t
+ {
+ Unknown,
+ Get,
+ Post,
+ Unsupported
+ };
+
+ PacketBuffer d_buffer;
+ PacketBuffer d_response;
+ std::string d_path;
+ std::string d_scheme;
+ std::string d_host;
+ std::string d_queryString;
+ std::string d_sni;
+ std::string d_contentTypeOut;
+ std::unique_ptr<HeadersMap> d_headers;
+ size_t d_queryPos{0};
+ uint32_t d_statusCode{0};
+ Method d_method{Method::Unknown};
+ };
+
+ IncomingHTTP2Connection(ConnectionInfo&& connectionInfo, TCPClientThreadData& threadData, const struct timeval& now);
+ ~IncomingHTTP2Connection() = default;
+ void handleIO() override;
+ void handleResponse(const struct timeval& now, TCPResponse&& response) override;
+ void notifyIOError(const struct timeval& now, TCPResponse&& response) override;
+ bool active() const override;
+
+private:
+ static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data);
+ static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data);
+ static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, StreamID stream_id, const uint8_t* data, size_t len, void* user_data);
+ static int on_stream_close_callback(nghttp2_session* session, StreamID stream_id, uint32_t error_code, void* user_data);
+ static int on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data);
+ static int on_begin_headers_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data);
+ static int on_error_callback(nghttp2_session* session, int lib_error_code, const char* msg, size_t len, void* user_data);
+ static void handleReadableIOCallback(int descriptor, FDMultiplexer::funcparam_t& param);
+ static void handleWritableIOCallback(int descriptor, FDMultiplexer::funcparam_t& param);
+
+ static constexpr size_t s_initialReceiveBufferSize{256U};
+
+ IOState sendResponse(const struct timeval& now, TCPResponse&& response) override;
+ bool forwardViaUDPFirst() const override
+ {
+ return true;
+ }
+ void restoreDOHUnit(std::unique_ptr<DOHUnitInterface>&&) override;
+ std::unique_ptr<DOHUnitInterface> getDOHUnit(uint32_t streamID) override;
+
+ void stopIO();
+ uint32_t getConcurrentStreamsCount() const;
+ void updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback);
+ void handleIOError();
+ bool sendResponse(StreamID streamID, PendingQuery& context, uint16_t responseCode, const HeadersMap& customResponseHeaders, const std::string& contentType = "", bool addContentType = true);
+ void handleIncomingQuery(PendingQuery&& query, StreamID streamID);
+ bool checkALPN();
+ IOState readHTTPData();
+ void handleConnectionReady();
+ IOState handleHandshake(const struct timeval& now) override;
+ bool hasPendingWrite() const;
+ void writeToSocket(bool socketReady);
+ boost::optional<struct timeval> getIdleClientReadTTD(struct timeval now) const;
+
+ std::unique_ptr<nghttp2_session, decltype(&nghttp2_session_del)> d_session{nullptr, nghttp2_session_del};
+ std::unordered_map<StreamID, PendingQuery> d_currentStreams;
+ PacketBuffer d_out;
+ PacketBuffer d_in;
+ size_t d_outPos{0};
+ /* this connection is done, the remote end has closed the connection
+ or something like that. We do not want to try to write to it. */
+ bool d_connectionDied{false};
+ /* we are done reading from this connection, but we might still want to
+ write to it to close it properly */
+ bool d_connectionClosing{false};
+ /* Whether we are still waiting for more data to be buffered
+ before writing to the socket (false) or not. */
+ bool d_needFlush{false};
+ /* Whether we have data that we want to write to the socket,
+ but the socket is full. */
+ bool d_pendingWrite{false};
+};
+
+class NGHTTP2Headers
+{
+public:
+ enum class HeaderConstantIndexes
+ {
+ OK_200_VALUE = 0,
+ METHOD_NAME,
+ METHOD_VALUE,
+ SCHEME_NAME,
+ SCHEME_VALUE,
+ AUTHORITY_NAME,
+ X_FORWARDED_FOR_NAME,
+ PATH_NAME,
+ CONTENT_LENGTH_NAME,
+ STATUS_NAME,
+ LOCATION_NAME,
+ ACCEPT_NAME,
+ ACCEPT_VALUE,
+ CACHE_CONTROL_NAME,
+ CONTENT_TYPE_NAME,
+ CONTENT_TYPE_VALUE,
+ USER_AGENT_NAME,
+ USER_AGENT_VALUE,
+ X_FORWARDED_PORT_NAME,
+ X_FORWARDED_PROTO_NAME,
+ X_FORWARDED_PROTO_VALUE_DNS_OVER_UDP,
+ X_FORWARDED_PROTO_VALUE_DNS_OVER_TCP,
+ X_FORWARDED_PROTO_VALUE_DNS_OVER_TLS,
+ X_FORWARDED_PROTO_VALUE_DNS_OVER_HTTP,
+ X_FORWARDED_PROTO_VALUE_DNS_OVER_HTTPS,
+ COUNT
+ };
+
+ static void addStaticHeader(std::vector<nghttp2_nv>& headers, HeaderConstantIndexes nameKey, HeaderConstantIndexes valueKey);
+ static void addDynamicHeader(std::vector<nghttp2_nv>& headers, HeaderConstantIndexes nameKey, const std::string_view& value);
+ static void addCustomDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& name, const std::string_view& value);
+};
+
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
#include "config.h"
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
#include <nghttp2/nghttp2.h>
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
#include "dnsdist-nghttp2.hh"
+#include "dnsdist-nghttp2-in.hh"
#include "dnsdist-tcp.hh"
#include "dnsdist-tcp-downstream.hh"
#include "dnsdist-downstream-connection.hh"
#include "dolog.hh"
+#include "channel.hh"
#include "iputils.hh"
#include "libssl.hh"
#include "noinitvector.hh"
std::unique_ptr<DoHClientCollection> g_dohClientThreads{nullptr};
std::optional<uint16_t> g_outgoingDoHWorkerThreads{std::nullopt};
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
class DoHConnectionToBackend : public ConnectionToBackend
{
public:
static void handleReadableIOCallback(int fd, FDMultiplexer::funcparam_t& param);
static void handleWritableIOCallback(int fd, FDMultiplexer::funcparam_t& param);
- static void addStaticHeader(std::vector<nghttp2_nv>& headers, const std::string& nameKey, const std::string& valueKey);
- static void addDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& nameKey, const std::string& value);
-
class PendingRequest
{
public:
uint16_t d_responseCode{0};
bool d_finished{false};
};
- void addToIOState(IOState state, FDMultiplexer::callbackfunc_t callback);
- void updateIO(IOState newState, FDMultiplexer::callbackfunc_t callback, bool noTTD = false);
+ void updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback, bool noTTD = false);
void watchForRemoteHostClosingConnection();
void handleResponse(PendingRequest&& request);
void handleResponseError(PendingRequest&& request, const struct timeval& now);
void DoHConnectionToBackend::handleResponse(PendingRequest&& request)
{
- struct timeval now;
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
+
gettimeofday(&now, nullptr);
try {
if (!d_healthCheckQuery) {
}
}
- request.d_sender->handleResponse(now, TCPResponse(std::move(request.d_buffer), std::move(request.d_query.d_idstate), shared_from_this(), d_ds));
+ TCPResponse response(std::move(request.d_query));
+ response.d_buffer = std::move(request.d_buffer);
+ response.d_connection = shared_from_this();
+ response.d_ds = d_ds;
+ request.d_sender->handleResponse(now, std::move(response));
}
catch (const std::exception& e) {
vinfolog("Got exception while handling response for cross-protocol DoH: %s", e.what());
d_ds->reportTimeoutOrError();
}
- request.d_sender->notifyIOError(std::move(request.d_query.d_idstate), now);
+ TCPResponse response(PacketBuffer(), std::move(request.d_query.d_idstate), nullptr, nullptr);
+ request.d_sender->notifyIOError(now, std::move(response));
}
catch (const std::exception& e) {
vinfolog("Got exception while handling response for cross-protocol DoH: %s", e.what());
d_connectionDied = true;
nghttp2_session_terminate_session(d_session.get(), NGHTTP2_PROTOCOL_ERROR);
- struct timeval now;
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
+
gettimeofday(&now, nullptr);
for (auto& request : d_currentStreams) {
handleResponseError(std::move(request.second), now);
return getConcurrentStreamsCount() == 0;
}
-const std::unordered_map<std::string, std::string> DoHConnectionToBackend::s_constants = {
- {"method-name", ":method"},
- {"method-value", "POST"},
- {"scheme-name", ":scheme"},
- {"scheme-value", "https"},
- {"accept-name", "accept"},
- {"accept-value", "application/dns-message"},
- {"content-type-name", "content-type"},
- {"content-type-value", "application/dns-message"},
- {"user-agent-name", "user-agent"},
- {"user-agent-value", "nghttp2-" NGHTTP2_VERSION "/dnsdist"},
- {"authority-name", ":authority"},
- {"path-name", ":path"},
- {"content-length-name", "content-length"},
- {"x-forwarded-for-name", "x-forwarded-for"},
- {"x-forwarded-port-name", "x-forwarded-port"},
- {"x-forwarded-proto-name", "x-forwarded-proto"},
- {"x-forwarded-proto-value-dns-over-udp", "dns-over-udp"},
- {"x-forwarded-proto-value-dns-over-tcp", "dns-over-tcp"},
- {"x-forwarded-proto-value-dns-over-tls", "dns-over-tls"},
- {"x-forwarded-proto-value-dns-over-http", "dns-over-http"},
- {"x-forwarded-proto-value-dns-over-https", "dns-over-https"},
-};
-
-void DoHConnectionToBackend::addStaticHeader(std::vector<nghttp2_nv>& headers, const std::string& nameKey, const std::string& valueKey)
-{
- const auto& name = s_constants.at(nameKey);
- const auto& value = s_constants.at(valueKey);
-
- headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.c_str())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.c_str())), name.size(), value.size(), NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE});
-}
-
-void DoHConnectionToBackend::addDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& nameKey, const std::string& value)
-{
- const auto& name = s_constants.at(nameKey);
-
- headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.c_str())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.c_str())), name.size(), value.size(), NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE});
-}
-
void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender, TCPQuery&& query)
{
auto payloadSize = std::to_string(query.d_buffer.size());
headers.reserve(8 + (addXForwarded ? 3 : 0));
/* Pseudo-headers need to come first (rfc7540 8.1.2.1) */
- addStaticHeader(headers, "method-name", "method-value");
- addStaticHeader(headers, "scheme-name", "scheme-value");
- addDynamicHeader(headers, "authority-name", d_ds->d_config.d_tlsSubjectName);
- addDynamicHeader(headers, "path-name", d_ds->d_config.d_dohPath);
- addStaticHeader(headers, "accept-name", "accept-value");
- addStaticHeader(headers, "content-type-name", "content-type-value");
- addStaticHeader(headers, "user-agent-name", "user-agent-value");
- addDynamicHeader(headers, "content-length-name", payloadSize);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::METHOD_NAME, NGHTTP2Headers::HeaderConstantIndexes::METHOD_VALUE);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_NAME, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_VALUE);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::AUTHORITY_NAME, d_ds->d_config.d_tlsSubjectName);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::PATH_NAME, d_ds->d_config.d_dohPath);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_NAME, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_VALUE);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_VALUE);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_NAME, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_VALUE);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_LENGTH_NAME, payloadSize);
/* no need to add these headers for health-check queries */
if (addXForwarded && query.d_idstate.origRemote.getPort() != 0) {
remote = query.d_idstate.origRemote.toString();
remotePort = std::to_string(query.d_idstate.origRemote.getPort());
- addDynamicHeader(headers, "x-forwarded-for-name", remote);
- addDynamicHeader(headers, "x-forwarded-port-name", remotePort);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_FOR_NAME, remote);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PORT_NAME, remotePort);
if (query.d_idstate.cs != nullptr) {
if (query.d_idstate.cs->isUDP()) {
- addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-udp");
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_UDP);
}
else if (query.d_idstate.cs->isDoH()) {
if (query.d_idstate.cs->hasTLS()) {
- addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-https");
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_HTTPS);
}
else {
- addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-http");
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_HTTP);
}
}
else if (query.d_idstate.cs->hasTLS()) {
- addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-tls");
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_TLS);
}
else {
- addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-tcp");
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_TCP);
}
}
}
*/
nghttp2_data_provider data_provider;
- /* we will not use this pointer */
data_provider.source.ptr = this;
data_provider.read_callback = [](nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -> ssize_t {
- auto conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
+ auto* conn = static_cast<DoHConnectionToBackend*>(user_data);
auto& request = conn->d_currentStreams.at(stream_id);
size_t toCopy = 0;
if (request.d_queryPos < request.d_query.d_buffer.size()) {
class DoHClientThreadData
{
public:
- DoHClientThreadData() :
- mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
+ DoHClientThreadData(pdns::channel::Receiver<CrossProtocolQuery>&& receiver) :
+ mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent())),
+ d_receiver(std::move(receiver))
{
}
std::unique_ptr<FDMultiplexer> mplexer{nullptr};
+ pdns::channel::Receiver<CrossProtocolQuery> d_receiver;
};
void DoHConnectionToBackend::handleReadableIOCallback(int fd, FDMultiplexer::funcparam_t& param)
throw std::runtime_error("Fatal error while passing received data to nghttp2: " + std::string(nghttp2_strerror((int)readlen)));
}
- struct timeval now;
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
+
gettimeofday(&now, nullptr);
conn->d_lastDataReceivedTime = now;
}
}
-void DoHConnectionToBackend::updateIO(IOState newState, FDMultiplexer::callbackfunc_t callback, bool noTTD)
+void DoHConnectionToBackend::updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback, bool noTTD)
{
- struct timeval now;
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
+
gettimeofday(&now, nullptr);
boost::optional<struct timeval> ttd{boost::none};
if (!noTTD) {
auto shared = std::dynamic_pointer_cast<DoHConnectionToBackend>(shared_from_this());
if (shared) {
if (newState == IOState::NeedRead) {
- d_ioState->update(newState, callback, shared, ttd);
+ d_ioState->update(newState, callback, std::move(shared), ttd);
}
else if (newState == IOState::NeedWrite) {
- d_ioState->update(newState, callback, shared, ttd);
+ d_ioState->update(newState, callback, std::move(shared), ttd);
}
}
}
}
}
-void DoHConnectionToBackend::addToIOState(IOState state, FDMultiplexer::callbackfunc_t callback)
-{
- struct timeval now;
- gettimeofday(&now, nullptr);
- boost::optional<struct timeval> ttd{boost::none};
- if (state == IOState::NeedRead) {
- ttd = getBackendReadTTD(now);
- }
- else if (isFresh() && d_firstWrite == 0) {
- /* first write just after the non-blocking connect */
- ttd = getBackendConnectTTD(now);
- }
- else {
- ttd = getBackendWriteTTD(now);
- }
-
- auto shared = std::dynamic_pointer_cast<DoHConnectionToBackend>(shared_from_this());
- if (shared) {
- if (state == IOState::NeedRead) {
- d_ioState->add(state, callback, shared, ttd);
- }
- else if (state == IOState::NeedWrite) {
- d_ioState->add(state, callback, shared, ttd);
- }
- }
-}
-
ssize_t DoHConnectionToBackend::send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
{
DoHConnectionToBackend* conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
}
else {
vinfolog("HTTP response has a non-200 status code: %d", request.d_responseCode);
- struct timeval now;
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
+
gettimeofday(&now, nullptr);
conn->handleResponseError(std::move(request), now);
}
else {
vinfolog("HTTP response has a non-200 status code: %d", request.d_responseCode);
- struct timeval now;
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
+
gettimeofday(&now, nullptr);
conn->handleResponseError(std::move(request), now);
return 0;
}
- struct timeval now;
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
+
gettimeofday(&now, nullptr);
auto request = std::move(stream->second);
conn->d_currentStreams.erase(stream->first);
static void handleCrossProtocolQuery(int pipefd, FDMultiplexer::funcparam_t& param)
{
auto threadData = boost::any_cast<DoHClientThreadData*>(param);
- CrossProtocolQuery* tmp{nullptr};
- ssize_t got = read(pipefd, &tmp, sizeof(tmp));
- if (got == 0) {
- throw std::runtime_error("EOF while reading from the DoH cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
- }
- else if (got == -1) {
- if (errno == EAGAIN || errno == EINTR) {
+ std::unique_ptr<CrossProtocolQuery> cpq{nullptr};
+ try {
+ auto tmp = threadData->d_receiver.receive();
+ if (!tmp) {
return;
}
- throw std::runtime_error("Error while reading from the DoH cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode:" + stringerror());
+ cpq = std::move(*tmp);
}
- else if (got != sizeof(tmp)) {
- throw std::runtime_error("Partial read while reading from the DoH cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
+ catch (const std::exception& e) {
+ throw std::runtime_error("Error while reading from the DoH cross-protocol channel:" + std::string(e.what()));
}
- try {
- struct timeval now;
- gettimeofday(&now, nullptr);
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
+ gettimeofday(&now, nullptr);
- std::shared_ptr<TCPQuerySender> tqs = tmp->getTCPQuerySender();
- auto query = std::move(tmp->query);
- auto downstreamServer = std::move(tmp->downstream);
- delete tmp;
- tmp = nullptr;
+ std::shared_ptr<TCPQuerySender> tqs = cpq->getTCPQuerySender();
+ auto query = std::move(cpq->query);
+ auto downstreamServer = std::move(cpq->downstream);
+ cpq.reset();
- try {
- auto downstream = t_downstreamDoHConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::move(query.d_proxyProtocolPayload));
- downstream->queueQuery(tqs, std::move(query));
- }
- catch (...) {
- tqs->notifyIOError(std::move(query.d_idstate), now);
- }
+ try {
+ auto downstream = t_downstreamDoHConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::move(query.d_proxyProtocolPayload));
+ downstream->queueQuery(tqs, std::move(query));
}
catch (...) {
- delete tmp;
- tmp = nullptr;
+ TCPResponse response(std::move(query));
+ tqs->notifyIOError(now, std::move(response));
}
}
-static void dohClientThread(int crossProtocolPipeFD)
+static void dohClientThread(pdns::channel::Receiver<CrossProtocolQuery>&& receiver)
{
setThreadName("dnsdist/dohClie");
try {
- DoHClientThreadData data;
- data.mplexer->addReadFD(crossProtocolPipeFD, handleCrossProtocolQuery, &data);
+ DoHClientThreadData data(std::move(receiver));
+ data.mplexer->addReadFD(data.d_receiver.getDescriptor(), handleCrossProtocolQuery, &data);
+
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
- struct timeval now;
gettimeofday(&now, nullptr);
time_t lastTimeoutScan = now.tv_sec;
if (g_dohStatesDumpRequested > 0) {
/* no race here, we took the lock so it can only be increased in the meantime */
--g_dohStatesDumpRequested;
- errlog("Dumping the DoH client states, as requested:");
+ infolog("Dumping the DoH client states, as requested:");
data.mplexer->runForAllWatchedFDs([](bool isRead, int fd, const FDMultiplexer::funcparam_t& param, struct timeval ttd) {
struct timeval lnow;
gettimeofday(&lnow, nullptr);
if (ttd.tv_sec > 0) {
- errlog("- Descriptor %d is in %s state, TTD in %d", fd, (isRead ? "read" : "write"), (ttd.tv_sec - lnow.tv_sec));
+ infolog("- Descriptor %d is in %s state, TTD in %d", fd, (isRead ? "read" : "write"), (ttd.tv_sec - lnow.tv_sec));
}
else {
- errlog("- Descriptor %d is in %s state, no TTD set", fd, (isRead ? "read" : "write"));
+ infolog("- Descriptor %d is in %s state, no TTD set", fd, (isRead ? "read" : "write"));
}
if (param.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(param);
- errlog(" - %s", conn->toString());
+ infolog(" - %s", conn->toString());
}
else if (param.type() == typeid(DoHClientThreadData*)) {
- errlog(" - Worker thread pipe");
+ infolog(" - Worker thread pipe");
}
});
- errlog("The DoH client cache has %d active and %d idle outgoing connections cached", t_downstreamDoHConnectionsManager.getActiveCount(), t_downstreamDoHConnectionsManager.getIdleCount());
+ infolog("The DoH client cache has %d active and %d idle outgoing connections cached", t_downstreamDoHConnectionsManager.getActiveCount(), t_downstreamDoHConnectionsManager.getIdleCount());
}
}
}
catch (const std::exception& e) {
- errlog("Error in outgoing DoH thread: %s", e.what());
+ warnlog("Error in outgoing DoH thread: %s", e.what());
}
}
}
return true;
}
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
struct DoHClientCollection::DoHWorkerThread
{
{
}
- DoHWorkerThread(int crossProtocolPipe) :
- d_crossProtocolQueryPipe(crossProtocolPipe)
+ DoHWorkerThread(pdns::channel::Sender<CrossProtocolQuery>&& sender) :
+ d_sender(std::move(sender))
{
}
DoHWorkerThread(DoHWorkerThread&& rhs) :
- d_crossProtocolQueryPipe(rhs.d_crossProtocolQueryPipe)
+ d_sender(std::move(rhs.d_sender))
{
- rhs.d_crossProtocolQueryPipe = -1;
}
DoHWorkerThread& operator=(DoHWorkerThread&& rhs)
{
- if (d_crossProtocolQueryPipe != -1) {
- close(d_crossProtocolQueryPipe);
- }
-
- d_crossProtocolQueryPipe = rhs.d_crossProtocolQueryPipe;
- rhs.d_crossProtocolQueryPipe = -1;
-
+ d_sender = std::move(rhs.d_sender);
return *this;
}
DoHWorkerThread(const DoHWorkerThread& rhs) = delete;
DoHWorkerThread& operator=(const DoHWorkerThread&) = delete;
- ~DoHWorkerThread()
- {
- if (d_crossProtocolQueryPipe != -1) {
- close(d_crossProtocolQueryPipe);
- }
- }
-
- int d_crossProtocolQueryPipe{-1};
+ pdns::channel::Sender<CrossProtocolQuery> d_sender;
};
DoHClientCollection::DoHClientCollection(size_t numberOfThreads) :
}
uint64_t pos = d_pos++;
- auto pipe = d_clientThreads.at(pos % d_numberOfThreads).d_crossProtocolQueryPipe;
- auto tmp = cpq.release();
-
- if (write(pipe, &tmp, sizeof(tmp)) != sizeof(tmp)) {
- delete tmp;
- ++g_stats.outgoingDoHQueryPipeFull;
- tmp = nullptr;
+ if (!d_clientThreads.at(pos % d_numberOfThreads).d_sender.send(std::move(cpq))) {
+ ++dnsdist::metrics::g_stats.outgoingDoHQueryPipeFull;
return false;
}
void DoHClientCollection::addThread()
{
-#ifdef HAVE_NGHTTP2
- auto preparePipe = [](int fds[2], const std::string& type) -> bool {
- if (pipe(fds) < 0) {
- errlog("Error creating the DoH thread %s pipe: %s", type, stringerror());
- return false;
- }
-
- if (!setNonBlocking(fds[0])) {
- int err = errno;
- close(fds[0]);
- close(fds[1]);
- errlog("Error setting the DoH thread %s pipe non-blocking: %s", type, stringerror(err));
- return false;
- }
-
- if (!setNonBlocking(fds[1])) {
- int err = errno;
- close(fds[0]);
- close(fds[1]);
- errlog("Error setting the DoH thread %s pipe non-blocking: %s", type, stringerror(err));
- return false;
- }
-
- if (g_tcpInternalPipeBufferSize > 0 && getPipeBufferSize(fds[0]) < g_tcpInternalPipeBufferSize) {
- setPipeBufferSize(fds[0], g_tcpInternalPipeBufferSize);
- }
-
- return true;
- };
-
- int crossProtocolFDs[2] = {-1, -1};
- if (!preparePipe(crossProtocolFDs, "cross-protocol")) {
- return;
- }
-
- vinfolog("Adding DoH Client thread");
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ try {
+ auto [sender, receiver] = pdns::channel::createObjectQueue<CrossProtocolQuery>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
- {
+ vinfolog("Adding DoH Client thread");
std::lock_guard<std::mutex> lock(d_mutex);
if (d_numberOfThreads >= d_clientThreads.size()) {
vinfolog("Adding a new DoH client thread would exceed the vector size (%d/%d), skipping. Consider increasing the maximum amount of DoH client threads with setMaxDoHClientThreads() in the configuration.", d_numberOfThreads, d_clientThreads.size());
- close(crossProtocolFDs[0]);
- close(crossProtocolFDs[1]);
return;
}
- /* from now on this side of the pipe will be managed by that object,
- no need to worry about it */
- DoHWorkerThread worker(crossProtocolFDs[1]);
+ DoHWorkerThread worker(std::move(sender));
try {
- std::thread t1(dohClientThread, crossProtocolFDs[0]);
+ std::thread t1(dohClientThread, std::move(receiver));
t1.detach();
}
catch (const std::runtime_error& e) {
- /* the thread creation failed, don't leak */
+ /* the thread creation failed */
errlog("Error creating a DoH thread: %s", e.what());
- close(crossProtocolFDs[0]);
return;
}
d_clientThreads.at(d_numberOfThreads) = std::move(worker);
++d_numberOfThreads;
}
-#else /* HAVE_NGHTTP2 */
+ catch (const std::exception& e) {
+ errlog("Error creating the DoH channel: %s", e.what());
+ return;
+ }
+#else /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
throw std::runtime_error("DoHClientCollection::addThread() called but nghttp2 support is not available");
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
}
bool initDoHWorkers()
{
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
if (!g_outgoingDoHWorkerThreads) {
/* Unless the value has been set to 0 explicitly, always start at least one outgoing DoH worker thread, in case a DoH backend
is added at a later time. */
return true;
#else
return false;
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
}
bool setupDoHClientProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx)
if (ctx == nullptr) {
return false;
}
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
/* we want to set the ALPN to h2, if only to mitigate the ALPACA attack */
const std::vector<std::vector<uint8_t>> h2Alpns = {{'h', '2'}};
ctx->setALPNProtos(h2Alpns);
ctx->setNextProtocolSelectCallback(select_next_proto_callback);
return true;
-#else /* HAVE_NGHTTP2 */
+#else /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
return false;
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
}
bool sendH2Query(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDMultiplexer>& mplexer, std::shared_ptr<TCPQuerySender>& sender, InternalQuery&& query, bool healthCheck)
{
-#ifdef HAVE_NGHTTP2
- struct timeval now;
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ struct timeval now
+ {
+ .tv_sec = 0, .tv_usec = 0
+ };
gettimeofday(&now, nullptr);
if (healthCheck) {
}
return true;
-#else /* HAVE_NGHTTP2 */
+#else /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
return false;
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
}
size_t clearH2Connections()
{
size_t cleared = 0;
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
cleared = t_downstreamDoHConnectionsManager.clear();
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
return cleared;
}
size_t handleH2Timeouts(FDMultiplexer& mplexer, const struct timeval& now)
{
size_t got = 0;
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
auto expiredReadConns = mplexer.getTimeouts(now, false);
for (const auto& cbData : expiredReadConns) {
if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
++got;
}
}
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
return got;
}
void setDoHDownstreamCleanupInterval(uint16_t max)
{
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
DownstreamDoHConnectionsManager::setCleanupInterval(max);
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
}
void setDoHDownstreamMaxIdleTime(uint16_t max)
{
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
DownstreamDoHConnectionsManager::setMaxIdleTime(max);
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
}
void setDoHDownstreamMaxIdleConnectionsPerBackend(size_t max)
{
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
DownstreamDoHConnectionsManager::setMaxIdleConnectionsPerDownstream(max);
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
}
*/
#pragma once
+namespace dnsdist::prometheus
+{
+struct PrometheusMetricDefinition
+{
+ const std::string& name;
+ const std::string& type;
+ const std::string& description;
+ const std::string& customName;
+};
+}
+
#ifndef DISABLE_PROMETHEUS
// Metric types for Prometheus
enum class PrometheusMetricType: uint8_t {
return true;
};
- static bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customName) {
+ static bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def) {
static const std::map<std::string, PrometheusMetricType> namesToTypes = {
{"counter", PrometheusMetricType::counter},
{"gauge", PrometheusMetricType::gauge},
};
- auto realtype = namesToTypes.find(type);
+ auto realtype = namesToTypes.find(def.type);
if (realtype == namesToTypes.end()) {
return false;
}
- metrics.emplace(name, MetricDefinition{realtype->second, description, customName});
+ metrics.emplace(def.name, MetricDefinition{realtype->second, def.description, def.customName});
return true;
}
+++ /dev/null
-../dnsdist-protobuf.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "config.h"
+
+#ifndef DISABLE_PROTOBUF
+#include "base64.hh"
+#include "dnsdist.hh"
+#include "dnsdist-protobuf.hh"
+#include "protozero.hh"
+
+DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSQuestion& dnsquestion) :
+ d_dq(dnsquestion)
+{
+}
+
+DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSResponse& dnsresponse, bool includeCNAME) :
+ d_dq(dnsresponse), d_dr(&dnsresponse), d_type(pdns::ProtoZero::Message::MessageType::DNSResponseType), d_includeCNAME(includeCNAME)
+{
+}
+
+void DNSDistProtoBufMessage::setServerIdentity(const std::string& serverId)
+{
+ d_serverIdentity = serverId;
+}
+
+void DNSDistProtoBufMessage::setRequestor(const ComboAddress& requestor)
+{
+ d_requestor = requestor;
+}
+
+void DNSDistProtoBufMessage::setResponder(const ComboAddress& responder)
+{
+ d_responder = responder;
+}
+
+void DNSDistProtoBufMessage::setRequestorPort(uint16_t port)
+{
+ if (d_requestor) {
+ d_requestor->setPort(port);
+ }
+}
+
+void DNSDistProtoBufMessage::setResponderPort(uint16_t port)
+{
+ if (d_responder) {
+ d_responder->setPort(port);
+ }
+}
+
+void DNSDistProtoBufMessage::setResponseCode(uint8_t rcode)
+{
+ d_rcode = rcode;
+}
+
+void DNSDistProtoBufMessage::setType(pdns::ProtoZero::Message::MessageType type)
+{
+ d_type = type;
+}
+
+void DNSDistProtoBufMessage::setBytes(size_t bytes)
+{
+ d_bytes = bytes;
+}
+
+void DNSDistProtoBufMessage::setTime(time_t sec, uint32_t usec)
+{
+ d_time = std::pair(sec, usec);
+}
+
+void DNSDistProtoBufMessage::setQueryTime(time_t sec, uint32_t usec)
+{
+ d_queryTime = std::pair(sec, usec);
+}
+
+void DNSDistProtoBufMessage::setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass)
+{
+ d_question = DNSDistProtoBufMessage::PBQuestion(name, qtype, qclass);
+}
+
+void DNSDistProtoBufMessage::setEDNSSubnet(const Netmask& nm)
+{
+ d_ednsSubnet = nm;
+}
+
+void DNSDistProtoBufMessage::addTag(const std::string& strValue)
+{
+ d_additionalTags.push_back(strValue);
+}
+
+void DNSDistProtoBufMessage::addMeta(const std::string& key, std::vector<std::string>&& strValues, const std::vector<int64_t>& intValues)
+{
+ auto& entry = d_metaTags[key];
+ for (auto& value : strValues) {
+ entry.d_strings.insert(std::move(value));
+ }
+ for (const auto& value : intValues) {
+ entry.d_integers.insert(value);
+ }
+}
+
+void DNSDistProtoBufMessage::addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)
+{
+ d_additionalRRs.push_back({std::move(qname), strBlob, uTTL, uType, uClass});
+}
+
+void DNSDistProtoBufMessage::serialize(std::string& data) const
+{
+ if ((data.capacity() - data.size()) < 128) {
+ data.reserve(data.size() + 128);
+ }
+ pdns::ProtoZero::Message msg{data};
+
+ msg.setType(d_type);
+
+ if (d_time) {
+ msg.setTime(d_time->first, d_time->second);
+ }
+ else {
+ timespec now{};
+ gettime(&now, true);
+ msg.setTime(now.tv_sec, now.tv_nsec / 1000);
+ }
+
+ const auto distProto = d_dq.getProtocol();
+ pdns::ProtoZero::Message::TransportProtocol protocol = pdns::ProtoZero::Message::TransportProtocol::UDP;
+
+ if (distProto == dnsdist::Protocol::DoTCP) {
+ protocol = pdns::ProtoZero::Message::TransportProtocol::TCP;
+ }
+ else if (distProto == dnsdist::Protocol::DoT) {
+ protocol = pdns::ProtoZero::Message::TransportProtocol::DoT;
+ }
+ else if (distProto == dnsdist::Protocol::DoH) {
+ protocol = pdns::ProtoZero::Message::TransportProtocol::DoH;
+ msg.setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion::HTTP2);
+ }
+ else if (distProto == dnsdist::Protocol::DoH3) {
+ protocol = pdns::ProtoZero::Message::TransportProtocol::DoH;
+ msg.setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion::HTTP3);
+ }
+ else if (distProto == dnsdist::Protocol::DNSCryptUDP) {
+ protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptUDP;
+ }
+ else if (distProto == dnsdist::Protocol::DNSCryptTCP) {
+ protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptTCP;
+ }
+ else if (distProto == dnsdist::Protocol::DoQ) {
+ protocol = pdns::ProtoZero::Message::TransportProtocol::DoQ;
+ }
+
+ msg.setRequest(d_dq.ids.d_protoBufData && d_dq.ids.d_protoBufData->uniqueId ? *d_dq.ids.d_protoBufData->uniqueId : getUniqueID(), d_requestor ? *d_requestor : d_dq.ids.origRemote, d_responder ? *d_responder : d_dq.ids.origDest, d_question ? d_question->d_name : d_dq.ids.qname, d_question ? d_question->d_type : d_dq.ids.qtype, d_question ? d_question->d_class : d_dq.ids.qclass, d_dq.getHeader()->id, protocol, d_bytes ? *d_bytes : d_dq.getData().size());
+
+ if (d_serverIdentity) {
+ msg.setServerIdentity(*d_serverIdentity);
+ }
+ else if (d_ServerIdentityRef != nullptr) {
+ msg.setServerIdentity(*d_ServerIdentityRef);
+ }
+
+ if (d_ednsSubnet) {
+ msg.setEDNSSubnet(*d_ednsSubnet, 128);
+ }
+
+ msg.startResponse();
+ if (d_queryTime) {
+ // coverity[store_truncates_time_t]
+ msg.setQueryTime(d_queryTime->first, d_queryTime->second);
+ }
+ else {
+ msg.setQueryTime(d_dq.getQueryRealTime().tv_sec, d_dq.getQueryRealTime().tv_nsec / 1000);
+ }
+
+ if (d_dr != nullptr) {
+ msg.setResponseCode(d_rcode ? *d_rcode : d_dr->getHeader()->rcode);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ msg.addRRsFromPacket(reinterpret_cast<const char*>(d_dr->getData().data()), d_dr->getData().size(), d_includeCNAME);
+ }
+ else {
+ if (d_rcode) {
+ msg.setResponseCode(*d_rcode);
+ }
+ }
+
+ for (const auto& arr : d_additionalRRs) {
+ msg.addRR(arr.d_name, arr.d_type, arr.d_class, arr.d_ttl, arr.d_data);
+ }
+
+ for (const auto& tag : d_additionalTags) {
+ msg.addPolicyTag(tag);
+ }
+
+ msg.commitResponse();
+
+ if (d_dq.ids.d_protoBufData) {
+ const auto& pbData = d_dq.ids.d_protoBufData;
+ if (!pbData->d_deviceName.empty()) {
+ msg.setDeviceName(pbData->d_deviceName);
+ }
+ if (!pbData->d_deviceID.empty()) {
+ msg.setDeviceId(pbData->d_deviceID);
+ }
+ if (!pbData->d_requestorID.empty()) {
+ msg.setRequestorId(pbData->d_requestorID);
+ }
+ }
+
+ for (const auto& [key, values] : d_metaTags) {
+ if (!values.d_strings.empty() || !values.d_integers.empty()) {
+ msg.setMeta(key, values.d_strings, values.d_integers);
+ }
+ else {
+ /* the MetaValue field is _required_ to exist, even if we have no value */
+ msg.setMeta(key, {std::string()}, {});
+ }
+ }
+}
+
+ProtoBufMetaKey::ProtoBufMetaKey(const std::string& key)
+{
+ auto& idx = s_types.get<NameTag>();
+ auto typeIt = idx.find(key);
+ if (typeIt != idx.end()) {
+ d_type = typeIt->d_type;
+ return;
+ }
+ else {
+ auto [prefix, variable] = splitField(key, ':');
+ if (!variable.empty()) {
+ typeIt = idx.find(prefix);
+ if (typeIt != idx.end() && typeIt->d_prefix) {
+ d_type = typeIt->d_type;
+ if (typeIt->d_numeric) {
+ try {
+ d_numericSubKey = std::stoi(variable);
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error("Unable to parse numeric ProtoBuf key '" + key + "'");
+ }
+ }
+ else {
+ if (!typeIt->d_caseSensitive) {
+ boost::algorithm::to_lower(variable);
+ }
+ d_subKey = variable;
+ }
+ return;
+ }
+ }
+ }
+ throw std::runtime_error("Invalid ProtoBuf key '" + key + "'");
+}
+
+std::vector<std::string> ProtoBufMetaKey::getValues(const DNSQuestion& dnsquestion) const
+{
+ auto& idx = s_types.get<TypeTag>();
+ auto typeIt = idx.find(d_type);
+ if (typeIt == idx.end()) {
+ throw std::runtime_error("Trying to get the values of an unsupported type: " + std::to_string(static_cast<uint8_t>(d_type)));
+ }
+ return (typeIt->d_func)(dnsquestion, d_subKey, d_numericSubKey);
+}
+
+const std::string& ProtoBufMetaKey::getName() const
+{
+ auto& idx = s_types.get<TypeTag>();
+ auto typeIt = idx.find(d_type);
+ if (typeIt == idx.end()) {
+ throw std::runtime_error("Trying to get the name of an unsupported type: " + std::to_string(static_cast<uint8_t>(d_type)));
+ }
+ return typeIt->d_name;
+}
+
+const ProtoBufMetaKey::TypeContainer ProtoBufMetaKey::s_types = {
+ ProtoBufMetaKey::KeyTypeDescription{"sni", Type::SNI, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> { return {dnsquestion.sni}; }, false},
+ ProtoBufMetaKey::KeyTypeDescription{"pool", Type::Pool, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> { return {dnsquestion.ids.poolName}; }, false},
+ ProtoBufMetaKey::KeyTypeDescription{"b64-content", Type::B64Content, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> { const auto& data = dnsquestion.getData(); return {Base64Encode(std::string(data.begin(), data.end()))}; }, false},
+#ifdef HAVE_DNS_OVER_HTTPS
+ ProtoBufMetaKey::KeyTypeDescription{"doh-header", Type::DoHHeader, [](const DNSQuestion& dnsquestion, const std::string& name, uint8_t) -> std::vector<std::string> {
+ if (!dnsquestion.ids.du) {
+ return {};
+ }
+ auto headers = dnsquestion.ids.du->getHTTPHeaders();
+ auto iter = headers.find(name);
+ if (iter != headers.end()) {
+ return {iter->second};
+ }
+ return {};
+ },
+ true, false},
+ ProtoBufMetaKey::KeyTypeDescription{"doh-host", Type::DoHHost, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+ if (dnsquestion.ids.du) {
+ return {dnsquestion.ids.du->getHTTPHost()};
+ }
+ return {};
+ },
+ true, false},
+ ProtoBufMetaKey::KeyTypeDescription{"doh-path", Type::DoHPath, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+ if (dnsquestion.ids.du) {
+ return {dnsquestion.ids.du->getHTTPPath()};
+ }
+ return {};
+ },
+ false},
+ ProtoBufMetaKey::KeyTypeDescription{"doh-query-string", Type::DoHQueryString, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+ if (dnsquestion.ids.du) {
+ return {dnsquestion.ids.du->getHTTPQueryString()};
+ }
+ return {};
+ },
+ false},
+ ProtoBufMetaKey::KeyTypeDescription{"doh-scheme", Type::DoHScheme, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+ if (dnsquestion.ids.du) {
+ return {dnsquestion.ids.du->getHTTPScheme()};
+ }
+ return {};
+ },
+ false, false},
+#endif // HAVE_DNS_OVER_HTTPS
+ ProtoBufMetaKey::KeyTypeDescription{"proxy-protocol-value", Type::ProxyProtocolValue, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t numericSubKey) -> std::vector<std::string> {
+ if (!dnsquestion.proxyProtocolValues) {
+ return {};
+ }
+ for (const auto& value : *dnsquestion.proxyProtocolValues) {
+ if (value.type == numericSubKey) {
+ return {value.content};
+ }
+ }
+ return {};
+ },
+ true, false, true},
+ ProtoBufMetaKey::KeyTypeDescription{"proxy-protocol-values", Type::ProxyProtocolValues, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+ std::vector<std::string> result;
+ if (!dnsquestion.proxyProtocolValues) {
+ return result;
+ }
+ for (const auto& value : *dnsquestion.proxyProtocolValues) {
+ result.push_back(std::to_string(value.type) + ":" + value.content);
+ }
+ return result;
+ }},
+ ProtoBufMetaKey::KeyTypeDescription{"tag", Type::Tag, [](const DNSQuestion& dnsquestion, const std::string& subKey, uint8_t) -> std::vector<std::string> {
+ if (!dnsquestion.ids.qTag) {
+ return {};
+ }
+ for (const auto& [key, value] : *dnsquestion.ids.qTag) {
+ if (key == subKey) {
+ return {value};
+ }
+ }
+ return {};
+ },
+ true, true},
+ ProtoBufMetaKey::KeyTypeDescription{"tags", Type::Tags, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+ std::vector<std::string> result;
+ if (!dnsquestion.ids.qTag) {
+ return result;
+ }
+ for (const auto& [key, value] : *dnsquestion.ids.qTag) {
+ if (value.empty()) {
+ /* avoids a spurious ':' when the value is empty */
+ result.push_back(key);
+ }
+ else {
+ auto tag = key;
+ tag.append(":");
+ tag.append(value);
+ result.push_back(tag);
+ }
+ }
+ return result;
+ }},
+};
+
+#endif /* DISABLE_PROTOBUF */
+++ /dev/null
-../dnsdist-protobuf.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "dnsdist.hh"
+#include "dnsname.hh"
+
+#ifndef DISABLE_PROTOBUF
+#include "protozero.hh"
+
+class DNSDistProtoBufMessage
+{
+public:
+ DNSDistProtoBufMessage(const DNSQuestion& dnsquestion);
+ DNSDistProtoBufMessage(const DNSResponse& dnsresponse, bool includeCNAME);
+ DNSDistProtoBufMessage(const DNSQuestion&&) = delete;
+ DNSDistProtoBufMessage(const DNSResponse&&, bool) = delete;
+
+ void setServerIdentity(const std::string& serverId);
+ void setRequestor(const ComboAddress& requestor);
+ void setResponder(const ComboAddress& responder);
+ void setRequestorPort(uint16_t port);
+ void setResponderPort(uint16_t port);
+ void setResponseCode(uint8_t rcode);
+ void setType(pdns::ProtoZero::Message::MessageType type);
+ void setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion version);
+ void setBytes(size_t bytes);
+ void setTime(time_t sec, uint32_t usec);
+ void setQueryTime(time_t sec, uint32_t usec);
+ void setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass);
+ void setEDNSSubnet(const Netmask& netmask);
+
+ void addTag(const std::string& strValue);
+ void addMeta(const std::string& key, std::vector<std::string>&& strValues, const std::vector<int64_t>& intValues);
+ void addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& data);
+
+ void serialize(std::string& data) const;
+
+ [[nodiscard]] std::string toDebugString() const;
+
+private:
+ struct PBRecord
+ {
+ DNSName d_name;
+ std::string d_data;
+ uint32_t d_ttl;
+ uint16_t d_type;
+ uint16_t d_class;
+ };
+ struct PBQuestion
+ {
+ PBQuestion(DNSName name, uint16_t type, uint16_t class_) :
+ d_name(std::move(name)), d_type(type), d_class(class_)
+ {
+ }
+
+ DNSName d_name;
+ uint16_t d_type;
+ uint16_t d_class;
+ };
+
+ std::vector<PBRecord> d_additionalRRs;
+ std::vector<std::string> d_additionalTags;
+ struct MetaValue
+ {
+ std::unordered_set<std::string> d_strings;
+ std::unordered_set<int64_t> d_integers;
+ };
+ std::unordered_map<std::string, MetaValue> d_metaTags;
+
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const DNSQuestion& d_dq;
+ const DNSResponse* d_dr{nullptr};
+ const std::string* d_ServerIdentityRef{nullptr};
+
+ boost::optional<PBQuestion> d_question{boost::none};
+ boost::optional<std::string> d_serverIdentity{boost::none};
+ boost::optional<ComboAddress> d_requestor{boost::none};
+ boost::optional<ComboAddress> d_responder{boost::none};
+ boost::optional<Netmask> d_ednsSubnet{boost::none};
+ boost::optional<std::pair<time_t, uint32_t>> d_time{boost::none};
+ boost::optional<std::pair<time_t, uint32_t>> d_queryTime{boost::none};
+ boost::optional<size_t> d_bytes{boost::none};
+ boost::optional<uint8_t> d_rcode{boost::none};
+
+ pdns::ProtoZero::Message::MessageType d_type{pdns::ProtoZero::Message::MessageType::DNSQueryType};
+ bool d_includeCNAME{false};
+};
+
+class ProtoBufMetaKey
+{
+ enum class Type : uint8_t
+ {
+ SNI,
+ Pool,
+ B64Content,
+ DoHHeader,
+ DoHHost,
+ DoHPath,
+ DoHQueryString,
+ DoHScheme,
+ ProxyProtocolValue,
+ ProxyProtocolValues,
+ Tag,
+ Tags
+ };
+
+ struct KeyTypeDescription
+ {
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const std::string d_name;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const Type d_type;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ const std::function<std::vector<std::string>(const DNSQuestion&, const std::string&, uint8_t)> d_func;
+ bool d_prefix{false};
+ bool d_caseSensitive{true};
+ bool d_numeric{false};
+ };
+
+ struct NameTag
+ {
+ };
+ struct TypeTag
+ {
+ };
+
+ using TypeContainer = boost::multi_index_container<
+ KeyTypeDescription,
+ indexed_by<
+ hashed_unique<tag<NameTag>, member<KeyTypeDescription, const std::string, &KeyTypeDescription::d_name>>,
+ hashed_unique<tag<TypeTag>, member<KeyTypeDescription, const Type, &KeyTypeDescription::d_type>>>>;
+
+ static const TypeContainer s_types;
+
+public:
+ ProtoBufMetaKey(const std::string& key);
+
+ [[nodiscard]] const std::string& getName() const;
+ [[nodiscard]] std::vector<std::string> getValues(const DNSQuestion& dnsquestion) const;
+
+private:
+ std::string d_subKey;
+ uint8_t d_numericSubKey{0};
+ Type d_type;
+};
+
+#endif /* DISABLE_PROTOBUF */
+++ /dev/null
-../dnsdist-protocols.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <algorithm>
+#include <stdexcept>
+
+#include "dnsdist-protocols.hh"
+
+namespace dnsdist
+{
+const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_names = {
+ "DoUDP",
+ "DoTCP",
+ "DNSCryptUDP",
+ "DNSCryptTCP",
+ "DoT",
+ "DoH",
+ "DoQ",
+ "DoH3"};
+
+const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_prettyNames = {
+ "Do53 UDP",
+ "Do53 TCP",
+ "DNSCrypt UDP",
+ "DNSCrypt TCP",
+ "DNS over TLS",
+ "DNS over HTTPS",
+ "DNS over QUIC",
+ "DNS over HTTP/3"};
+
+Protocol::Protocol(const std::string& protocol)
+{
+ const auto& namesIt = std::find(s_names.begin(), s_names.end(), protocol);
+ if (namesIt == s_names.end()) {
+ throw std::runtime_error("Unknown protocol name: '" + protocol + "'");
+ }
+
+ auto index = std::distance(s_names.begin(), namesIt);
+ d_protocol = static_cast<Protocol::typeenum>(index);
+}
+
+bool Protocol::operator==(Protocol::typeenum type) const
+{
+ return d_protocol == type;
+}
+
+bool Protocol::operator!=(Protocol::typeenum type) const
+{
+ return d_protocol != type;
+}
+
+const std::string& Protocol::toString() const
+{
+ return s_names.at(static_cast<uint8_t>(d_protocol));
+}
+
+const std::string& Protocol::toPrettyString() const
+{
+ return s_prettyNames.at(static_cast<uint8_t>(d_protocol));
+}
+
+bool Protocol::isUDP() const
+{
+ return d_protocol == DoUDP || d_protocol == DNSCryptUDP;
+}
+
+bool Protocol::isEncrypted() const
+{
+ return d_protocol != DoUDP && d_protocol != DoTCP;
+}
+
+uint8_t Protocol::toNumber() const
+{
+ return static_cast<uint8_t>(d_protocol);
+}
+}
+++ /dev/null
-../dnsdist-protocols.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <string>
+
+namespace dnsdist
+{
+class Protocol
+{
+public:
+ enum typeenum : uint8_t
+ {
+ DoUDP = 0,
+ DoTCP,
+ DNSCryptUDP,
+ DNSCryptTCP,
+ DoT,
+ DoH,
+ DoQ,
+ DoH3
+ };
+
+ Protocol(typeenum protocol = DoUDP) :
+ d_protocol(protocol)
+ {
+ if (protocol >= s_names.size()) {
+ throw std::runtime_error("Unknown protocol: '" + std::to_string(protocol) + "'");
+ }
+ }
+
+ explicit Protocol(const std::string& protocol);
+
+ bool operator==(typeenum) const;
+ bool operator!=(typeenum) const;
+
+ const std::string& toString() const;
+ const std::string& toPrettyString() const;
+ bool isUDP() const;
+ bool isEncrypted() const;
+ uint8_t toNumber() const;
+
+private:
+ typeenum d_protocol;
+
+ static constexpr size_t s_numberOfProtocols = 8;
+ static const std::array<std::string, s_numberOfProtocols> s_names;
+ static const std::array<std::string, s_numberOfProtocols> s_prettyNames;
+};
+}
*/
#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-metrics.hh"
#include "dolog.hh"
NetmaskGroup g_proxyProtocolACL;
ssize_t used = parseProxyHeader(query, proxyProto, realRemote, realDestination, tcp, values);
if (used <= 0) {
- ++g_stats.proxyProtocolInvalid;
+ ++dnsdist::metrics::g_stats.proxyProtocolInvalid;
vinfolog("Ignoring invalid proxy protocol (%d, %d) query over %s from %s", query.size(), used, (isTCP ? "TCP" : "UDP"), remote.toStringWithPort());
return false;
}
else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
vinfolog("Proxy protocol header in %s packet from %s is larger than proxy-protocol-maximum-size (%d), dropping", (isTCP ? "TCP" : "UDP"), remote.toStringWithPort(), used);
- ++g_stats.proxyProtocolInvalid;
+ ++dnsdist::metrics::g_stats.proxyProtocolInvalid;
return false;
}
/* on TCP we have not read the actual query yet */
if (!isTCP && query.size() < sizeof(struct dnsheader)) {
- ++g_stats.nonCompliantQueries;
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
return false;
}
if (proxyProto && g_applyACLToProxiedClients) {
if (!acl.match(realRemote)) {
vinfolog("Query from %s dropped because of ACL", realRemote.toStringWithPort());
- ++g_stats.aclDrops;
+ ++dnsdist::metrics::g_stats.aclDrops;
return false;
}
}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <vector>
+
+#include "dnsdist-resolver.hh"
+#include "iputils.hh"
+
+namespace dnsdist::resolver
+{
+void asynchronousResolver(const std::string& hostname, const std::function<void(const std::string& hostname, std::vector<ComboAddress>& ips)>& callback)
+{
+ addrinfo hints{};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_ADDRCONFIG;
+ hints.ai_socktype = SOCK_DGRAM;
+ addrinfo* infosRaw{nullptr};
+ std::vector<ComboAddress> addresses;
+ auto ret = getaddrinfo(hostname.c_str(), nullptr, &hints, &infosRaw);
+ if (ret != 0) {
+ callback(hostname, addresses);
+ return;
+ }
+ auto infos = std::unique_ptr<addrinfo, decltype(&freeaddrinfo)>(infosRaw, &freeaddrinfo);
+ for (const auto* addr = infos.get(); addr != nullptr; addr = addr->ai_next) {
+ try {
+ addresses.emplace_back(addr->ai_addr, addr->ai_addrlen);
+ }
+ catch (...) {
+ }
+ }
+ callback(hostname, addresses);
+}
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#include "iputils.hh"
+
+namespace dnsdist::resolver
+{
+void asynchronousResolver(const std::string& hostname, const std::function<void(const std::string& hostname, std::vector<ComboAddress>& ips)>& callback);
+}
+++ /dev/null
-../dnsdist-rings.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <fstream>
+
+#include "dnsdist-rings.hh"
+
+void Rings::setCapacity(size_t newCapacity, size_t numberOfShards)
+{
+ if (d_initialized) {
+ throw std::runtime_error("Rings::setCapacity() should not be called once the rings have been initialized");
+ }
+ d_capacity = newCapacity;
+ d_numberOfShards = numberOfShards;
+}
+
+void Rings::init()
+{
+ if (d_initialized.exchange(true)) {
+ throw std::runtime_error("Rings::init() should only be called once");
+ }
+
+ if (d_numberOfShards <= 1) {
+ d_nbLockTries = 0;
+ }
+
+ d_shards.resize(d_numberOfShards);
+
+ /* resize all the rings */
+ for (auto& shard : d_shards) {
+ shard = std::make_unique<Shard>();
+ if (shouldRecordQueries()) {
+ shard->queryRing.lock()->set_capacity(d_capacity / d_numberOfShards);
+ }
+ if (shouldRecordResponses()) {
+ shard->respRing.lock()->set_capacity(d_capacity / d_numberOfShards);
+ }
+ }
+
+ /* we just recreated the shards so they are now empty */
+ d_nbQueryEntries = 0;
+ d_nbResponseEntries = 0;
+}
+
+void Rings::setNumberOfLockRetries(size_t retries)
+{
+ if (d_numberOfShards <= 1) {
+ d_nbLockTries = 0;
+ }
+ else {
+ d_nbLockTries = retries;
+ }
+}
+
+void Rings::setRecordQueries(bool record)
+{
+ d_recordQueries = record;
+}
+
+void Rings::setRecordResponses(bool record)
+{
+ d_recordResponses = record;
+}
+
+size_t Rings::numDistinctRequestors()
+{
+ std::set<ComboAddress, ComboAddress::addressOnlyLessThan> requestors;
+ for (const auto& shard : d_shards) {
+ auto queries = shard->queryRing.lock();
+ for (const auto& query : *queries) {
+ requestors.insert(query.requestor);
+ }
+ }
+ return requestors.size();
+}
+
+std::unordered_map<int, vector<boost::variant<string, double>>> Rings::getTopBandwidth(unsigned int numentries)
+{
+ map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts;
+ uint64_t total = 0;
+ for (const auto& shard : d_shards) {
+ {
+ auto queries = shard->queryRing.lock();
+ for (const auto& query : *queries) {
+ counts[query.requestor] += query.size;
+ total += query.size;
+ }
+ }
+ {
+ auto responses = shard->respRing.lock();
+ for (const auto& response : *responses) {
+ counts[response.requestor] += response.size;
+ total += response.size;
+ }
+ }
+ }
+
+ using ret_t = vector<pair<unsigned int, ComboAddress>>;
+ ret_t rcounts;
+ rcounts.reserve(counts.size());
+ for (const auto& count : counts) {
+ rcounts.emplace_back(count.second, count.first);
+ }
+ numentries = rcounts.size() < numentries ? rcounts.size() : numentries;
+ partial_sort(rcounts.begin(), rcounts.begin() + numentries, rcounts.end(), [](const ret_t::value_type& lhs, const ret_t::value_type& rhs) {
+ return (rhs.first < lhs.first);
+ });
+ std::unordered_map<int, vector<boost::variant<string, double>>> ret;
+ uint64_t rest = 0;
+ int count = 1;
+ for (const auto& rcount : rcounts) {
+ if (count == static_cast<int>(numentries + 1)) {
+ rest += rcount.first;
+ }
+ else {
+ ret.insert({count++, {rcount.second.toString(), rcount.first, 100.0 * rcount.first / static_cast<double>(total)}});
+ }
+ }
+
+ if (total > 0) {
+ ret.insert({count, {"Rest", rest, 100.0 * static_cast<double>(rest) / static_cast<double>(total)}});
+ }
+ else {
+ ret.insert({count, {"Rest", rest, 100.0}});
+ }
+
+ return ret;
+}
+
+size_t Rings::loadFromFile(const std::string& filepath, const struct timespec& now)
+{
+ ifstream ifs(filepath);
+ if (!ifs) {
+ throw std::runtime_error("unable to open the file at " + filepath);
+ }
+
+ size_t inserted = 0;
+ string line;
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
+
+ while (std::getline(ifs, line)) {
+ boost::trim_right_if(line, boost::is_any_of(" \r\n\x1a"));
+ boost::trim_left(line);
+ bool isResponse = false;
+ vector<string> parts;
+ stringtok(parts, line, " \t,");
+
+ if (parts.size() == 8) {
+ }
+ else if (parts.size() >= 11 && parts.size() <= 13) {
+ isResponse = true;
+ }
+ else {
+ cerr << "skipping line with " << parts.size() << "parts: " << line << endl;
+ continue;
+ }
+
+ size_t idx = 0;
+ vector<string> timeStr;
+ stringtok(timeStr, parts.at(idx++), ".");
+ if (timeStr.size() != 2) {
+ cerr << "skipping invalid time " << parts.at(0) << endl;
+ continue;
+ }
+
+ timespec when{};
+ try {
+ when.tv_sec = now.tv_sec + std::stoi(timeStr.at(0));
+ when.tv_nsec = now.tv_nsec + static_cast<long>(std::stoi(timeStr.at(1)) * 100 * 1000 * 1000);
+ }
+ catch (const std::exception& e) {
+ cerr << "error parsing time " << parts.at(idx - 1) << " from line " << line << endl;
+ continue;
+ }
+
+ ComboAddress from(parts.at(idx++));
+ ComboAddress dest;
+ dnsdist::Protocol protocol(parts.at(idx++));
+ if (isResponse) {
+ dest = ComboAddress(parts.at(idx++));
+ }
+ /* skip ID */
+ idx++;
+ DNSName qname(parts.at(idx++));
+ QType qtype(QType::chartocode(parts.at(idx++).c_str()));
+
+ if (isResponse) {
+ insertResponse(when, from, qname, qtype.getCode(), 0, 0, dnsHeader, dest, protocol);
+ }
+ else {
+ insertQuery(when, from, qname, qtype.getCode(), 0, dnsHeader, protocol);
+ }
+ ++inserted;
+ }
+
+ return inserted;
+}
+
+bool Rings::Response::isACacheHit() const
+{
+ bool hit = ds.sin4.sin_family == 0;
+ if (!hit && ds.isIPv4() && ds.sin4.sin_addr.s_addr == 0 && ds.sin4.sin_port == 0) {
+ hit = true;
+ }
+ return hit;
+}
+++ /dev/null
-../dnsdist-rings.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <time.h>
+#include <unordered_map>
+
+#include <boost/variant.hpp>
+
+#include "circular_buffer.hh"
+#include "dnsname.hh"
+#include "iputils.hh"
+#include "lock.hh"
+#include "stat_t.hh"
+#include "dnsdist-protocols.hh"
+#include "dnsdist-mac-address.hh"
+
+struct Rings
+{
+ struct Query
+ {
+ ComboAddress requestor;
+ DNSName name;
+ struct timespec when;
+ struct dnsheader dh;
+ uint16_t size;
+ uint16_t qtype;
+ // incoming protocol
+ dnsdist::Protocol protocol;
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+ dnsdist::MacAddress macaddress;
+ bool hasmac{false};
+#endif
+ };
+ struct Response
+ {
+ ComboAddress requestor;
+ ComboAddress ds; // who handled it
+ DNSName name;
+ struct timespec when;
+ struct dnsheader dh;
+ unsigned int usec;
+ uint16_t size;
+ uint16_t qtype;
+ // outgoing protocol
+ dnsdist::Protocol protocol;
+
+ bool isACacheHit() const;
+ };
+
+ struct Shard
+ {
+ LockGuarded<boost::circular_buffer<Query>> queryRing;
+ LockGuarded<boost::circular_buffer<Response>> respRing;
+ };
+
+ Rings(size_t capacity = 10000, size_t numberOfShards = 10, size_t nbLockTries = 5, bool keepLockingStats = false) :
+ d_blockingQueryInserts(0), d_blockingResponseInserts(0), d_deferredQueryInserts(0), d_deferredResponseInserts(0), d_nbQueryEntries(0), d_nbResponseEntries(0), d_currentShardId(0), d_capacity(capacity), d_numberOfShards(numberOfShards), d_nbLockTries(nbLockTries), d_keepLockingStats(keepLockingStats)
+ {
+ }
+
+ std::unordered_map<int, vector<boost::variant<string, double>>> getTopBandwidth(unsigned int numentries);
+ size_t numDistinctRequestors();
+ /* this function should not be called after init() has been called */
+ void setCapacity(size_t newCapacity, size_t numberOfShards);
+
+ /* This function should only be called at configuration time before any query or response has been inserted */
+ void init();
+
+ void setNumberOfLockRetries(size_t retries);
+ void setRecordQueries(bool);
+ void setRecordResponses(bool);
+
+ size_t getNumberOfShards() const
+ {
+ return d_numberOfShards;
+ }
+
+ size_t getNumberOfQueryEntries() const
+ {
+ return d_nbQueryEntries;
+ }
+
+ size_t getNumberOfResponseEntries() const
+ {
+ return d_nbResponseEntries;
+ }
+
+ void insertQuery(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
+ {
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+ dnsdist::MacAddress macaddress;
+ bool hasmac{false};
+ if (dnsdist::MacAddressesCache::get(requestor, macaddress.data(), macaddress.size()) == 0) {
+ hasmac = true;
+ }
+#endif
+ for (size_t idx = 0; idx < d_nbLockTries; idx++) {
+ auto& shard = getOneShard();
+ auto lock = shard->queryRing.try_lock();
+ if (lock.owns_lock()) {
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+ insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
+#else
+ insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
+#endif
+ return;
+ }
+ if (d_keepLockingStats) {
+ ++d_deferredQueryInserts;
+ }
+ }
+
+ /* out of luck, let's just wait */
+ if (d_keepLockingStats) {
+ ++d_blockingResponseInserts;
+ }
+ auto& shard = getOneShard();
+ auto lock = shard->queryRing.lock();
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+ insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
+#else
+ insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
+#endif
+ }
+
+ void insertResponse(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, unsigned int size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
+ {
+ for (size_t idx = 0; idx < d_nbLockTries; idx++) {
+ auto& shard = getOneShard();
+ auto lock = shard->respRing.try_lock();
+ if (lock.owns_lock()) {
+ insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol);
+ return;
+ }
+ if (d_keepLockingStats) {
+ ++d_deferredResponseInserts;
+ }
+ }
+
+ /* out of luck, let's just wait */
+ if (d_keepLockingStats) {
+ ++d_blockingResponseInserts;
+ }
+ auto& shard = getOneShard();
+ auto lock = shard->respRing.lock();
+ insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol);
+ }
+
+ void clear()
+ {
+ for (auto& shard : d_shards) {
+ shard->queryRing.lock()->clear();
+ shard->respRing.lock()->clear();
+ }
+
+ d_nbQueryEntries.store(0);
+ d_nbResponseEntries.store(0);
+ d_currentShardId.store(0);
+ d_blockingQueryInserts.store(0);
+ d_blockingResponseInserts.store(0);
+ d_deferredQueryInserts.store(0);
+ d_deferredResponseInserts.store(0);
+ }
+
+ /* this should be called in the unit tests, and never at runtime */
+ void reset()
+ {
+ clear();
+ d_initialized = false;
+ }
+
+ /* load the content of the ring buffer from a file in the format emitted by grepq(),
+ only useful for debugging purposes */
+ size_t loadFromFile(const std::string& filepath, const struct timespec& now);
+
+ bool shouldRecordQueries() const
+ {
+ return d_recordQueries;
+ }
+
+ bool shouldRecordResponses() const
+ {
+ return d_recordResponses;
+ }
+
+ std::vector<std::unique_ptr<Shard>> d_shards;
+ pdns::stat_t d_blockingQueryInserts;
+ pdns::stat_t d_blockingResponseInserts;
+ pdns::stat_t d_deferredQueryInserts;
+ pdns::stat_t d_deferredResponseInserts;
+
+private:
+ size_t getShardId()
+ {
+ return (d_currentShardId++ % d_numberOfShards);
+ }
+
+ std::unique_ptr<Shard>& getOneShard()
+ {
+ return d_shards[getShardId()];
+ }
+
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+ void insertQueryLocked(boost::circular_buffer<Query>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol, const dnsdist::MacAddress& macaddress, const bool hasmac)
+#else
+ void insertQueryLocked(boost::circular_buffer<Query>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
+#endif
+ {
+ if (!ring.full()) {
+ d_nbQueryEntries++;
+ }
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+ Rings::Query query{requestor, name, when, dh, size, qtype, protocol, dnsdist::MacAddress{""}, hasmac};
+ if (hasmac) {
+ memcpy(query.macaddress.data(), macaddress.data(), macaddress.size());
+ }
+ ring.push_back(std::move(query));
+#else
+ ring.push_back({requestor, name, when, dh, size, qtype, protocol});
+#endif
+ }
+
+ void insertResponseLocked(boost::circular_buffer<Response>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, uint16_t size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
+ {
+ if (!ring.full()) {
+ d_nbResponseEntries++;
+ }
+ ring.push_back({requestor, backend, name, when, dh, usec, size, qtype, protocol});
+ }
+
+ std::atomic<size_t> d_nbQueryEntries;
+ std::atomic<size_t> d_nbResponseEntries;
+ std::atomic<size_t> d_currentShardId;
+ std::atomic<bool> d_initialized{false};
+
+ size_t d_capacity;
+ size_t d_numberOfShards;
+ size_t d_nbLockTries = 5;
+ bool d_keepLockingStats{false};
+ bool d_recordQueries{true};
+ bool d_recordResponses{true};
+};
+
+extern Rings g_rings;
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dnsdist-rule-chains.hh"
+
+namespace dnsdist::rules
+{
+GlobalStateHolder<std::vector<RuleAction>> g_ruleactions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_respruleactions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_cachehitrespruleactions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_selfansweredrespruleactions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_cacheInsertedRespRuleActions;
+
+static const std::vector<ResponseRuleChainDescription> s_responseRuleChains{
+ {"", "response-rules", s_respruleactions},
+ {"CacheHit", "cache-hit-response-rules", s_cachehitrespruleactions},
+ {"CacheInserted", "cache-inserted-response-rules", s_selfansweredrespruleactions},
+ {"SelfAnswered", "self-answered-response-rules", s_cacheInsertedRespRuleActions},
+};
+
+const std::vector<ResponseRuleChainDescription>& getResponseRuleChains()
+{
+ return s_responseRuleChains;
+}
+
+GlobalStateHolder<std::vector<ResponseRuleAction>>& getResponseRuleChainHolder(ResponseRuleChain chain)
+{
+ return s_responseRuleChains.at(static_cast<size_t>(chain)).holder;
+}
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "sholder.hh"
+#include "uuid-utils.hh"
+
+class DNSRule;
+class DNSAction;
+class DNSResponseAction;
+
+namespace dnsdist::rules
+{
+enum class ResponseRuleChain : uint8_t
+{
+ ResponseRules = 0,
+ CacheHitResponseRules = 1,
+ CacheInsertedResponseRules = 2,
+ SelfAnsweredResponseRules = 3,
+ ResponseRuleChainsCount = 4
+};
+
+struct RuleAction
+{
+ std::shared_ptr<DNSRule> d_rule;
+ std::shared_ptr<DNSAction> d_action;
+ std::string d_name;
+ boost::uuids::uuid d_id;
+ uint64_t d_creationOrder;
+};
+
+struct ResponseRuleAction
+{
+ std::shared_ptr<DNSRule> d_rule;
+ std::shared_ptr<DNSResponseAction> d_action;
+ std::string d_name;
+ boost::uuids::uuid d_id;
+ uint64_t d_creationOrder;
+};
+
+struct ResponseRuleChainDescription
+{
+ std::string prefix;
+ std::string metricName;
+ GlobalStateHolder<std::vector<ResponseRuleAction>>& holder;
+};
+
+extern GlobalStateHolder<std::vector<RuleAction>> g_ruleactions;
+
+const std::vector<ResponseRuleChainDescription>& getResponseRuleChains();
+GlobalStateHolder<std::vector<ResponseRuleAction>>& getResponseRuleChainHolder(ResponseRuleChain chain);
+
+}
#include "dnsdist-lua-ffi.hh"
#include "dolog.hh"
#include "dnsparser.hh"
+#include "dns_random.hh"
class MaxQPSIPRule : public DNSRule
{
class HTTPPathRule : public DNSRule
{
public:
- HTTPPathRule(const std::string& path);
+ HTTPPathRule(std::string path);
bool matches(const DNSQuestion* dq) const override;
string toString() const override;
private:
{
if(d_proba == 1.0)
return true;
- double rnd = 1.0*random() / RAND_MAX;
+ double rnd = 1.0*dns_random_uint32() / UINT32_MAX;
return rnd > (1.0 - d_proba);
}
string toString() const override
class TagRule : public DNSRule
{
public:
- TagRule(const std::string& tag, boost::optional<std::string> value) : d_value(value), d_tag(tag)
+ TagRule(const std::string& tag, boost::optional<std::string> value) : d_value(std::move(value)), d_tag(tag)
{
}
bool matches(const DNSQuestion* dq) const override
class ProxyProtocolValueRule : public DNSRule
{
public:
- ProxyProtocolValueRule(uint8_t type, boost::optional<std::string> value): d_value(value), d_type(type)
+ ProxyProtocolValueRule(uint8_t type, boost::optional<std::string> value): d_value(std::move(value)), d_type(type)
{
}
boost::optional<std::string> d_value;
uint8_t d_type;
};
+
+class PayloadSizeRule : public DNSRule
+{
+ enum class Comparisons : uint8_t { equal, greater, greaterOrEqual, smaller, smallerOrEqual };
+public:
+ PayloadSizeRule(const std::string& comparison, uint16_t size): d_size(size)
+ {
+ if (comparison == "equal") {
+ d_comparison = Comparisons::equal;
+ }
+ else if (comparison == "greater") {
+ d_comparison = Comparisons::greater;
+ }
+ else if (comparison == "greaterOrEqual") {
+ d_comparison = Comparisons::greaterOrEqual;
+ }
+ else if (comparison == "smaller") {
+ d_comparison = Comparisons::smaller;
+ }
+ else if (comparison == "smallerOrEqual") {
+ d_comparison = Comparisons::smallerOrEqual;
+ }
+ else {
+ throw std::runtime_error("Unsupported comparison '" + comparison + "'");
+ }
+ }
+
+ bool matches(const DNSQuestion* dq) const override
+ {
+ const auto size = dq->getData().size();
+
+ switch (d_comparison) {
+ case Comparisons::equal:
+ return size == d_size;
+ case Comparisons::greater:
+ return size > d_size;
+ case Comparisons::greaterOrEqual:
+ return size >= d_size;
+ case Comparisons::smaller:
+ return size < d_size;
+ case Comparisons::smallerOrEqual:
+ return size <= d_size;
+ default:
+ return false;
+ }
+ }
+
+ string toString() const override
+ {
+ static const std::array<const std::string, 5> comparisonStr{
+ "equal to" ,
+ "greater than",
+ "equal to or greater than",
+ "smaller than",
+ "equal to or smaller than"
+ };
+ return "payload size is " + comparisonStr.at(static_cast<size_t>(d_comparison)) + " " + std::to_string(d_size);
+ }
+
+private:
+ uint16_t d_size;
+ Comparisons d_comparison;
+};
#include "sstuff.hh"
#include "dnsdist.hh"
+#include "dnsdist-metrics.hh"
#include "dnsdist-random.hh"
#ifndef PACKAGEVERSION
throw std::runtime_error("Looking for a TXT record in an answer smaller than the DNS header");
}
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(answer.data());
+ const dnsheader_aligned dh(answer.data());
PacketReader pr(answer);
uint16_t qdcount = ntohs(dh->qdcount);
uint16_t ancount = ntohs(dh->ancount);
int securityStatus = std::stoi(split.first);
std::string securityMessage = split.second;
- if(securityStatus == 1 && !g_secPollDone) {
- warnlog("Polled security status of version %s at startup, no known issues reported: %s", std::string(VERSION), securityMessage);
+ if (securityStatus == 1 && !g_secPollDone) {
+ infolog("Polled security status of version %s at startup, no known issues reported: %s", std::string(VERSION), securityMessage);
}
- if(securityStatus == 2) {
+ if (securityStatus == 2) {
errlog("PowerDNS DNSDist Security Update Recommended: %s", securityMessage);
}
else if(securityStatus == 3) {
errlog("PowerDNS DNSDist Security Update Mandatory: %s", securityMessage);
}
- g_stats.securityStatus = securityStatus;
+ dnsdist::metrics::g_stats.securityStatus = securityStatus;
g_secPollDone = true;
return;
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
if (releaseVersion) {
warnlog("Error while retrieving the security update for version %s: %s", version, e.what());
}
+++ /dev/null
-../dnsdist-snmp.cc
\ No newline at end of file
--- /dev/null
+
+#include "dnsdist-snmp.hh"
+#include "dnsdist-metrics.hh"
+#include "dolog.hh"
+
+bool g_snmpEnabled{false};
+bool g_snmpTrapsEnabled{false};
+std::unique_ptr<DNSDistSNMPAgent> g_snmpAgent{nullptr};
+
+#ifdef HAVE_NET_SNMP
+
+#define DNSDIST_OID 1, 3, 6, 1, 4, 1, 43315, 3
+#define DNSDIST_STATS_OID DNSDIST_OID, 1
+#define DNSDIST_STATS_TABLE_OID DNSDIST_OID, 2
+#define DNSDIST_TRAPS_OID DNSDIST_OID, 10, 0
+#define DNSDIST_TRAP_OBJECTS_OID DNSDIST_OID, 11
+
+using OIDStat = std::array<oid, 10>;
+using OIDTrap = std::array<oid, 11>;
+using OIDTrapObject = std::array<oid, 11>;
+using OIDStatTable = std::array<oid, 12>;
+
+static const OIDStat queriesOID{DNSDIST_STATS_OID, 1};
+static const OIDStat responsesOID{DNSDIST_STATS_OID, 2};
+static const OIDStat servfailResponsesOID{DNSDIST_STATS_OID, 3};
+static const OIDStat aclDropsOID{DNSDIST_STATS_OID, 4};
+// 5 was BlockFilter, removed in 1.2.0
+static const OIDStat ruleDropOID{DNSDIST_STATS_OID, 6};
+static const OIDStat ruleNXDomainOID{DNSDIST_STATS_OID, 7};
+static const OIDStat ruleRefusedOID{DNSDIST_STATS_OID, 8};
+static const OIDStat selfAnsweredOID{DNSDIST_STATS_OID, 9};
+static const OIDStat downstreamTimeoutsOID{DNSDIST_STATS_OID, 10};
+static const OIDStat downstreamSendErrorsOID{DNSDIST_STATS_OID, 11};
+static const OIDStat truncFailOID{DNSDIST_STATS_OID, 12};
+static const OIDStat noPolicyOID{DNSDIST_STATS_OID, 13};
+static const OIDStat latency0_1OID{DNSDIST_STATS_OID, 14};
+static const OIDStat latency1_10OID{DNSDIST_STATS_OID, 15};
+static const OIDStat latency10_50OID{DNSDIST_STATS_OID, 16};
+static const OIDStat latency50_100OID{DNSDIST_STATS_OID, 17};
+static const OIDStat latency100_1000OID{DNSDIST_STATS_OID, 18};
+static const OIDStat latencySlowOID{DNSDIST_STATS_OID, 19};
+static const OIDStat latencyAvg100OID{DNSDIST_STATS_OID, 20};
+static const OIDStat latencyAvg1000OID{DNSDIST_STATS_OID, 21};
+static const OIDStat latencyAvg10000OID{DNSDIST_STATS_OID, 22};
+static const OIDStat latencyAvg1000000OID{DNSDIST_STATS_OID, 23};
+static const OIDStat uptimeOID{DNSDIST_STATS_OID, 24};
+static const OIDStat realMemoryUsageOID{DNSDIST_STATS_OID, 25};
+static const OIDStat nonCompliantQueriesOID{DNSDIST_STATS_OID, 26};
+static const OIDStat nonCompliantResponsesOID{DNSDIST_STATS_OID, 27};
+static const OIDStat rdQueriesOID{DNSDIST_STATS_OID, 28};
+static const OIDStat emptyQueriesOID{DNSDIST_STATS_OID, 29};
+static const OIDStat cacheHitsOID{DNSDIST_STATS_OID, 30};
+static const OIDStat cacheMissesOID{DNSDIST_STATS_OID, 31};
+static const OIDStat cpuUserMSecOID{DNSDIST_STATS_OID, 32};
+static const OIDStat cpuSysMSecOID{DNSDIST_STATS_OID, 33};
+static const OIDStat fdUsageOID{DNSDIST_STATS_OID, 34};
+static const OIDStat dynBlockedOID{DNSDIST_STATS_OID, 35};
+static const OIDStat dynBlockedNMGSizeOID{DNSDIST_STATS_OID, 36};
+static const OIDStat ruleServFailOID{DNSDIST_STATS_OID, 37};
+static const OIDStat securityStatusOID{DNSDIST_STATS_OID, 38};
+static const OIDStat specialMemoryUsageOID{DNSDIST_STATS_OID, 39};
+static const OIDStat ruleTruncatedOID{DNSDIST_STATS_OID, 40};
+
+static std::unordered_map<oid, dnsdist::metrics::Stats::entry_t> s_statsMap;
+
+/* We are never called for a GETNEXT if it's registered as a
+ "instance", as it's "magically" handled for us. */
+/* a instance handler also only hands us one request at a time, so
+ we don't need to loop over a list of requests; we'll only get one. */
+
+static int handleCounter64Stats(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ if (reqinfo->mode != MODE_GET) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+ return SNMP_ERR_GENERR;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): net-snmp API
+ const auto& stIt = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+ if (stIt == s_statsMap.end()) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (const auto& val = std::get_if<pdns::stat_t*>(&stIt->second)) {
+ return DNSDistSNMPAgent::setCounter64Value(requests, (*val)->load());
+ }
+
+ return SNMP_ERR_GENERR;
+}
+
+static void registerCounter64Stat(const char* name, const OIDStat& statOID, pdns::stat_t* ptr)
+{
+ if (statOID.size() != OID_LENGTH(queriesOID)) {
+ errlog("Invalid OID for SNMP Counter64 statistic %s", name);
+ return;
+ }
+
+ if (s_statsMap.find(statOID.at(statOID.size() - 1)) != s_statsMap.end()) {
+ errlog("OID for SNMP Counter64 statistic %s has already been registered", name);
+ return;
+ }
+
+ s_statsMap[statOID.at(statOID.size() - 1)] = ptr;
+ netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+ handleCounter64Stats,
+ statOID.data(),
+ statOID.size(),
+ HANDLER_CAN_RONLY));
+}
+
+static int handleFloatStats(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ if (reqinfo->mode != MODE_GET) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+ return SNMP_ERR_GENERR;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): net-snmp API
+ const auto& stIt = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+ if (stIt == s_statsMap.end()) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (const auto& val = std::get_if<double*>(&stIt->second)) {
+ std::string str(std::to_string(**val));
+ snmp_set_var_typed_value(requests->requestvb,
+ ASN_OCTET_STR,
+ str.c_str(),
+ str.size());
+ return SNMP_ERR_NOERROR;
+ }
+
+ return SNMP_ERR_GENERR;
+}
+
+static void registerFloatStat(const char* name, const OIDStat& statOID, double* ptr)
+{
+ if (statOID.size() != OID_LENGTH(queriesOID)) {
+ errlog("Invalid OID for SNMP Float statistic %s", name);
+ return;
+ }
+
+ if (s_statsMap.find(statOID.at(statOID.size() - 1)) != s_statsMap.end()) {
+ errlog("OID for SNMP Float statistic %s has already been registered", name);
+ return;
+ }
+
+ s_statsMap[statOID.at(statOID.size() - 1)] = ptr;
+ netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+ handleFloatStats,
+ statOID.data(),
+ statOID.size(),
+ HANDLER_CAN_RONLY));
+}
+
+static int handleGauge64Stats(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ if (reqinfo->mode != MODE_GET) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+ return SNMP_ERR_GENERR;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): net-snmp API
+ const auto& stIt = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+ if (stIt == s_statsMap.end()) {
+ return SNMP_ERR_GENERR;
+ }
+
+ std::string str;
+ uint64_t value = (*std::get_if<dnsdist::metrics::Stats::statfunction_t>(&stIt->second))(str);
+ return DNSDistSNMPAgent::setCounter64Value(requests, value);
+}
+
+static void registerGauge64Stat(const char* name, const OIDStat& statOID, const dnsdist::metrics::Stats::statfunction_t& ptr)
+{
+ if (statOID.size() != OID_LENGTH(queriesOID)) {
+ errlog("Invalid OID for SNMP Gauge64 statistic %s", name);
+ return;
+ }
+
+ if (s_statsMap.find(statOID.at(statOID.size() - 1)) != s_statsMap.end()) {
+ errlog("OID for SNMP Gauge64 statistic %s has already been registered", name);
+ return;
+ }
+
+ s_statsMap[statOID.at(statOID.size() - 1)] = ptr;
+ netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+ handleGauge64Stats,
+ statOID.data(),
+ statOID.size(),
+ HANDLER_CAN_RONLY));
+}
+
+/* column number definitions for table backendStatTable */
+static constexpr unsigned int COLUMN_BACKENDNAME = 2;
+static constexpr unsigned int COLUMN_BACKENDLATENCY = 3;
+static constexpr unsigned int COLUMN_BACKENDWEIGHT = 4;
+static constexpr unsigned int COLUMN_BACKENDOUTSTANDING = 5;
+static constexpr unsigned int COLUMN_BACKENDQPSLIMIT = 6;
+static constexpr unsigned int COLUMN_BACKENDREUSED = 7;
+static constexpr unsigned int COLUMN_BACKENDSTATE = 8;
+static constexpr unsigned int COLUMN_BACKENDADDRESS = 9;
+static constexpr unsigned int COLUMN_BACKENDPOOLS = 10;
+static constexpr unsigned int COLUMN_BACKENDQPS = 11;
+static constexpr unsigned int COLUMN_BACKENDQUERIES = 12;
+static constexpr unsigned int COLUMN_BACKENDORDER = 13;
+
+static const std::array<oid, 9> backendStatTableOID{DNSDIST_STATS_TABLE_OID};
+static const OIDStatTable backendNameOID{DNSDIST_STATS_TABLE_OID, 1, 2};
+static const OIDStatTable backendStateOID{DNSDIST_STATS_TABLE_OID, 1, 8};
+static const OIDStatTable backendAddressOID{DNSDIST_STATS_TABLE_OID, 1, 9};
+
+static const OIDTrapObject socketFamilyOID{DNSDIST_TRAP_OBJECTS_OID, 1, 0};
+static const OIDTrapObject socketProtocolOID{DNSDIST_TRAP_OBJECTS_OID, 2, 0};
+static const OIDTrapObject fromAddressOID{DNSDIST_TRAP_OBJECTS_OID, 3, 0};
+static const OIDTrapObject toAddressOID{DNSDIST_TRAP_OBJECTS_OID, 4, 0};
+static const OIDTrapObject queryTypeOID{DNSDIST_TRAP_OBJECTS_OID, 5, 0};
+static const OIDTrapObject querySizeOID{DNSDIST_TRAP_OBJECTS_OID, 6, 0};
+static const OIDTrapObject queryIDOID{DNSDIST_TRAP_OBJECTS_OID, 7, 0};
+static const OIDTrapObject qNameOID{DNSDIST_TRAP_OBJECTS_OID, 8, 0};
+static const OIDTrapObject qClassOID{DNSDIST_TRAP_OBJECTS_OID, 9, 0};
+static const OIDTrapObject qTypeOID{DNSDIST_TRAP_OBJECTS_OID, 10, 0};
+static const OIDTrapObject trapReasonOID{DNSDIST_TRAP_OBJECTS_OID, 11, 0};
+
+static const OIDTrap backendStatusChangeTrapOID{DNSDIST_TRAPS_OID, 1};
+static const OIDTrap actionTrapOID{DNSDIST_TRAPS_OID, 2};
+static const OIDTrap customTrapOID{DNSDIST_TRAPS_OID, 3};
+
+static servers_t s_servers;
+static size_t s_currentServerIdx = 0;
+
+static netsnmp_variable_list* backendStatTable_get_next_data_point(void** loop_context,
+ void** my_data_context,
+ netsnmp_variable_list* put_index_data,
+ netsnmp_iterator_info* mydata)
+{
+ if (s_currentServerIdx >= s_servers.size()) {
+ return nullptr;
+ }
+
+ *my_data_context = (void*)(s_servers[s_currentServerIdx]).get();
+ snmp_set_var_typed_integer(put_index_data, ASN_UNSIGNED, static_cast<long>(s_currentServerIdx));
+ s_currentServerIdx++;
+
+ return put_index_data;
+}
+
+static netsnmp_variable_list* backendStatTable_get_first_data_point(void** loop_context,
+ void** data_context,
+ netsnmp_variable_list* put_index_data,
+ netsnmp_iterator_info* data)
+{
+ s_currentServerIdx = 0;
+
+ /* get a copy of the shared_ptrs so they are not
+ destroyed while we process the request */
+ auto dstates = g_dstates.getLocal();
+ s_servers.clear();
+ s_servers.reserve(dstates->size());
+ for (const auto& server : *dstates) {
+ s_servers.push_back(server);
+ }
+
+ return backendStatTable_get_next_data_point(loop_context,
+ data_context,
+ put_index_data,
+ data);
+}
+
+static int backendStatTable_handler(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ netsnmp_request_info* request{nullptr};
+
+ switch (reqinfo->mode) {
+ case MODE_GET:
+ for (request = requests; request != nullptr; request = request->next) {
+ netsnmp_table_request_info* table_info = netsnmp_extract_table_info(request);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+ const auto* server = reinterpret_cast<const DownstreamState*>(netsnmp_extract_iterator_context(request));
+ if (server == nullptr) {
+ continue;
+ }
+
+ switch (table_info->colnum) {
+ case COLUMN_BACKENDNAME:
+ snmp_set_var_typed_value(request->requestvb,
+ ASN_OCTET_STR,
+ server->getName().c_str(),
+ server->getName().size());
+ break;
+ case COLUMN_BACKENDLATENCY:
+ DNSDistSNMPAgent::setCounter64Value(request,
+ static_cast<uint64_t>(server->getRelevantLatencyUsec() / 1000.0));
+ break;
+ case COLUMN_BACKENDWEIGHT:
+ DNSDistSNMPAgent::setCounter64Value(request,
+ server->d_config.d_weight);
+ break;
+ case COLUMN_BACKENDOUTSTANDING:
+ DNSDistSNMPAgent::setCounter64Value(request,
+ server->outstanding.load());
+ break;
+ case COLUMN_BACKENDQPSLIMIT:
+ DNSDistSNMPAgent::setCounter64Value(request,
+ server->qps.getRate());
+ break;
+ case COLUMN_BACKENDREUSED:
+ DNSDistSNMPAgent::setCounter64Value(request, server->reuseds.load());
+ break;
+ case COLUMN_BACKENDSTATE: {
+ std::string state(server->getStatus());
+ snmp_set_var_typed_value(request->requestvb,
+ ASN_OCTET_STR,
+ state.c_str(),
+ state.size());
+ break;
+ }
+ case COLUMN_BACKENDADDRESS: {
+ std::string addr(server->d_config.remote.toStringWithPort());
+ snmp_set_var_typed_value(request->requestvb,
+ ASN_OCTET_STR,
+ addr.c_str(),
+ addr.size());
+ break;
+ }
+ case COLUMN_BACKENDPOOLS: {
+ std::string pools;
+ for (const auto& pool : server->d_config.pools) {
+ if (!pools.empty()) {
+ pools += " ";
+ }
+ pools += pool;
+ }
+ snmp_set_var_typed_value(request->requestvb,
+ ASN_OCTET_STR,
+ pools.c_str(),
+ pools.size());
+ break;
+ }
+ case COLUMN_BACKENDQPS:
+ DNSDistSNMPAgent::setCounter64Value(request, static_cast<uint64_t>(server->queryLoad.load()));
+ break;
+ case COLUMN_BACKENDQUERIES:
+ DNSDistSNMPAgent::setCounter64Value(request, server->queries.load());
+ break;
+ case COLUMN_BACKENDORDER:
+ DNSDistSNMPAgent::setCounter64Value(request, server->d_config.order);
+ break;
+ default:
+ netsnmp_set_request_error(reqinfo,
+ request,
+ SNMP_NOSUCHOBJECT);
+ break;
+ }
+ }
+ break;
+ }
+ return SNMP_ERR_NOERROR;
+}
+#endif /* HAVE_NET_SNMP */
+
+bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const DownstreamState& dss)
+{
+#ifdef HAVE_NET_SNMP
+ const string backendAddress = dss.d_config.remote.toStringWithPort();
+ const string backendStatus = dss.getStatus();
+ netsnmp_variable_list* varList = nullptr;
+
+ snmp_varlist_add_variable(&varList,
+ snmpTrapOID.data(),
+ snmpTrapOID.size(),
+ ASN_OBJECT_ID,
+ backendStatusChangeTrapOID.data(),
+ backendStatusChangeTrapOID.size() * sizeof(oid));
+
+ snmp_varlist_add_variable(&varList,
+ backendNameOID.data(),
+ backendNameOID.size(),
+ ASN_OCTET_STR,
+ dss.getName().c_str(),
+ dss.getName().size());
+
+ snmp_varlist_add_variable(&varList,
+ backendAddressOID.data(),
+ backendAddressOID.size(),
+ ASN_OCTET_STR,
+ backendAddress.c_str(),
+ backendAddress.size());
+
+ snmp_varlist_add_variable(&varList,
+ backendStateOID.data(),
+ backendStateOID.size(),
+ ASN_OCTET_STR,
+ backendStatus.c_str(),
+ backendStatus.size());
+
+ return sendTrap(d_sender, varList);
+#else
+ return true;
+#endif /* HAVE_NET_SNMP */
+}
+
+bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason)
+{
+#ifdef HAVE_NET_SNMP
+ netsnmp_variable_list* varList = nullptr;
+
+ snmp_varlist_add_variable(&varList,
+ snmpTrapOID.data(),
+ snmpTrapOID.size(),
+ ASN_OBJECT_ID,
+ customTrapOID.data(),
+ customTrapOID.size() * sizeof(oid));
+
+ snmp_varlist_add_variable(&varList,
+ trapReasonOID.data(),
+ trapReasonOID.size(),
+ ASN_OCTET_STR,
+ reason.c_str(),
+ reason.size());
+
+ return sendTrap(d_sender, varList);
+#else
+ return true;
+#endif /* HAVE_NET_SNMP */
+}
+
+bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dnsQuestion, const std::string& reason)
+{
+#ifdef HAVE_NET_SNMP
+ std::string local = dnsQuestion.ids.origDest.toString();
+ std::string remote = dnsQuestion.ids.origRemote.toString();
+ std::string qname = dnsQuestion.ids.qname.toStringNoDot();
+ const uint32_t socketFamily = dnsQuestion.ids.origRemote.isIPv4() ? 1 : 2;
+ const uint32_t socketProtocol = dnsQuestion.overTCP() ? 2 : 1;
+ const uint32_t queryType = dnsQuestion.getHeader()->qr ? 2 : 1;
+ const auto querySize = static_cast<uint32_t>(dnsQuestion.getData().size());
+ const auto queryID = static_cast<uint32_t>(ntohs(dnsQuestion.getHeader()->id));
+ const auto qType = static_cast<uint32_t>(dnsQuestion.ids.qtype);
+ const auto qClass = static_cast<uint32_t>(dnsQuestion.ids.qclass);
+
+ netsnmp_variable_list* varList = nullptr;
+
+ snmp_varlist_add_variable(&varList,
+ snmpTrapOID.data(),
+ snmpTrapOID.size(),
+ ASN_OBJECT_ID,
+ actionTrapOID.data(),
+ actionTrapOID.size() * sizeof(oid));
+
+ snmp_varlist_add_variable(&varList,
+ socketFamilyOID.data(),
+ socketFamilyOID.size(),
+ ASN_INTEGER,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+ reinterpret_cast<const u_char*>(&socketFamily),
+ sizeof(socketFamily));
+
+ snmp_varlist_add_variable(&varList,
+ socketProtocolOID.data(),
+ socketProtocolOID.size(),
+ ASN_INTEGER,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+ reinterpret_cast<const u_char*>(&socketProtocol),
+ sizeof(socketProtocol));
+
+ snmp_varlist_add_variable(&varList,
+ fromAddressOID.data(),
+ fromAddressOID.size(),
+ ASN_OCTET_STR,
+ remote.c_str(),
+ remote.size());
+
+ snmp_varlist_add_variable(&varList,
+ toAddressOID.data(),
+ toAddressOID.size(),
+ ASN_OCTET_STR,
+ local.c_str(),
+ local.size());
+
+ snmp_varlist_add_variable(&varList,
+ queryTypeOID.data(),
+ queryTypeOID.size(),
+ ASN_INTEGER,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+ reinterpret_cast<const u_char*>(&queryType),
+ sizeof(queryType));
+
+ snmp_varlist_add_variable(&varList,
+ querySizeOID.data(),
+ querySizeOID.size(),
+ ASN_UNSIGNED,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+ reinterpret_cast<const u_char*>(&querySize),
+ sizeof(querySize));
+
+ snmp_varlist_add_variable(&varList,
+ queryIDOID.data(),
+ queryIDOID.size(),
+ ASN_UNSIGNED,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+ reinterpret_cast<const u_char*>(&queryID),
+ sizeof(queryID));
+
+ snmp_varlist_add_variable(&varList,
+ qNameOID.data(),
+ qNameOID.size(),
+ ASN_OCTET_STR,
+ qname.c_str(),
+ qname.size());
+
+ snmp_varlist_add_variable(&varList,
+ qClassOID.data(),
+ qClassOID.size(),
+ ASN_UNSIGNED,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+ reinterpret_cast<const u_char*>(&qClass),
+ sizeof(qClass));
+
+ snmp_varlist_add_variable(&varList,
+ qTypeOID.data(),
+ qTypeOID.size(),
+ ASN_UNSIGNED,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+ reinterpret_cast<const u_char*>(&qType),
+ sizeof(qType));
+
+ snmp_varlist_add_variable(&varList,
+ trapReasonOID.data(),
+ trapReasonOID.size(),
+ ASN_OCTET_STR,
+ reason.c_str(),
+ reason.size());
+
+ return sendTrap(d_sender, varList);
+#else
+ return true;
+#endif /* HAVE_NET_SNMP */
+}
+
+DNSDistSNMPAgent::DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket) :
+ SNMPAgent(name, daemonSocket)
+{
+#ifdef HAVE_NET_SNMP
+
+ registerCounter64Stat("queries", queriesOID, &dnsdist::metrics::g_stats.queries);
+ registerCounter64Stat("responses", responsesOID, &dnsdist::metrics::g_stats.responses);
+ registerCounter64Stat("servfailResponses", servfailResponsesOID, &dnsdist::metrics::g_stats.servfailResponses);
+ registerCounter64Stat("aclDrops", aclDropsOID, &dnsdist::metrics::g_stats.aclDrops);
+ registerCounter64Stat("ruleDrop", ruleDropOID, &dnsdist::metrics::g_stats.ruleDrop);
+ registerCounter64Stat("ruleNXDomain", ruleNXDomainOID, &dnsdist::metrics::g_stats.ruleNXDomain);
+ registerCounter64Stat("ruleRefused", ruleRefusedOID, &dnsdist::metrics::g_stats.ruleRefused);
+ registerCounter64Stat("ruleServFail", ruleServFailOID, &dnsdist::metrics::g_stats.ruleServFail);
+ registerCounter64Stat("ruleTruncated", ruleTruncatedOID, &dnsdist::metrics::g_stats.ruleTruncated);
+ registerCounter64Stat("selfAnswered", selfAnsweredOID, &dnsdist::metrics::g_stats.selfAnswered);
+ registerCounter64Stat("downstreamTimeouts", downstreamTimeoutsOID, &dnsdist::metrics::g_stats.downstreamTimeouts);
+ registerCounter64Stat("downstreamSendErrors", downstreamSendErrorsOID, &dnsdist::metrics::g_stats.downstreamSendErrors);
+ registerCounter64Stat("truncFail", truncFailOID, &dnsdist::metrics::g_stats.truncFail);
+ registerCounter64Stat("noPolicy", noPolicyOID, &dnsdist::metrics::g_stats.noPolicy);
+ registerCounter64Stat("latency0_1", latency0_1OID, &dnsdist::metrics::g_stats.latency0_1);
+ registerCounter64Stat("latency1_10", latency1_10OID, &dnsdist::metrics::g_stats.latency1_10);
+ registerCounter64Stat("latency10_50", latency10_50OID, &dnsdist::metrics::g_stats.latency10_50);
+ registerCounter64Stat("latency50_100", latency50_100OID, &dnsdist::metrics::g_stats.latency50_100);
+ registerCounter64Stat("latency100_1000", latency100_1000OID, &dnsdist::metrics::g_stats.latency100_1000);
+ registerCounter64Stat("latencySlow", latencySlowOID, &dnsdist::metrics::g_stats.latencySlow);
+ registerCounter64Stat("nonCompliantQueries", nonCompliantQueriesOID, &dnsdist::metrics::g_stats.nonCompliantQueries);
+ registerCounter64Stat("nonCompliantResponses", nonCompliantResponsesOID, &dnsdist::metrics::g_stats.nonCompliantResponses);
+ registerCounter64Stat("rdQueries", rdQueriesOID, &dnsdist::metrics::g_stats.rdQueries);
+ registerCounter64Stat("emptyQueries", emptyQueriesOID, &dnsdist::metrics::g_stats.emptyQueries);
+ registerCounter64Stat("cacheHits", cacheHitsOID, &dnsdist::metrics::g_stats.cacheHits);
+ registerCounter64Stat("cacheMisses", cacheMissesOID, &dnsdist::metrics::g_stats.cacheMisses);
+ registerCounter64Stat("dynBlocked", dynBlockedOID, &dnsdist::metrics::g_stats.dynBlocked);
+ registerFloatStat("latencyAvg100", latencyAvg100OID, &dnsdist::metrics::g_stats.latencyAvg100);
+ registerFloatStat("latencyAvg1000", latencyAvg1000OID, &dnsdist::metrics::g_stats.latencyAvg1000);
+ registerFloatStat("latencyAvg10000", latencyAvg10000OID, &dnsdist::metrics::g_stats.latencyAvg10000);
+ registerFloatStat("latencyAvg1000000", latencyAvg1000000OID, &dnsdist::metrics::g_stats.latencyAvg1000000);
+ registerGauge64Stat("uptime", uptimeOID, &uptimeOfProcess);
+ registerGauge64Stat("specialMemoryUsage", specialMemoryUsageOID, &getSpecialMemoryUsage);
+ registerGauge64Stat("cpuUserMSec", cpuUserMSecOID, &getCPUTimeUser);
+ registerGauge64Stat("cpuSysMSec", cpuSysMSecOID, &getCPUTimeSystem);
+ registerGauge64Stat("fdUsage", fdUsageOID, &getOpenFileDescriptors);
+ registerGauge64Stat("dynBlockedNMGSize", dynBlockedNMGSizeOID, [](const std::string&) { return g_dynblockNMG.getLocal()->size(); });
+ registerGauge64Stat("securityStatus", securityStatusOID, [](const std::string&) { return dnsdist::metrics::g_stats.securityStatus.load(); });
+ registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, &getRealMemoryUsage);
+
+ // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): net-snmp API
+ auto* table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
+ netsnmp_table_helper_add_indexes(table_info,
+ ASN_GAUGE, /* index: backendId */
+ 0);
+ table_info->min_column = COLUMN_BACKENDNAME;
+ table_info->max_column = COLUMN_BACKENDORDER;
+ // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): net-snmp API
+ auto* iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
+ iinfo->get_first_data_point = backendStatTable_get_first_data_point;
+ iinfo->get_next_data_point = backendStatTable_get_next_data_point;
+ iinfo->table_reginfo = table_info;
+
+ netsnmp_register_table_iterator(netsnmp_create_handler_registration("backendStatTable",
+ backendStatTable_handler,
+ backendStatTableOID.data(),
+ backendStatTableOID.size(),
+ HANDLER_CAN_RONLY),
+ iinfo);
+
+#endif /* HAVE_NET_SNMP */
+}
+++ /dev/null
-../dnsdist-snmp.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "snmp-agent.hh"
+
+class DNSDistSNMPAgent;
+
+#include "dnsdist.hh"
+
+class DNSDistSNMPAgent : public SNMPAgent
+{
+public:
+ DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket);
+ bool sendBackendStatusChangeTrap(const DownstreamState&);
+ bool sendCustomTrap(const std::string& reason);
+ bool sendDNSTrap(const DNSQuestion&, const std::string& reason = "");
+};
return true;
}
catch (const std::runtime_error& e) {
- vinfolog("Connection to downstream server %s failed: %s", d_ds->getName(), e.what());
+ vinfolog("Connection to downstream server %s failed: %s", d_ds->getNameWithAddr(), e.what());
d_downstreamFailures++;
if (d_downstreamFailures >= d_ds->d_config.d_retries) {
throw;
static bool getSerialFromIXFRQuery(TCPQuery& query)
{
try {
- size_t proxyPayloadSize = query.d_proxyProtocolPayloadAdded ? query.d_proxyProtocolPayloadAddedSize : 0;
+ size_t proxyPayloadSize = query.d_proxyProtocolPayloadAdded ? query.d_idstate.d_proxyProtocolPayloadSize : 0;
if (query.d_buffer.size() <= (proxyPayloadSize + sizeof(uint16_t))) {
return false;
}
if (!unknownContent) {
return false;
}
- auto raw = unknownContent->getRawContent();
+ const auto& raw = unknownContent->getRawContent();
query.d_ixfrQuerySerial = getSerialFromRawSOAContent(raw);
return true;
}
if (query.d_proxyProtocolPayload.size() > 0 && !query.d_proxyProtocolPayloadAdded) {
query.d_buffer.insert(query.d_buffer.begin(), query.d_proxyProtocolPayload.begin(), query.d_proxyProtocolPayload.end());
query.d_proxyProtocolPayloadAdded = true;
- query.d_proxyProtocolPayloadAddedSize = query.d_proxyProtocolPayload.size();
+ query.d_idstate.d_proxyProtocolPayloadSize = query.d_proxyProtocolPayload.size();
}
}
else if (connectionState == ConnectionState::proxySent) {
if (query.d_proxyProtocolPayloadAdded) {
- if (query.d_buffer.size() < query.d_proxyProtocolPayloadAddedSize) {
+ if (query.d_buffer.size() < query.d_idstate.d_proxyProtocolPayloadSize) {
throw std::runtime_error("Trying to remove a proxy protocol payload of size " + std::to_string(query.d_proxyProtocolPayload.size()) + " from a buffer of size " + std::to_string(query.d_buffer.size()));
}
- query.d_buffer.erase(query.d_buffer.begin(), query.d_buffer.begin() + query.d_proxyProtocolPayloadAddedSize);
+ // NOLINTNEXTLINE(*-narrowing-conversions): the size of the payload is limited to 2^16-1
+ query.d_buffer.erase(query.d_buffer.begin(), query.d_buffer.begin() + static_cast<ssize_t>(query.d_idstate.d_proxyProtocolPayloadSize));
query.d_proxyProtocolPayloadAdded = false;
- query.d_proxyProtocolPayloadAddedSize = 0;
+ query.d_idstate.d_proxyProtocolPayloadSize = 0;
}
}
if (query.d_idstate.qclass == QClass::IN && query.d_idstate.qtype == QType::IXFR) {
getSerialFromIXFRQuery(query);
}
- editPayloadID(query.d_buffer, id, query.d_proxyProtocolPayloadAdded ? query.d_proxyProtocolPayloadAddedSize : 0, true);
+ editPayloadID(query.d_buffer, id, query.d_proxyProtocolPayloadAdded ? query.d_idstate.d_proxyProtocolPayloadSize : 0, true);
}
IOState TCPConnectionToBackend::queueNextQuery(std::shared_ptr<TCPConnectionToBackend>& conn)
IOState TCPConnectionToBackend::sendQuery(std::shared_ptr<TCPConnectionToBackend>& conn, const struct timeval& now)
{
- DEBUGLOG("sending query to backend "<<conn->getDS()->getName()<<" over FD "<<conn->d_handler->getDescriptor());
+ DEBUGLOG("sending query to backend "<<conn->getDS()->getNameWithAddr()<<" over FD "<<conn->d_handler->getDescriptor());
IOState state = conn->d_handler->tryWrite(conn->d_currentQuery.d_query.d_buffer, conn->d_currentPos, conn->d_currentQuery.d_query.d_buffer.size());
iostate = conn->handleResponse(conn, now);
}
catch (const std::exception& e) {
- vinfolog("Got an exception while handling TCP response from %s (client is %s): %s", conn->d_ds ? conn->d_ds->getName() : "unknown", conn->d_currentQuery.d_query.d_idstate.origRemote.toStringWithPort(), e.what());
+ vinfolog("Got an exception while handling TCP response from %s (client is %s): %s", conn->d_ds ? conn->d_ds->getNameWithAddr() : "unknown", conn->d_currentQuery.d_query.d_idstate.origRemote.toStringWithPort(), e.what());
ioGuard.release();
conn->release();
return;
/* this one can't be restarted, sorry */
DEBUGLOG("A XFR for which a response has already been sent cannot be restarted");
try {
- pending.second.d_sender->notifyIOError(std::move(pending.second.d_query.d_idstate), now);
+ TCPResponse response(std::move(pending.second.d_query));
+ pending.second.d_sender->notifyIOError(now, std::move(response));
}
catch (const std::exception& e) {
vinfolog("Got an exception while notifying: %s", e.what());
if (write) {
if (isFresh() && d_queries == 0) {
++d_ds->tcpConnectTimeouts;
- vinfolog("Timeout while connecting to TCP backend %s", d_ds->getName());
+ vinfolog("Timeout while connecting to TCP backend %s", d_ds->getNameWithAddr());
}
else {
++d_ds->tcpWriteTimeouts;
- vinfolog("Timeout while writing to TCP backend %s", d_ds->getName());
+ vinfolog("Timeout while writing to TCP backend %s", d_ds->getNameWithAddr());
}
}
else {
++d_ds->tcpReadTimeouts;
- vinfolog("Timeout while reading from TCP backend %s", d_ds->getName());
+ vinfolog("Timeout while reading from TCP backend %s", d_ds->getNameWithAddr());
}
try {
increaseCounters(d_currentQuery.d_query.d_idstate.cs);
auto sender = d_currentQuery.d_sender;
if (sender->active()) {
- sender->notifyIOError(std::move(d_currentQuery.d_query.d_idstate), now);
+ TCPResponse response(std::move(d_currentQuery.d_query));
+ sender->notifyIOError(now, std::move(response));
}
}
increaseCounters(query.d_query.d_idstate.cs);
auto sender = query.d_sender;
if (sender->active()) {
- sender->notifyIOError(std::move(query.d_query.d_idstate), now);
+ TCPResponse response(std::move(query.d_query));
+ sender->notifyIOError(now, std::move(response));
}
}
increaseCounters(response.second.d_query.d_idstate.cs);
auto sender = response.second.d_sender;
if (sender->active()) {
- sender->notifyIOError(std::move(response.second.d_query.d_idstate), now);
+ TCPResponse tresp(std::move(response.second.d_query));
+ sender->notifyIOError(now, std::move(tresp));
}
}
}
d_state = State::idle;
t_downstreamTCPConnectionsManager.moveToIdle(conn);
}
+ else if (!d_pendingResponses.empty()) {
+ d_currentPos = 0;
+ d_state = State::waitingForResponseFromBackend;
+ }
+ // be very careful that handleResponse() might trigger new queries being assigned to us,
+ // which may reset our d_currentPos, d_state and/or d_responseBuffer, so we cannot assume
+ // anything without checking first
auto shared = conn;
if (sender->active()) {
DEBUGLOG("passing response to client connection for "<<ids.qname);
// make sure that we still exist after calling handleResponse()
- sender->handleResponse(now, TCPResponse(std::move(d_responseBuffer), std::move(ids), conn, conn->d_ds));
+ TCPResponse response(std::move(d_responseBuffer), std::move(ids), conn, conn->d_ds);
+ sender->handleResponse(now, std::move(response));
}
if (!d_pendingQueries.empty()) {
}
else if (!d_pendingResponses.empty()) {
DEBUGLOG("still have some responses to read");
- d_state = State::waitingForResponseFromBackend;
- d_currentPos = 0;
- d_responseBuffer.resize(sizeof(uint16_t));
return IOState::NeedRead;
}
else {
if (!unknownContent) {
continue;
}
- auto raw = unknownContent->getRawContent();
+ const auto& raw = unknownContent->getRawContent();
auto serial = getSerialFromRawSOAContent(raw);
- if (query.d_xfrMasterSerial == 0) {
+ if (query.d_xfrPrimarySerial == 0) {
// store the first SOA in our client's connection metadata
- query.d_xfrMasterSerial = serial;
- if (query.d_idstate.qtype == QType::IXFR && (query.d_xfrMasterSerial == query.d_ixfrQuerySerial || rfc1982LessThan(query.d_xfrMasterSerial, query.d_ixfrQuerySerial))) {
- /* This is the first message with a master SOA:
+ query.d_xfrPrimarySerial = serial;
+ if (query.d_idstate.qtype == QType::IXFR && (query.d_xfrPrimarySerial == query.d_ixfrQuerySerial || rfc1982LessThan(query.d_xfrPrimarySerial, query.d_ixfrQuerySerial))) {
+ /* This is the first message with a primary SOA:
RFC 1995 Section 2:
If an IXFR query with the same or newer version number
than that of the server is received, it is replied to
}
++query.d_xfrSerialCount;
- if (serial == query.d_xfrMasterSerial) {
- ++query.d_xfrMasterSerialCount;
- // figure out if it's end when receiving master's SOA again
+ if (serial == query.d_xfrPrimarySerial) {
+ ++query.d_xfrPrimarySerialCount;
+ // figure out if it's end when receiving primary's SOA again
if (query.d_xfrSerialCount == 2) {
// if there are only two SOA records marks a finished AXFR
done = true;
break;
}
- if (query.d_xfrMasterSerialCount == 3) {
- // receiving master's SOA 3 times marks a finished IXFR
+ if (query.d_xfrPrimarySerialCount == 3) {
+ // receiving primary's SOA 3 times marks a finished IXFR
done = true;
break;
}
class TCPConnectionToBackend : public ConnectionToBackend
{
public:
- TCPConnectionToBackend(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDMultiplexer>& mplexer, const struct timeval& now, std::string&& /* proxyProtocolPayload*, unused but there to match the HTTP2 connections, so we can use the same templated connections manager class */): ConnectionToBackend(ds, mplexer, now), d_responseBuffer(s_maxPacketCacheEntrySize)
+ TCPConnectionToBackend(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDMultiplexer>& mplexer, const struct timeval& now, std::string&& /* proxyProtocolPayload*, unused but there to match the HTTP2 connections, so we can use the same templated connections manager class */): ConnectionToBackend(ds, mplexer, now), d_responseBuffer(512)
{
}
#include "dolog.hh"
#include "dnsdist-tcp.hh"
+#include "dnsdist-tcp-downstream.hh"
+
+struct TCPCrossProtocolResponse;
class TCPClientThreadData
{
public:
TCPClientThreadData():
- localRespRuleActions(g_respruleactions.getLocal()), localCacheInsertedRespRuleActions(g_cacheInsertedRespRuleActions.getLocal()), mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
+ localRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal()), localCacheInsertedRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal()), mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
{
}
LocalHolders holders;
- LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions;
- LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions;
+ LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions;
+ LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions;
std::unique_ptr<FDMultiplexer> mplexer{nullptr};
- int crossProtocolResponsesPipe{-1};
+ pdns::channel::Receiver<ConnectionInfo> queryReceiver;
+ pdns::channel::Receiver<CrossProtocolQuery> crossProtocolQueryReceiver;
+ pdns::channel::Receiver<TCPCrossProtocolResponse> crossProtocolResponseReceiver;
+ pdns::channel::Sender<TCPCrossProtocolResponse> crossProtocolResponseSender;
};
class IncomingTCPConnectionState : public TCPQuerySender, public std::enable_shared_from_this<IncomingTCPConnectionState>
{
public:
- IncomingTCPConnectionState(ConnectionInfo&& ci, TCPClientThreadData& threadData, const struct timeval& now): d_buffer(s_maxPacketCacheEntrySize), d_ci(std::move(ci)), d_handler(d_ci.fd, timeval{g_tcpRecvTimeout,0}, d_ci.cs->tlsFrontend ? d_ci.cs->tlsFrontend->getContext() : nullptr, now.tv_sec), d_connectionStartTime(now), d_ioState(make_unique<IOStateHandler>(*threadData.mplexer, d_ci.fd)), d_threadData(threadData), d_creatorThreadID(std::this_thread::get_id())
+ enum class QueryProcessingResult : uint8_t { Forwarded, TooSmall, InvalidHeaders, Dropped, SelfAnswered, NoBackend, Asynchronous };
+ enum class ProxyProtocolResult : uint8_t { Reading, Done, Error };
+
+ IncomingTCPConnectionState(ConnectionInfo&& ci, TCPClientThreadData& threadData, const struct timeval& now): d_buffer(sizeof(uint16_t)), d_ci(std::move(ci)), d_handler(d_ci.fd, timeval{g_tcpRecvTimeout,0}, d_ci.cs->tlsFrontend ? d_ci.cs->tlsFrontend->getContext() : (d_ci.cs->dohFrontend ? d_ci.cs->dohFrontend->d_tlsContext.getContext() : nullptr), now.tv_sec), d_connectionStartTime(now), d_ioState(make_unique<IOStateHandler>(*threadData.mplexer, d_ci.fd)), d_threadData(threadData), d_creatorThreadID(std::this_thread::get_id())
{
d_origDest.reset();
d_origDest.sin4.sin_family = d_ci.remote.sin4.sin_family;
IncomingTCPConnectionState(const IncomingTCPConnectionState& rhs) = delete;
IncomingTCPConnectionState& operator=(const IncomingTCPConnectionState& rhs) = delete;
- ~IncomingTCPConnectionState();
+ virtual ~IncomingTCPConnectionState();
void resetForNewQuery();
return false;
}
- std::shared_ptr<TCPConnectionToBackend> getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs);
- std::shared_ptr<TCPConnectionToBackend> getDownstreamConnection(std::shared_ptr<DownstreamState>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now);
+ std::shared_ptr<TCPConnectionToBackend> getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs);
+ std::shared_ptr<TCPConnectionToBackend> getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now);
void registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn);
static size_t clearAllDownstreamConnections();
- static void handleIO(std::shared_ptr<IncomingTCPConnectionState>& conn, const struct timeval& now);
- static void handleIOCallback(int fd, FDMultiplexer::funcparam_t& param);
- static void handleAsyncReady(int fd, FDMultiplexer::funcparam_t& param);
+ static void handleIOCallback(int desc, FDMultiplexer::funcparam_t& param);
+ static void handleAsyncReady(int desc, FDMultiplexer::funcparam_t& param);
static void updateIO(std::shared_ptr<IncomingTCPConnectionState>& state, IOState newState, const struct timeval& now);
- static IOState sendResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response);
- static void queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response);
-static void handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bool write);
+ static void queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response, bool fromBackend);
+ static void handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bool write);
- /* we take a copy of a shared pointer, not a reference, because the initial shared pointer might be released during the handling of the response */
- void handleResponse(const struct timeval& now, TCPResponse&& response) override;
+ virtual void handleIO();
+
+ QueryProcessingResult handleQuery(PacketBuffer&& query, const struct timeval& now, std::optional<int32_t> streamID);
+ virtual void handleResponse(const struct timeval& now, TCPResponse&& response) override;
+ virtual void notifyIOError(const struct timeval& now, TCPResponse&& response) override;
void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override;
- void notifyIOError(InternalQueryState&& query, const struct timeval& now) override;
+ virtual IOState sendResponse(const struct timeval& now, TCPResponse&& response);
+ void handleResponseSent(TCPResponse& currentResponse);
+ virtual IOState handleHandshake(const struct timeval& now);
+ void handleHandshakeDone(const struct timeval& now);
+ ProxyProtocolResult handleProxyProtocolPayload();
void handleCrossProtocolResponse(const struct timeval& now, TCPResponse&& response);
void terminateClientConnection();
- void queueQuery(TCPQuery&& query);
bool canAcceptNewQueries(const struct timeval& now);
{
return d_ioState != nullptr;
}
+ bool isProxyPayloadOutsideTLS() const
+ {
+ if (!d_ci.cs->hasTLS()) {
+ return false;
+ }
+ return d_ci.cs->getTLSFrontend().d_proxyProtocolOutsideTLS;
+ }
+
+ virtual bool forwardViaUDPFirst() const
+ {
+ return false;
+ }
+ virtual std::unique_ptr<DOHUnitInterface> getDOHUnit(uint32_t streamID)
+ {
+ throw std::runtime_error("Getting a DOHUnit state from a generic TCP/DoT connection is not supported");
+ }
+ virtual void restoreDOHUnit(std::unique_ptr<DOHUnitInterface>&&)
+ {
+ throw std::runtime_error("Restoring a DOHUnit state to a generic TCP/DoT connection is not supported");
+ }
+
+ std::unique_ptr<CrossProtocolQuery> getCrossProtocolQuery(PacketBuffer&& query, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& backend);
std::string toString() const
{
return o.str();
}
- enum class State : uint8_t { doingHandshake, readingProxyProtocolHeader, waitingForQuery, readingQuerySize, readingQuery, sendingResponse, idle /* in case of XFR, we stop processing queries */ };
+ dnsdist::Protocol getProtocol() const;
+ IOState handleIncomingQueryReceived(const struct timeval& now);
+ void handleExceptionDuringIO(const std::exception& exp);
+ bool readIncomingQuery(const timeval& now, IOState& iostate);
+
+ enum class State : uint8_t { starting, doingHandshake, readingProxyProtocolHeader, waitingForQuery, readingQuerySize, readingQuery, sendingResponse, idle /* in case of XFR, we stop processing queries */ };
TCPResponse d_currentResponse;
std::map<std::shared_ptr<DownstreamState>, std::deque<std::shared_ptr<TCPConnectionToBackend>>> d_ownedConnectionsToBackend;
size_t d_currentQueriesCount{0};
std::thread::id d_creatorThreadID;
uint16_t d_querySize{0};
- State d_state{State::doingHandshake};
+ State d_state{State::starting};
bool d_isXFR{false};
bool d_proxyProtocolPayloadHasTLV{false};
bool d_lastIOBlocked{false};
+++ /dev/null
-../dnsdist-tcp.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <thread>
+#include <netinet/tcp.h>
+#include <queue>
+
+#include "dnsdist.hh"
+#include "dnsdist-concurrent-connections.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-nghttp2-in.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-tcp-downstream.hh"
+#include "dnsdist-downstream-connection.hh"
+#include "dnsdist-tcp-upstream.hh"
+#include "dnsdist-xpf.hh"
+#include "dnsparser.hh"
+#include "dolog.hh"
+#include "gettime.hh"
+#include "lock.hh"
+#include "sstuff.hh"
+#include "tcpiohandler.hh"
+#include "tcpiohandler-mplexer.hh"
+#include "threadname.hh"
+
+/* TCP: the grand design.
+ We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops.
+ An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially
+ we will not go there.
+
+ In a sense there is a strong symmetry between UDP and TCP, once a connection to a downstream has been setup.
+ This symmetry is broken because of head-of-line blocking within TCP though, necessitating additional connections
+ to guarantee performance.
+
+ So the idea is to have a 'pool' of available downstream connections, and forward messages to/from them and never queue.
+ So whenever an answer comes in, we know where it needs to go.
+
+ Let's start naively.
+*/
+
+size_t g_maxTCPQueriesPerConn{0};
+size_t g_maxTCPConnectionDuration{0};
+
+#ifdef __linux__
+// On Linux this gives us 128k pending queries (default is 8192 queries),
+// which should be enough to deal with huge spikes
+size_t g_tcpInternalPipeBufferSize{1048576U};
+uint64_t g_maxTCPQueuedConnections{10000};
+#else
+size_t g_tcpInternalPipeBufferSize{0};
+uint64_t g_maxTCPQueuedConnections{1000};
+#endif
+
+int g_tcpRecvTimeout{2};
+int g_tcpSendTimeout{2};
+std::atomic<uint64_t> g_tcpStatesDumpRequested{0};
+
+LockGuarded<std::map<ComboAddress, size_t, ComboAddress::addressOnlyLessThan>> dnsdist::IncomingConcurrentTCPConnectionsManager::s_tcpClientsConcurrentConnectionsCount;
+size_t dnsdist::IncomingConcurrentTCPConnectionsManager::s_maxTCPConnectionsPerClient = 0;
+
+IncomingTCPConnectionState::~IncomingTCPConnectionState()
+{
+ dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(d_ci.remote);
+
+ if (d_ci.cs != nullptr) {
+ timeval now{};
+ gettimeofday(&now, nullptr);
+
+ auto diff = now - d_connectionStartTime;
+ d_ci.cs->updateTCPMetrics(d_queriesCount, diff.tv_sec * 1000 + diff.tv_usec / 1000);
+ }
+
+ // would have been done when the object is destroyed anyway,
+ // but that way we make sure it's done before the ConnectionInfo is destroyed,
+ // closing the descriptor, instead of relying on the declaration order of the objects in the class
+ d_handler.close();
+}
+
+dnsdist::Protocol IncomingTCPConnectionState::getProtocol() const
+{
+ if (d_ci.cs->dohFrontend) {
+ return dnsdist::Protocol::DoH;
+ }
+ if (d_handler.isTLS()) {
+ return dnsdist::Protocol::DoT;
+ }
+ return dnsdist::Protocol::DoTCP;
+}
+
+size_t IncomingTCPConnectionState::clearAllDownstreamConnections()
+{
+ return t_downstreamTCPConnectionsManager.clear();
+}
+
+std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now)
+{
+ auto downstream = getOwnedDownstreamConnection(backend, tlvs);
+
+ if (!downstream) {
+ /* we don't have a connection to this backend owned yet, let's get one (it might not be a fresh one, though) */
+ downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(d_threadData.mplexer, backend, now, std::string());
+ if (backend->d_config.useProxyProtocol) {
+ registerOwnedDownstreamConnection(downstream);
+ }
+ }
+
+ return downstream;
+}
+
+static void tcpClientThread(pdns::channel::Receiver<ConnectionInfo>&& queryReceiver, pdns::channel::Receiver<CrossProtocolQuery>&& crossProtocolQueryReceiver, pdns::channel::Receiver<TCPCrossProtocolResponse>&& crossProtocolResponseReceiver, pdns::channel::Sender<TCPCrossProtocolResponse>&& crossProtocolResponseSender, std::vector<ClientState*> tcpAcceptStates);
+
+TCPClientCollection::TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpAcceptStates) :
+ d_tcpclientthreads(maxThreads), d_maxthreads(maxThreads)
+{
+ for (size_t idx = 0; idx < maxThreads; idx++) {
+ addTCPClientThread(tcpAcceptStates);
+ }
+}
+
+void TCPClientCollection::addTCPClientThread(std::vector<ClientState*>& tcpAcceptStates)
+{
+ try {
+ auto [queryChannelSender, queryChannelReceiver] = pdns::channel::createObjectQueue<ConnectionInfo>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
+
+ auto [crossProtocolQueryChannelSender, crossProtocolQueryChannelReceiver] = pdns::channel::createObjectQueue<CrossProtocolQuery>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
+
+ auto [crossProtocolResponseChannelSender, crossProtocolResponseChannelReceiver] = pdns::channel::createObjectQueue<TCPCrossProtocolResponse>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
+
+ vinfolog("Adding TCP Client thread");
+
+ if (d_numthreads >= d_tcpclientthreads.size()) {
+ vinfolog("Adding a new TCP client thread would exceed the vector size (%d/%d), skipping. Consider increasing the maximum amount of TCP client threads with setMaxTCPClientThreads() in the configuration.", d_numthreads.load(), d_tcpclientthreads.size());
+ return;
+ }
+
+ TCPWorkerThread worker(std::move(queryChannelSender), std::move(crossProtocolQueryChannelSender));
+
+ try {
+ std::thread clientThread(tcpClientThread, std::move(queryChannelReceiver), std::move(crossProtocolQueryChannelReceiver), std::move(crossProtocolResponseChannelReceiver), std::move(crossProtocolResponseChannelSender), tcpAcceptStates);
+ clientThread.detach();
+ }
+ catch (const std::runtime_error& e) {
+ errlog("Error creating a TCP thread: %s", e.what());
+ return;
+ }
+
+ d_tcpclientthreads.at(d_numthreads) = std::move(worker);
+ ++d_numthreads;
+ }
+ catch (const std::exception& e) {
+ errlog("Error creating TCP worker: %", e.what());
+ }
+}
+
+std::unique_ptr<TCPClientCollection> g_tcpclientthreads;
+
+static IOState sendQueuedResponses(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
+{
+ IOState result = IOState::Done;
+
+ while (state->active() && !state->d_queuedResponses.empty()) {
+ DEBUGLOG("queue size is " << state->d_queuedResponses.size() << ", sending the next one");
+ TCPResponse resp = std::move(state->d_queuedResponses.front());
+ state->d_queuedResponses.pop_front();
+ state->d_state = IncomingTCPConnectionState::State::idle;
+ result = state->sendResponse(now, std::move(resp));
+ if (result != IOState::Done) {
+ return result;
+ }
+ }
+
+ state->d_state = IncomingTCPConnectionState::State::idle;
+ return IOState::Done;
+}
+
+void IncomingTCPConnectionState::handleResponseSent(TCPResponse& currentResponse)
+{
+ if (currentResponse.d_idstate.qtype == QType::AXFR || currentResponse.d_idstate.qtype == QType::IXFR) {
+ return;
+ }
+
+ --d_currentQueriesCount;
+
+ const auto& backend = currentResponse.d_connection ? currentResponse.d_connection->getDS() : currentResponse.d_ds;
+ if (!currentResponse.d_idstate.selfGenerated && backend) {
+ const auto& ids = currentResponse.d_idstate;
+ double udiff = ids.queryRealTime.udiff();
+ vinfolog("Got answer from %s, relayed to %s (%s, %d bytes), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), getProtocol().toString(), currentResponse.d_buffer.size(), udiff);
+
+ auto backendProtocol = backend->getProtocol();
+ if (backendProtocol == dnsdist::Protocol::DoUDP && !currentResponse.d_idstate.forwardedOverUDP) {
+ backendProtocol = dnsdist::Protocol::DoTCP;
+ }
+ ::handleResponseSent(ids, udiff, d_ci.remote, backend->d_config.remote, static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, backendProtocol, true);
+ }
+ else {
+ const auto& ids = currentResponse.d_idstate;
+ ::handleResponseSent(ids, 0., d_ci.remote, ComboAddress(), static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, ids.protocol, false);
+ }
+
+ currentResponse.d_buffer.clear();
+ currentResponse.d_connection.reset();
+}
+
+static void prependSizeToTCPQuery(PacketBuffer& buffer, size_t proxyProtocolPayloadSize)
+{
+ if (buffer.size() <= proxyProtocolPayloadSize) {
+ throw std::runtime_error("The payload size is smaller or equal to the buffer size");
+ }
+
+ uint16_t queryLen = proxyProtocolPayloadSize > 0 ? (buffer.size() - proxyProtocolPayloadSize) : buffer.size();
+ const std::array<uint8_t, 2> sizeBytes{static_cast<uint8_t>(queryLen / 256), static_cast<uint8_t>(queryLen % 256)};
+ /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes
+ that could occur if we had to deal with the size during the processing,
+ especially alignment issues */
+ buffer.insert(buffer.begin() + static_cast<PacketBuffer::iterator::difference_type>(proxyProtocolPayloadSize), sizeBytes.begin(), sizeBytes.end());
+}
+
+bool IncomingTCPConnectionState::canAcceptNewQueries(const struct timeval& now)
+{
+ if (d_hadErrors) {
+ DEBUGLOG("not accepting new queries because we encountered some error during the processing already");
+ return false;
+ }
+
+ // for DoH, this is already handled by the underlying library
+ if (!d_ci.cs->dohFrontend && d_currentQueriesCount >= d_ci.cs->d_maxInFlightQueriesPerConn) {
+ DEBUGLOG("not accepting new queries because we already have " << d_currentQueriesCount << " out of " << d_ci.cs->d_maxInFlightQueriesPerConn);
+ return false;
+ }
+
+ if (g_maxTCPQueriesPerConn != 0 && d_queriesCount > g_maxTCPQueriesPerConn) {
+ vinfolog("not accepting new queries from %s because it reached the maximum number of queries per conn (%d / %d)", d_ci.remote.toStringWithPort(), d_queriesCount, g_maxTCPQueriesPerConn);
+ return false;
+ }
+
+ if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
+ vinfolog("not accepting new queries from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
+ return false;
+ }
+
+ return true;
+}
+
+void IncomingTCPConnectionState::resetForNewQuery()
+{
+ d_buffer.clear();
+ d_currentPos = 0;
+ d_querySize = 0;
+ d_state = State::waitingForQuery;
+}
+
+std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs)
+{
+ auto connIt = d_ownedConnectionsToBackend.find(backend);
+ if (connIt == d_ownedConnectionsToBackend.end()) {
+ DEBUGLOG("no owned connection found for " << backend->getName());
+ return nullptr;
+ }
+
+ for (auto& conn : connIt->second) {
+ if (conn->canBeReused(true) && conn->matchesTLVs(tlvs)) {
+ DEBUGLOG("Got one owned connection accepting more for " << backend->getName());
+ conn->setReused();
+ return conn;
+ }
+ DEBUGLOG("not accepting more for " << backend->getName());
+ }
+
+ return nullptr;
+}
+
+void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn)
+{
+ d_ownedConnectionsToBackend[conn->getDS()].push_front(conn);
+}
+
+/* called when the buffer has been set and the rules have been processed, and only from handleIO (sometimes indirectly via handleQuery) */
+IOState IncomingTCPConnectionState::sendResponse(const struct timeval& now, TCPResponse&& response)
+{
+ d_state = State::sendingResponse;
+
+ const auto responseSize = static_cast<uint16_t>(response.d_buffer.size());
+ const std::array<uint8_t, 2> sizeBytes{static_cast<uint8_t>(responseSize / 256), static_cast<uint8_t>(responseSize % 256)};
+ /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes
+ that could occur if we had to deal with the size during the processing,
+ especially alignment issues */
+ response.d_buffer.insert(response.d_buffer.begin(), sizeBytes.begin(), sizeBytes.end());
+ d_currentPos = 0;
+ d_currentResponse = std::move(response);
+
+ try {
+ auto iostate = d_handler.tryWrite(d_currentResponse.d_buffer, d_currentPos, d_currentResponse.d_buffer.size());
+ if (iostate == IOState::Done) {
+ DEBUGLOG("response sent from " << __PRETTY_FUNCTION__);
+ handleResponseSent(d_currentResponse);
+ return iostate;
+ }
+ d_lastIOBlocked = true;
+ DEBUGLOG("partial write");
+ return iostate;
+ }
+ catch (const std::exception& e) {
+ vinfolog("Closing TCP client connection with %s: %s", d_ci.remote.toStringWithPort(), e.what());
+ DEBUGLOG("Closing TCP client connection: " << e.what());
+ ++d_ci.cs->tcpDiedSendingResponse;
+
+ terminateClientConnection();
+
+ return IOState::Done;
+ }
+}
+
+void IncomingTCPConnectionState::terminateClientConnection()
+{
+ DEBUGLOG("terminating client connection");
+ d_queuedResponses.clear();
+ /* we have already released idle connections that could be reused,
+ we don't care about the ones still waiting for responses */
+ for (auto& backend : d_ownedConnectionsToBackend) {
+ for (auto& conn : backend.second) {
+ conn->release();
+ }
+ }
+ d_ownedConnectionsToBackend.clear();
+
+ /* meaning we will no longer be 'active' when the backend
+ response or timeout comes in */
+ d_ioState.reset();
+
+ /* if we do have remaining async descriptors associated with this TLS
+ connection, we need to defer the destruction of the TLS object until
+ the engine has reported back, otherwise we have a use-after-free.. */
+ auto afds = d_handler.getAsyncFDs();
+ if (afds.empty()) {
+ d_handler.close();
+ }
+ else {
+ /* we might already be waiting, but we might also not because sometimes we have already been
+ notified via the descriptor, not received Async again, but the async job still exists.. */
+ auto state = shared_from_this();
+ for (const auto desc : afds) {
+ try {
+ state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state);
+ }
+ catch (...) {
+ }
+ }
+ }
+}
+
+void IncomingTCPConnectionState::queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response, bool fromBackend)
+{
+ // queue response
+ state->d_queuedResponses.emplace_back(std::move(response));
+ DEBUGLOG("queueing response, state is " << (int)state->d_state << ", queue size is now " << state->d_queuedResponses.size());
+
+ // when the response comes from a backend, there is a real possibility that we are currently
+ // idle, and thus not trying to send the response right away would make our ref count go to 0.
+ // Even if we are waiting for a query, we will not wake up before the new query arrives or a
+ // timeout occurs
+ if (state->d_state == State::idle || state->d_state == State::waitingForQuery) {
+ auto iostate = sendQueuedResponses(state, now);
+
+ if (iostate == IOState::Done && state->active()) {
+ if (state->canAcceptNewQueries(now)) {
+ state->resetForNewQuery();
+ state->d_state = State::waitingForQuery;
+ iostate = IOState::NeedRead;
+ }
+ else {
+ state->d_state = State::idle;
+ }
+ }
+
+ // for the same reason we need to update the state right away, nobody will do that for us
+ if (state->active()) {
+ updateIO(state, iostate, now);
+ // if we have not finished reading every available byte, we _need_ to do an actual read
+ // attempt before waiting for the socket to become readable again, because if there is
+ // buffered data available the socket might never become readable again.
+ // This is true as soon as we deal with TLS because TLS records are processed one by
+ // one and might not match what we see at the application layer, so data might already
+ // be available in the TLS library's buffers. This is especially true when OpenSSL's
+ // read-ahead mode is enabled because then it buffers even more than one SSL record
+ // for performance reasons.
+ if (fromBackend && !state->d_lastIOBlocked) {
+ state->handleIO();
+ }
+ }
+ }
+}
+
+void IncomingTCPConnectionState::handleAsyncReady([[maybe_unused]] int desc, FDMultiplexer::funcparam_t& param)
+{
+ auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
+
+ /* If we are here, the async jobs for this SSL* are finished
+ so we should be able to remove all FDs */
+ auto afds = state->d_handler.getAsyncFDs();
+ for (const auto afd : afds) {
+ try {
+ state->d_threadData.mplexer->removeReadFD(afd);
+ }
+ catch (...) {
+ }
+ }
+
+ if (state->active()) {
+ /* and now we restart our own I/O state machine */
+ state->handleIO();
+ }
+ else {
+ /* we were only waiting for the engine to come back,
+ to prevent a use-after-free */
+ state->d_handler.close();
+ }
+}
+
+void IncomingTCPConnectionState::updateIO(std::shared_ptr<IncomingTCPConnectionState>& state, IOState newState, const struct timeval& now)
+{
+ if (newState == IOState::Async) {
+ auto fds = state->d_handler.getAsyncFDs();
+ for (const auto desc : fds) {
+ state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state);
+ }
+ state->d_ioState->update(IOState::Done, handleIOCallback, state);
+ }
+ else {
+ state->d_ioState->update(newState, handleIOCallback, state, newState == IOState::NeedWrite ? state->getClientWriteTTD(now) : state->getClientReadTTD(now));
+ }
+}
+
+/* called from the backend code when a new response has been received */
+void IncomingTCPConnectionState::handleResponse(const struct timeval& now, TCPResponse&& response)
+{
+ if (std::this_thread::get_id() != d_creatorThreadID) {
+ handleCrossProtocolResponse(now, std::move(response));
+ return;
+ }
+
+ std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+
+ if (!response.isAsync() && response.d_connection && response.d_connection->getDS() && response.d_connection->getDS()->d_config.useProxyProtocol) {
+ // if we have added a TCP Proxy Protocol payload to a connection, don't release it to the general pool as no one else will be able to use it anyway
+ if (!response.d_connection->willBeReusable(true)) {
+ // if it can't be reused even by us, well
+ const auto connIt = state->d_ownedConnectionsToBackend.find(response.d_connection->getDS());
+ if (connIt != state->d_ownedConnectionsToBackend.end()) {
+ auto& list = connIt->second;
+
+ for (auto it = list.begin(); it != list.end(); ++it) {
+ if (*it == response.d_connection) {
+ try {
+ response.d_connection->release();
+ }
+ catch (const std::exception& e) {
+ vinfolog("Error releasing connection: %s", e.what());
+ }
+ list.erase(it);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (response.d_buffer.size() < sizeof(dnsheader)) {
+ state->terminateClientConnection();
+ return;
+ }
+
+ if (!response.isAsync()) {
+ try {
+ auto& ids = response.d_idstate;
+ std::shared_ptr<DownstreamState> backend = response.d_ds ? response.d_ds : (response.d_connection ? response.d_connection->getDS() : nullptr);
+ if (backend == nullptr || !responseContentMatches(response.d_buffer, ids.qname, ids.qtype, ids.qclass, backend)) {
+ state->terminateClientConnection();
+ return;
+ }
+
+ if (backend != nullptr) {
+ ++backend->responses;
+ }
+
+ DNSResponse dnsResponse(ids, response.d_buffer, backend);
+ dnsResponse.d_incomingTCPState = state;
+
+ memcpy(&response.d_cleartextDH, dnsResponse.getHeader().get(), sizeof(response.d_cleartextDH));
+
+ if (!processResponse(response.d_buffer, *state->d_threadData.localRespRuleActions, *state->d_threadData.localCacheInsertedRespRuleActions, dnsResponse, false)) {
+ state->terminateClientConnection();
+ return;
+ }
+
+ if (dnsResponse.isAsynchronous()) {
+ /* we are done for now */
+ return;
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Unexpected exception while handling response from backend: %s", e.what());
+ state->terminateClientConnection();
+ return;
+ }
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ ++state->d_ci.cs->responses;
+
+ queueResponse(state, now, std::move(response), true);
+}
+
+struct TCPCrossProtocolResponse
+{
+ TCPCrossProtocolResponse(TCPResponse&& response, std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now) :
+ d_response(std::move(response)), d_state(state), d_now(now)
+ {
+ }
+ TCPCrossProtocolResponse(const TCPCrossProtocolResponse&) = delete;
+ TCPCrossProtocolResponse& operator=(const TCPCrossProtocolResponse&) = delete;
+ TCPCrossProtocolResponse(TCPCrossProtocolResponse&&) = delete;
+ TCPCrossProtocolResponse& operator=(TCPCrossProtocolResponse&&) = delete;
+ ~TCPCrossProtocolResponse() = default;
+
+ TCPResponse d_response;
+ std::shared_ptr<IncomingTCPConnectionState> d_state;
+ struct timeval d_now;
+};
+
+class TCPCrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+ TCPCrossProtocolQuery(PacketBuffer&& buffer, InternalQueryState&& ids, std::shared_ptr<DownstreamState> backend, std::shared_ptr<IncomingTCPConnectionState> sender) :
+ CrossProtocolQuery(InternalQuery(std::move(buffer), std::move(ids)), backend), d_sender(std::move(sender))
+ {
+ }
+ TCPCrossProtocolQuery(const TCPCrossProtocolQuery&) = delete;
+ TCPCrossProtocolQuery& operator=(const TCPCrossProtocolQuery&) = delete;
+ TCPCrossProtocolQuery(TCPCrossProtocolQuery&&) = delete;
+ TCPCrossProtocolQuery& operator=(TCPCrossProtocolQuery&&) = delete;
+ ~TCPCrossProtocolQuery() override = default;
+
+ std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+ {
+ return d_sender;
+ }
+
+ DNSQuestion getDQ() override
+ {
+ auto& ids = query.d_idstate;
+ DNSQuestion dnsQuestion(ids, query.d_buffer);
+ dnsQuestion.d_incomingTCPState = d_sender;
+ return dnsQuestion;
+ }
+
+ DNSResponse getDR() override
+ {
+ auto& ids = query.d_idstate;
+ DNSResponse dnsResponse(ids, query.d_buffer, downstream);
+ dnsResponse.d_incomingTCPState = d_sender;
+ return dnsResponse;
+ }
+
+private:
+ std::shared_ptr<IncomingTCPConnectionState> d_sender;
+};
+
+std::unique_ptr<CrossProtocolQuery> IncomingTCPConnectionState::getCrossProtocolQuery(PacketBuffer&& query, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& backend)
+{
+ return std::make_unique<TCPCrossProtocolQuery>(std::move(query), std::move(state), backend, shared_from_this());
+}
+
+std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion)
+{
+ auto state = dnsQuestion.getIncomingTCPState();
+ if (!state) {
+ throw std::runtime_error("Trying to create a TCP cross protocol query without a valid TCP state");
+ }
+
+ dnsQuestion.ids.origID = dnsQuestion.getHeader()->id;
+ return std::make_unique<TCPCrossProtocolQuery>(std::move(dnsQuestion.getMutableData()), std::move(dnsQuestion.ids), nullptr, std::move(state));
+}
+
+void IncomingTCPConnectionState::handleCrossProtocolResponse(const struct timeval& now, TCPResponse&& response)
+{
+ std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+ try {
+ auto ptr = std::make_unique<TCPCrossProtocolResponse>(std::move(response), state, now);
+ if (!state->d_threadData.crossProtocolResponseSender.send(std::move(ptr))) {
+ ++dnsdist::metrics::g_stats.tcpCrossProtocolResponsePipeFull;
+ vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because the pipe is full");
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because we couldn't write to the pipe: %s", stringerror());
+ }
+}
+
+IncomingTCPConnectionState::QueryProcessingResult IncomingTCPConnectionState::handleQuery(PacketBuffer&& queryIn, const struct timeval& now, std::optional<int32_t> streamID)
+{
+ auto query = std::move(queryIn);
+ if (query.size() < sizeof(dnsheader)) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++d_ci.cs->nonCompliantQueries;
+ return QueryProcessingResult::TooSmall;
+ }
+
+ ++d_queriesCount;
+ ++d_ci.cs->queries;
+ ++dnsdist::metrics::g_stats.queries;
+
+ if (d_handler.isTLS()) {
+ auto tlsVersion = d_handler.getTLSVersion();
+ switch (tlsVersion) {
+ case LibsslTLSVersion::TLS10:
+ ++d_ci.cs->tls10queries;
+ break;
+ case LibsslTLSVersion::TLS11:
+ ++d_ci.cs->tls11queries;
+ break;
+ case LibsslTLSVersion::TLS12:
+ ++d_ci.cs->tls12queries;
+ break;
+ case LibsslTLSVersion::TLS13:
+ ++d_ci.cs->tls13queries;
+ break;
+ default:
+ ++d_ci.cs->tlsUnknownqueries;
+ }
+ }
+
+ auto state = shared_from_this();
+ InternalQueryState ids;
+ ids.origDest = d_proxiedDestination;
+ ids.origRemote = d_proxiedRemote;
+ ids.cs = d_ci.cs;
+ ids.queryRealTime.start();
+ if (streamID) {
+ ids.d_streamID = *streamID;
+ }
+
+ auto dnsCryptResponse = checkDNSCryptQuery(*d_ci.cs, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, true);
+ if (dnsCryptResponse) {
+ TCPResponse response;
+ d_state = State::idle;
+ ++d_currentQueriesCount;
+ queueResponse(state, now, std::move(response), false);
+ return QueryProcessingResult::SelfAnswered;
+ }
+
+ {
+ /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
+ const dnsheader_aligned dnsHeader(query.data());
+ if (!checkQueryHeaders(*dnsHeader, *d_ci.cs)) {
+ return QueryProcessingResult::InvalidHeaders;
+ }
+
+ if (dnsHeader->qdcount == 0) {
+ TCPResponse response;
+ auto queryID = dnsHeader->id;
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
+ header.rcode = RCode::NotImp;
+ header.qr = true;
+ return true;
+ });
+ response.d_idstate = std::move(ids);
+ response.d_idstate.origID = queryID;
+ response.d_idstate.selfGenerated = true;
+ response.d_buffer = std::move(query);
+ d_state = State::idle;
+ ++d_currentQueriesCount;
+ queueResponse(state, now, std::move(response), false);
+ return QueryProcessingResult::SelfAnswered;
+ }
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast
+ ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), static_cast<int>(query.size()), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ ids.protocol = getProtocol();
+ if (ids.dnsCryptQuery) {
+ ids.protocol = dnsdist::Protocol::DNSCryptTCP;
+ }
+
+ DNSQuestion dnsQuestion(ids, query);
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&ids](dnsheader& header) {
+ const uint16_t* flags = getFlagsFromDNSHeader(&header);
+ ids.origFlags = *flags;
+ return true;
+ });
+ dnsQuestion.d_incomingTCPState = state;
+ dnsQuestion.sni = d_handler.getServerNameIndication();
+
+ if (d_proxyProtocolValues) {
+ /* we need to copy them, because the next queries received on that connection will
+ need to get the _unaltered_ values */
+ dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(*d_proxyProtocolValues);
+ }
+
+ if (dnsQuestion.ids.qtype == QType::AXFR || dnsQuestion.ids.qtype == QType::IXFR) {
+ dnsQuestion.ids.skipCache = true;
+ }
+
+ if (forwardViaUDPFirst()) {
+ // if there was no EDNS, we add it with a large buffer size
+ // so we can use UDP to talk to the backend.
+ const dnsheader_aligned dnsHeader(query.data());
+ if (dnsHeader->arcount == 0U) {
+ if (addEDNS(query, 4096, false, 4096, 0)) {
+ dnsQuestion.ids.ednsAdded = true;
+ }
+ }
+ }
+
+ if (streamID) {
+ auto unit = getDOHUnit(*streamID);
+ if (unit) {
+ dnsQuestion.ids.du = std::move(unit);
+ }
+ }
+
+ std::shared_ptr<DownstreamState> backend;
+ auto result = processQuery(dnsQuestion, d_threadData.holders, backend);
+
+ if (result == ProcessQueryResult::Asynchronous) {
+ /* we are done for now */
+ ++d_currentQueriesCount;
+ return QueryProcessingResult::Asynchronous;
+ }
+
+ if (streamID) {
+ restoreDOHUnit(std::move(dnsQuestion.ids.du));
+ }
+
+ if (result == ProcessQueryResult::Drop) {
+ return QueryProcessingResult::Dropped;
+ }
+
+ // the buffer might have been invalidated by now
+ uint16_t queryID{0};
+ {
+ const auto dnsHeader = dnsQuestion.getHeader();
+ queryID = dnsHeader->id;
+ }
+
+ if (result == ProcessQueryResult::SendAnswer) {
+ TCPResponse response;
+ {
+ const auto dnsHeader = dnsQuestion.getHeader();
+ memcpy(&response.d_cleartextDH, dnsHeader.get(), sizeof(response.d_cleartextDH));
+ }
+ response.d_idstate = std::move(ids);
+ response.d_idstate.origID = queryID;
+ response.d_idstate.selfGenerated = true;
+ response.d_idstate.cs = d_ci.cs;
+ response.d_buffer = std::move(query);
+
+ d_state = State::idle;
+ ++d_currentQueriesCount;
+ queueResponse(state, now, std::move(response), false);
+ return QueryProcessingResult::SelfAnswered;
+ }
+
+ if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
+ return QueryProcessingResult::NoBackend;
+ }
+
+ dnsQuestion.ids.origID = queryID;
+
+ ++d_currentQueriesCount;
+
+ std::string proxyProtocolPayload;
+ if (backend->isDoH()) {
+ vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", ids.qname.toLogString(), QType(ids.qtype).toString(), d_proxiedRemote.toStringWithPort(), getProtocol().toString(), query.size(), backend->getNameWithAddr());
+
+ /* we need to do this _before_ creating the cross protocol query because
+ after that the buffer will have been moved */
+ if (backend->d_config.useProxyProtocol) {
+ proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+ }
+
+ auto cpq = std::make_unique<TCPCrossProtocolQuery>(std::move(query), std::move(ids), backend, state);
+ cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+ backend->passCrossProtocolQuery(std::move(cpq));
+ return QueryProcessingResult::Forwarded;
+ }
+ if (!backend->isTCPOnly() && forwardViaUDPFirst()) {
+ if (streamID) {
+ auto unit = getDOHUnit(*streamID);
+ if (unit) {
+ dnsQuestion.ids.du = std::move(unit);
+ }
+ }
+ if (assignOutgoingUDPQueryToBackend(backend, queryID, dnsQuestion, query)) {
+ return QueryProcessingResult::Forwarded;
+ }
+ restoreDOHUnit(std::move(dnsQuestion.ids.du));
+ // fallback to the normal flow
+ }
+
+ prependSizeToTCPQuery(query, 0);
+
+ auto downstreamConnection = getDownstreamConnection(backend, dnsQuestion.proxyProtocolValues, now);
+
+ if (backend->d_config.useProxyProtocol) {
+ /* if we ever sent a TLV over a connection, we can never go back */
+ if (!d_proxyProtocolPayloadHasTLV) {
+ d_proxyProtocolPayloadHasTLV = dnsQuestion.proxyProtocolValues && !dnsQuestion.proxyProtocolValues->empty();
+ }
+
+ proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+ }
+
+ if (dnsQuestion.proxyProtocolValues) {
+ downstreamConnection->setProxyProtocolValuesSent(std::move(dnsQuestion.proxyProtocolValues));
+ }
+
+ TCPQuery tcpquery(std::move(query), std::move(ids));
+ tcpquery.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+ vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", tcpquery.d_idstate.qname.toLogString(), QType(tcpquery.d_idstate.qtype).toString(), d_proxiedRemote.toStringWithPort(), getProtocol().toString(), tcpquery.d_buffer.size(), backend->getNameWithAddr());
+ std::shared_ptr<TCPQuerySender> incoming = state;
+ downstreamConnection->queueQuery(incoming, std::move(tcpquery));
+ return QueryProcessingResult::Forwarded;
+}
+
+void IncomingTCPConnectionState::handleIOCallback(int desc, FDMultiplexer::funcparam_t& param)
+{
+ auto conn = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
+ if (desc != conn->d_handler.getDescriptor()) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): __PRETTY_FUNCTION__ is fine
+ throw std::runtime_error("Unexpected socket descriptor " + std::to_string(desc) + " received in " + std::string(__PRETTY_FUNCTION__) + ", expected " + std::to_string(conn->d_handler.getDescriptor()));
+ }
+
+ conn->handleIO();
+}
+
+void IncomingTCPConnectionState::handleHandshakeDone(const struct timeval& now)
+{
+ if (d_handler.isTLS()) {
+ if (!d_handler.hasTLSSessionBeenResumed()) {
+ ++d_ci.cs->tlsNewSessions;
+ }
+ else {
+ ++d_ci.cs->tlsResumptions;
+ }
+ if (d_handler.getResumedFromInactiveTicketKey()) {
+ ++d_ci.cs->tlsInactiveTicketKey;
+ }
+ if (d_handler.getUnknownTicketKey()) {
+ ++d_ci.cs->tlsUnknownTicketKey;
+ }
+ }
+
+ d_handshakeDoneTime = now;
+}
+
+IncomingTCPConnectionState::ProxyProtocolResult IncomingTCPConnectionState::handleProxyProtocolPayload()
+{
+ do {
+ DEBUGLOG("reading proxy protocol header");
+ auto iostate = d_handler.tryRead(d_buffer, d_currentPos, d_proxyProtocolNeed, false, isProxyPayloadOutsideTLS());
+ if (iostate == IOState::Done) {
+ d_buffer.resize(d_currentPos);
+ ssize_t remaining = isProxyHeaderComplete(d_buffer);
+ if (remaining == 0) {
+ vinfolog("Unable to consume proxy protocol header in packet from TCP client %s", d_ci.remote.toStringWithPort());
+ ++dnsdist::metrics::g_stats.proxyProtocolInvalid;
+ return ProxyProtocolResult::Error;
+ }
+ if (remaining < 0) {
+ d_proxyProtocolNeed += -remaining;
+ d_buffer.resize(d_currentPos + d_proxyProtocolNeed);
+ /* we need to keep reading, since we might have buffered data */
+ }
+ else {
+ /* proxy header received */
+ std::vector<ProxyProtocolValue> proxyProtocolValues;
+ if (!handleProxyProtocol(d_ci.remote, true, *d_threadData.holders.acl, d_buffer, d_proxiedRemote, d_proxiedDestination, proxyProtocolValues)) {
+ vinfolog("Error handling the Proxy Protocol received from TCP client %s", d_ci.remote.toStringWithPort());
+ return ProxyProtocolResult::Error;
+ }
+
+ if (!proxyProtocolValues.empty()) {
+ d_proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
+ }
+
+ return ProxyProtocolResult::Done;
+ }
+ }
+ else {
+ d_lastIOBlocked = true;
+ }
+ } while (active() && !d_lastIOBlocked);
+
+ return ProxyProtocolResult::Reading;
+}
+
+IOState IncomingTCPConnectionState::handleHandshake(const struct timeval& now)
+{
+ DEBUGLOG("doing handshake");
+ auto iostate = d_handler.tryHandshake();
+ if (iostate == IOState::Done) {
+ DEBUGLOG("handshake done");
+ handleHandshakeDone(now);
+
+ if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && !isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
+ d_state = State::readingProxyProtocolHeader;
+ d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
+ d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+ }
+ else {
+ d_state = State::readingQuerySize;
+ }
+ }
+ else {
+ d_lastIOBlocked = true;
+ }
+
+ return iostate;
+}
+
+IOState IncomingTCPConnectionState::handleIncomingQueryReceived(const struct timeval& now)
+{
+ DEBUGLOG("query received");
+ d_buffer.resize(d_querySize);
+
+ d_state = State::idle;
+ auto processingResult = handleQuery(std::move(d_buffer), now, std::nullopt);
+ switch (processingResult) {
+ case QueryProcessingResult::TooSmall:
+ /* fall-through */
+ case QueryProcessingResult::InvalidHeaders:
+ /* fall-through */
+ case QueryProcessingResult::Dropped:
+ /* fall-through */
+ case QueryProcessingResult::NoBackend:
+ terminateClientConnection();
+ ;
+ default:
+ break;
+ }
+
+ /* the state might have been updated in the meantime, we don't want to override it
+ in that case */
+ if (active() && d_state != State::idle) {
+ if (d_ioState->isWaitingForRead()) {
+ return IOState::NeedRead;
+ }
+ if (d_ioState->isWaitingForWrite()) {
+ return IOState::NeedWrite;
+ }
+ return IOState::Done;
+ }
+ return IOState::Done;
+};
+
+void IncomingTCPConnectionState::handleExceptionDuringIO(const std::exception& exp)
+{
+ if (d_state == State::idle || d_state == State::waitingForQuery) {
+ /* no need to increase any counters in that case, the client is simply done with us */
+ }
+ else if (d_state == State::doingHandshake || d_state == State::readingProxyProtocolHeader || d_state == State::waitingForQuery || d_state == State::readingQuerySize || d_state == State::readingQuery) {
+ ++d_ci.cs->tcpDiedReadingQuery;
+ }
+ else if (d_state == State::sendingResponse) {
+ /* unlikely to happen here, the exception should be handled in sendResponse() */
+ ++d_ci.cs->tcpDiedSendingResponse;
+ }
+
+ if (d_ioState->isWaitingForWrite() || d_queriesCount == 0) {
+ DEBUGLOG("Got an exception while handling TCP query: " << exp.what());
+ vinfolog("Got an exception while handling (%s) TCP query from %s: %s", (d_ioState->isWaitingForRead() ? "reading" : "writing"), d_ci.remote.toStringWithPort(), exp.what());
+ }
+ else {
+ vinfolog("Closing TCP client connection with %s: %s", d_ci.remote.toStringWithPort(), exp.what());
+ DEBUGLOG("Closing TCP client connection: " << exp.what());
+ }
+ /* remove this FD from the IO multiplexer */
+ terminateClientConnection();
+}
+
+bool IncomingTCPConnectionState::readIncomingQuery(const timeval& now, IOState& iostate)
+{
+ if (!d_lastIOBlocked && (d_state == State::waitingForQuery || d_state == State::readingQuerySize)) {
+ DEBUGLOG("reading query size");
+ d_buffer.resize(sizeof(uint16_t));
+ iostate = d_handler.tryRead(d_buffer, d_currentPos, sizeof(uint16_t));
+ if (d_currentPos > 0) {
+ /* if we got at least one byte, we can't go around sending responses */
+ d_state = State::readingQuerySize;
+ }
+
+ if (iostate == IOState::Done) {
+ DEBUGLOG("query size received");
+ d_state = State::readingQuery;
+ d_querySizeReadTime = now;
+ if (d_queriesCount == 0) {
+ d_firstQuerySizeReadTime = now;
+ }
+ d_querySize = d_buffer.at(0) * 256 + d_buffer.at(1);
+ if (d_querySize < sizeof(dnsheader)) {
+ /* go away */
+ terminateClientConnection();
+ return true;
+ }
+
+ d_buffer.resize(d_querySize);
+ d_currentPos = 0;
+ }
+ else {
+ d_lastIOBlocked = true;
+ }
+ }
+
+ if (!d_lastIOBlocked && d_state == State::readingQuery) {
+ DEBUGLOG("reading query");
+ iostate = d_handler.tryRead(d_buffer, d_currentPos, d_querySize);
+ if (iostate == IOState::Done) {
+ iostate = handleIncomingQueryReceived(now);
+ }
+ else {
+ d_lastIOBlocked = true;
+ }
+ }
+
+ return false;
+}
+
+void IncomingTCPConnectionState::handleIO()
+{
+ // why do we loop? Because the TLS layer does buffering, and thus can have data ready to read
+ // even though the underlying socket is not ready, so we need to actually ask for the data first
+ IOState iostate = IOState::Done;
+ timeval now{};
+ gettimeofday(&now, nullptr);
+
+ do {
+ iostate = IOState::Done;
+ IOStateGuard ioGuard(d_ioState);
+
+ if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
+ vinfolog("Terminating TCP connection from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
+ // will be handled by the ioGuard
+ // handleNewIOState(state, IOState::Done, fd, handleIOCallback);
+ return;
+ }
+
+ d_lastIOBlocked = false;
+
+ try {
+ if (d_state == State::starting) {
+ if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
+ d_state = State::readingProxyProtocolHeader;
+ d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
+ d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+ }
+ else {
+ d_state = State::doingHandshake;
+ }
+ }
+
+ if (d_state == State::doingHandshake) {
+ iostate = handleHandshake(now);
+ }
+
+ if (!d_lastIOBlocked && d_state == State::readingProxyProtocolHeader) {
+ auto status = handleProxyProtocolPayload();
+ if (status == ProxyProtocolResult::Done) {
+ if (isProxyPayloadOutsideTLS()) {
+ d_state = State::doingHandshake;
+ iostate = handleHandshake(now);
+ }
+ else {
+ d_state = State::readingQuerySize;
+ d_buffer.resize(sizeof(uint16_t));
+ d_currentPos = 0;
+ d_proxyProtocolNeed = 0;
+ }
+ }
+ else if (status == ProxyProtocolResult::Error) {
+ iostate = IOState::Done;
+ }
+ else {
+ iostate = IOState::NeedRead;
+ }
+ }
+
+ if (!d_lastIOBlocked && (d_state == State::waitingForQuery || d_state == State::readingQuerySize || d_state == State::readingQuery)) {
+ if (readIncomingQuery(now, iostate)) {
+ return;
+ }
+ }
+
+ if (!d_lastIOBlocked && d_state == State::sendingResponse) {
+ DEBUGLOG("sending response");
+ iostate = d_handler.tryWrite(d_currentResponse.d_buffer, d_currentPos, d_currentResponse.d_buffer.size());
+ if (iostate == IOState::Done) {
+ DEBUGLOG("response sent from " << __PRETTY_FUNCTION__);
+ handleResponseSent(d_currentResponse);
+ d_state = State::idle;
+ }
+ else {
+ d_lastIOBlocked = true;
+ }
+ }
+
+ if (active() && !d_lastIOBlocked && iostate == IOState::Done && (d_state == State::idle || d_state == State::waitingForQuery)) {
+ // try sending queued responses
+ DEBUGLOG("send responses, if any");
+ auto state = shared_from_this();
+ iostate = sendQueuedResponses(state, now);
+
+ if (!d_lastIOBlocked && active() && iostate == IOState::Done) {
+ // if the query has been passed to a backend, or dropped, and the responses have been sent,
+ // we can start reading again
+ if (canAcceptNewQueries(now)) {
+ resetForNewQuery();
+ iostate = IOState::NeedRead;
+ }
+ else {
+ d_state = State::idle;
+ iostate = IOState::Done;
+ }
+ }
+ }
+
+ if (d_state != State::idle && d_state != State::doingHandshake && d_state != State::readingProxyProtocolHeader && d_state != State::waitingForQuery && d_state != State::readingQuerySize && d_state != State::readingQuery && d_state != State::sendingResponse) {
+ vinfolog("Unexpected state %d in handleIOCallback", static_cast<int>(d_state));
+ }
+ }
+ catch (const std::exception& exp) {
+ /* most likely an EOF because the other end closed the connection,
+ but it might also be a real IO error or something else.
+ Let's just drop the connection
+ */
+ handleExceptionDuringIO(exp);
+ }
+
+ if (!active()) {
+ DEBUGLOG("state is no longer active");
+ return;
+ }
+
+ auto state = shared_from_this();
+ if (iostate == IOState::Done) {
+ d_ioState->update(iostate, handleIOCallback, state);
+ }
+ else {
+ updateIO(state, iostate, now);
+ }
+ ioGuard.release();
+ } while ((iostate == IOState::NeedRead || iostate == IOState::NeedWrite) && !d_lastIOBlocked);
+}
+
+void IncomingTCPConnectionState::notifyIOError(const struct timeval& now, TCPResponse&& response)
+{
+ if (std::this_thread::get_id() != d_creatorThreadID) {
+ /* empty buffer will signal an IO error */
+ response.d_buffer.clear();
+ handleCrossProtocolResponse(now, std::move(response));
+ return;
+ }
+
+ std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+ --state->d_currentQueriesCount;
+ state->d_hadErrors = true;
+
+ if (state->d_state == State::sendingResponse) {
+ /* if we have responses to send, let's do that first */
+ }
+ else if (!state->d_queuedResponses.empty()) {
+ /* stop reading and send what we have */
+ try {
+ auto iostate = sendQueuedResponses(state, now);
+
+ if (state->active() && iostate != IOState::Done) {
+ // we need to update the state right away, nobody will do that for us
+ updateIO(state, iostate, now);
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Exception in notifyIOError: %s", e.what());
+ }
+ }
+ else {
+ // the backend code already tried to reconnect if it was possible
+ state->terminateClientConnection();
+ }
+}
+
+void IncomingTCPConnectionState::handleXFRResponse(const struct timeval& now, TCPResponse&& response)
+{
+ if (std::this_thread::get_id() != d_creatorThreadID) {
+ handleCrossProtocolResponse(now, std::move(response));
+ return;
+ }
+
+ std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+ queueResponse(state, now, std::move(response), true);
+}
+
+void IncomingTCPConnectionState::handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bool write)
+{
+ vinfolog("Timeout while %s TCP client %s", (write ? "writing to" : "reading from"), state->d_ci.remote.toStringWithPort());
+ DEBUGLOG("client timeout");
+ DEBUGLOG("Processed " << state->d_queriesCount << " queries, current count is " << state->d_currentQueriesCount << ", " << state->d_ownedConnectionsToBackend.size() << " owned connections, " << state->d_queuedResponses.size() << " response queued");
+
+ if (write || state->d_currentQueriesCount == 0) {
+ ++state->d_ci.cs->tcpClientTimeouts;
+ state->d_ioState.reset();
+ }
+ else {
+ DEBUGLOG("Going idle");
+ /* we still have some queries in flight, let's just stop reading for now */
+ state->d_state = State::idle;
+ state->d_ioState->update(IOState::Done, handleIOCallback, state);
+ }
+}
+
+static void handleIncomingTCPQuery(int pipefd, FDMultiplexer::funcparam_t& param)
+{
+ auto* threadData = boost::any_cast<TCPClientThreadData*>(param);
+
+ std::unique_ptr<ConnectionInfo> citmp{nullptr};
+ try {
+ auto tmp = threadData->queryReceiver.receive();
+ if (!tmp) {
+ return;
+ }
+ citmp = std::move(*tmp);
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error("Error while reading from the TCP query channel: " + std::string(e.what()));
+ }
+
+ g_tcpclientthreads->decrementQueuedCount();
+
+ timeval now{};
+ gettimeofday(&now, nullptr);
+
+ if (citmp->cs->dohFrontend) {
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ auto state = std::make_shared<IncomingHTTP2Connection>(std::move(*citmp), *threadData, now);
+ state->handleIO();
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+ }
+ else {
+ auto state = std::make_shared<IncomingTCPConnectionState>(std::move(*citmp), *threadData, now);
+ state->handleIO();
+ }
+}
+
+static void handleCrossProtocolQuery(int pipefd, FDMultiplexer::funcparam_t& param)
+{
+ auto* threadData = boost::any_cast<TCPClientThreadData*>(param);
+
+ std::unique_ptr<CrossProtocolQuery> cpq{nullptr};
+ try {
+ auto tmp = threadData->crossProtocolQueryReceiver.receive();
+ if (!tmp) {
+ return;
+ }
+ cpq = std::move(*tmp);
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error("Error while reading from the TCP cross-protocol channel: " + std::string(e.what()));
+ }
+
+ timeval now{};
+ gettimeofday(&now, nullptr);
+
+ std::shared_ptr<TCPQuerySender> tqs = cpq->getTCPQuerySender();
+ auto query = std::move(cpq->query);
+ auto downstreamServer = std::move(cpq->downstream);
+
+ try {
+ auto downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::string());
+
+ prependSizeToTCPQuery(query.d_buffer, query.d_idstate.d_proxyProtocolPayloadSize);
+
+ vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", query.d_idstate.qname.toLogString(), QType(query.d_idstate.qtype).toString(), query.d_idstate.origRemote.toStringWithPort(), query.d_idstate.protocol.toString(), query.d_buffer.size(), downstreamServer->getNameWithAddr());
+
+ downstream->queueQuery(tqs, std::move(query));
+ }
+ catch (...) {
+ tqs->notifyIOError(now, std::move(query));
+ }
+}
+
+static void handleCrossProtocolResponse(int pipefd, FDMultiplexer::funcparam_t& param)
+{
+ auto* threadData = boost::any_cast<TCPClientThreadData*>(param);
+
+ std::unique_ptr<TCPCrossProtocolResponse> cpr{nullptr};
+ try {
+ auto tmp = threadData->crossProtocolResponseReceiver.receive();
+ if (!tmp) {
+ return;
+ }
+ cpr = std::move(*tmp);
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error("Error while reading from the TCP cross-protocol response: " + std::string(e.what()));
+ }
+
+ auto& response = *cpr;
+
+ try {
+ if (response.d_response.d_buffer.empty()) {
+ response.d_state->notifyIOError(response.d_now, std::move(response.d_response));
+ }
+ else if (response.d_response.d_idstate.qtype == QType::AXFR || response.d_response.d_idstate.qtype == QType::IXFR) {
+ response.d_state->handleXFRResponse(response.d_now, std::move(response.d_response));
+ }
+ else {
+ response.d_state->handleResponse(response.d_now, std::move(response.d_response));
+ }
+ }
+ catch (...) {
+ /* no point bubbling up from there */
+ }
+}
+
+struct TCPAcceptorParam
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ ClientState& clientState;
+ ComboAddress local;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+ LocalStateHolder<NetmaskGroup>& acl;
+ int socket{-1};
+};
+
+static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData);
+
+static void scanForTimeouts(const TCPClientThreadData& data, const timeval& now)
+{
+ auto expiredReadConns = data.mplexer->getTimeouts(now, false);
+ for (const auto& cbData : expiredReadConns) {
+ if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
+ auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
+ if (cbData.first == state->d_handler.getDescriptor()) {
+ vinfolog("Timeout (read) from remote TCP client %s", state->d_ci.remote.toStringWithPort());
+ state->handleTimeout(state, false);
+ }
+ }
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ else if (cbData.second.type() == typeid(std::shared_ptr<IncomingHTTP2Connection>)) {
+ auto state = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(cbData.second);
+ if (cbData.first == state->d_handler.getDescriptor()) {
+ vinfolog("Timeout (read) from remote H2 client %s", state->d_ci.remote.toStringWithPort());
+ std::shared_ptr<IncomingTCPConnectionState> parentState = state;
+ state->handleTimeout(parentState, false);
+ }
+ }
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+ else if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
+ auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
+ vinfolog("Timeout (read) from remote backend %s", conn->getBackendName());
+ conn->handleTimeout(now, false);
+ }
+ }
+
+ auto expiredWriteConns = data.mplexer->getTimeouts(now, true);
+ for (const auto& cbData : expiredWriteConns) {
+ if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
+ auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
+ if (cbData.first == state->d_handler.getDescriptor()) {
+ vinfolog("Timeout (write) from remote TCP client %s", state->d_ci.remote.toStringWithPort());
+ state->handleTimeout(state, true);
+ }
+ }
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ else if (cbData.second.type() == typeid(std::shared_ptr<IncomingHTTP2Connection>)) {
+ auto state = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(cbData.second);
+ if (cbData.first == state->d_handler.getDescriptor()) {
+ vinfolog("Timeout (write) from remote H2 client %s", state->d_ci.remote.toStringWithPort());
+ std::shared_ptr<IncomingTCPConnectionState> parentState = state;
+ state->handleTimeout(parentState, true);
+ }
+ }
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+ else if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
+ auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
+ vinfolog("Timeout (write) from remote backend %s", conn->getBackendName());
+ conn->handleTimeout(now, true);
+ }
+ }
+}
+
+static void dumpTCPStates(const TCPClientThreadData& data)
+{
+ /* just to keep things clean in the output, debug only */
+ static std::mutex s_lock;
+ std::lock_guard<decltype(s_lock)> lck(s_lock);
+ if (g_tcpStatesDumpRequested > 0) {
+ /* no race here, we took the lock so it can only be increased in the meantime */
+ --g_tcpStatesDumpRequested;
+ infolog("Dumping the TCP states, as requested:");
+ data.mplexer->runForAllWatchedFDs([](bool isRead, int desc, const FDMultiplexer::funcparam_t& param, struct timeval ttd) {
+ timeval lnow{};
+ gettimeofday(&lnow, nullptr);
+ if (ttd.tv_sec > 0) {
+ infolog("- Descriptor %d is in %s state, TTD in %d", desc, (isRead ? "read" : "write"), (ttd.tv_sec - lnow.tv_sec));
+ }
+ else {
+ infolog("- Descriptor %d is in %s state, no TTD set", desc, (isRead ? "read" : "write"));
+ }
+
+ if (param.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
+ auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
+ infolog(" - %s", state->toString());
+ }
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ else if (param.type() == typeid(std::shared_ptr<IncomingHTTP2Connection>)) {
+ auto state = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(param);
+ infolog(" - %s", state->toString());
+ }
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+ else if (param.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
+ auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(param);
+ infolog(" - %s", conn->toString());
+ }
+ else if (param.type() == typeid(TCPClientThreadData*)) {
+ infolog(" - Worker thread pipe");
+ }
+ });
+ infolog("The TCP/DoT client cache has %d active and %d idle outgoing connections cached", t_downstreamTCPConnectionsManager.getActiveCount(), t_downstreamTCPConnectionsManager.getIdleCount());
+ }
+}
+
+// NOLINTNEXTLINE(performance-unnecessary-value-param): you are wrong, clang-tidy, go home
+static void tcpClientThread(pdns::channel::Receiver<ConnectionInfo>&& queryReceiver, pdns::channel::Receiver<CrossProtocolQuery>&& crossProtocolQueryReceiver, pdns::channel::Receiver<TCPCrossProtocolResponse>&& crossProtocolResponseReceiver, pdns::channel::Sender<TCPCrossProtocolResponse>&& crossProtocolResponseSender, std::vector<ClientState*> tcpAcceptStates)
+{
+ /* we get launched with a pipe on which we receive file descriptors from clients that we own
+ from that point on */
+
+ setThreadName("dnsdist/tcpClie");
+
+ try {
+ TCPClientThreadData data;
+ data.crossProtocolResponseSender = std::move(crossProtocolResponseSender);
+ data.queryReceiver = std::move(queryReceiver);
+ data.crossProtocolQueryReceiver = std::move(crossProtocolQueryReceiver);
+ data.crossProtocolResponseReceiver = std::move(crossProtocolResponseReceiver);
+
+ data.mplexer->addReadFD(data.queryReceiver.getDescriptor(), handleIncomingTCPQuery, &data);
+ data.mplexer->addReadFD(data.crossProtocolQueryReceiver.getDescriptor(), handleCrossProtocolQuery, &data);
+ data.mplexer->addReadFD(data.crossProtocolResponseReceiver.getDescriptor(), handleCrossProtocolResponse, &data);
+
+ /* only used in single acceptor mode for now */
+ auto acl = g_ACL.getLocal();
+ std::vector<TCPAcceptorParam> acceptParams;
+ acceptParams.reserve(tcpAcceptStates.size());
+
+ for (auto& state : tcpAcceptStates) {
+ acceptParams.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD});
+ for (const auto& [addr, socket] : state->d_additionalAddresses) {
+ acceptParams.emplace_back(TCPAcceptorParam{*state, addr, acl, socket});
+ }
+ }
+
+ auto acceptCallback = [&data](int socket, FDMultiplexer::funcparam_t& funcparam) {
+ const auto* acceptorParam = boost::any_cast<const TCPAcceptorParam*>(funcparam);
+ acceptNewConnection(*acceptorParam, &data);
+ };
+
+ for (const auto& param : acceptParams) {
+ setNonBlocking(param.socket);
+ data.mplexer->addReadFD(param.socket, acceptCallback, ¶m);
+ }
+
+ timeval now{};
+ gettimeofday(&now, nullptr);
+ time_t lastTimeoutScan = now.tv_sec;
+
+ for (;;) {
+ data.mplexer->run(&now);
+
+ try {
+ t_downstreamTCPConnectionsManager.cleanupClosedConnections(now);
+
+ if (now.tv_sec > lastTimeoutScan) {
+ lastTimeoutScan = now.tv_sec;
+ scanForTimeouts(data, now);
+
+ if (g_tcpStatesDumpRequested > 0) {
+ dumpTCPStates(data);
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ warnlog("Error in TCP worker thread: %s", e.what());
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ errlog("Fatal error in TCP worker thread: %s", e.what());
+ }
+}
+
+static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData)
+{
+ auto& clientState = param.clientState;
+ auto& acl = param.acl;
+ const bool checkACL = clientState.dohFrontend == nullptr || (!clientState.dohFrontend->d_trustForwardedForHeader && clientState.dohFrontend->d_earlyACLDrop);
+ const int socket = param.socket;
+ bool tcpClientCountIncremented = false;
+ ComboAddress remote;
+ remote.sin4.sin_family = param.local.sin4.sin_family;
+
+ tcpClientCountIncremented = false;
+ try {
+ socklen_t remlen = remote.getSocklen();
+ ConnectionInfo connInfo(&clientState);
+#ifdef HAVE_ACCEPT4
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ connInfo.fd = accept4(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen, SOCK_NONBLOCK);
+#else
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ connInfo.fd = accept(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen);
+#endif
+ // will be decremented when the ConnectionInfo object is destroyed, no matter the reason
+ auto concurrentConnections = ++clientState.tcpCurrentConnections;
+
+ if (connInfo.fd < 0) {
+ throw std::runtime_error((boost::format("accepting new connection on socket: %s") % stringerror()).str());
+ }
+
+ if (checkACL && !acl->match(remote)) {
+ ++dnsdist::metrics::g_stats.aclDrops;
+ vinfolog("Dropped TCP connection from %s because of ACL", remote.toStringWithPort());
+ return;
+ }
+
+ if (clientState.d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > clientState.d_tcpConcurrentConnectionsLimit) {
+ vinfolog("Dropped TCP connection from %s because of concurrent connections limit", remote.toStringWithPort());
+ return;
+ }
+
+ if (concurrentConnections > clientState.tcpMaxConcurrentConnections.load()) {
+ clientState.tcpMaxConcurrentConnections.store(concurrentConnections);
+ }
+
+#ifndef HAVE_ACCEPT4
+ if (!setNonBlocking(connInfo.fd)) {
+ return;
+ }
+#endif
+
+ setTCPNoDelay(connInfo.fd); // disable NAGLE
+
+ if (g_maxTCPQueuedConnections > 0 && g_tcpclientthreads->getQueuedCount() >= g_maxTCPQueuedConnections) {
+ vinfolog("Dropping TCP connection from %s because we have too many queued already", remote.toStringWithPort());
+ return;
+ }
+
+ if (!dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(remote)) {
+ vinfolog("Dropping TCP connection from %s because we have too many from this client already", remote.toStringWithPort());
+ return;
+ }
+ tcpClientCountIncremented = true;
+
+ vinfolog("Got TCP connection from %s", remote.toStringWithPort());
+
+ connInfo.remote = remote;
+
+ if (threadData == nullptr) {
+ if (!g_tcpclientthreads->passConnectionToThread(std::make_unique<ConnectionInfo>(std::move(connInfo)))) {
+ if (tcpClientCountIncremented) {
+ dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
+ }
+ }
+ }
+ else {
+ timeval now{};
+ gettimeofday(&now, nullptr);
+
+ if (connInfo.cs->dohFrontend) {
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ auto state = std::make_shared<IncomingHTTP2Connection>(std::move(connInfo), *threadData, now);
+ state->handleIO();
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+ }
+ else {
+ auto state = std::make_shared<IncomingTCPConnectionState>(std::move(connInfo), *threadData, now);
+ state->handleIO();
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ errlog("While reading a TCP question: %s", e.what());
+ if (tcpClientCountIncremented) {
+ dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
+ }
+ }
+ catch (...) {
+ }
+}
+
+/* spawn as many of these as required, they call Accept on a socket on which they will accept queries, and
+ they will hand off to worker threads & spawn more of them if required
+*/
+#ifndef USE_SINGLE_ACCEPTOR_THREAD
+void tcpAcceptorThread(const std::vector<ClientState*>& states)
+{
+ setThreadName("dnsdist/tcpAcce");
+
+ auto acl = g_ACL.getLocal();
+ std::vector<TCPAcceptorParam> params;
+ params.reserve(states.size());
+
+ for (const auto& state : states) {
+ params.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD});
+ for (const auto& [addr, socket] : state->d_additionalAddresses) {
+ params.emplace_back(TCPAcceptorParam{*state, addr, acl, socket});
+ }
+ }
+
+ if (params.size() == 1) {
+ while (true) {
+ acceptNewConnection(params.at(0), nullptr);
+ }
+ }
+ else {
+ auto acceptCallback = [](int socket, FDMultiplexer::funcparam_t& funcparam) {
+ const auto* acceptorParam = boost::any_cast<const TCPAcceptorParam*>(funcparam);
+ acceptNewConnection(*acceptorParam, nullptr);
+ };
+
+ auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
+ for (const auto& param : params) {
+ mplexer->addReadFD(param.socket, acceptCallback, ¶m);
+ }
+
+ timeval now{};
+ while (true) {
+ mplexer->run(&now, -1);
+ }
+ }
+}
+#endif
*/
#pragma once
+#include <optional>
#include <unistd.h>
+#include "channel.hh"
#include "iputils.hh"
#include "dnsdist.hh"
+#include "dnsdist-metrics.hh"
struct ConnectionInfo
{
InternalQueryState d_idstate;
std::string d_proxyProtocolPayload;
PacketBuffer d_buffer;
- uint32_t d_proxyProtocolPayloadAddedSize{0};
uint32_t d_ixfrQuerySerial{0};
- uint32_t d_xfrMasterSerial{0};
+ uint32_t d_xfrPrimarySerial{0};
uint32_t d_xfrSerialCount{0};
uint32_t d_downstreamFailures{0};
- uint8_t d_xfrMasterSerialCount{0};
+ uint8_t d_xfrPrimarySerialCount{0};
bool d_xfrStarted{false};
bool d_proxyProtocolPayloadAdded{false};
};
}
TCPResponse(PacketBuffer&& buffer, InternalQueryState&& state, std::shared_ptr<ConnectionToBackend> conn, std::shared_ptr<DownstreamState> ds) :
- TCPQuery(std::move(buffer), std::move(state)), d_connection(conn), d_ds(ds)
+ TCPQuery(std::move(buffer), std::move(state)), d_connection(std::move(conn)), d_ds(std::move(ds))
{
if (d_buffer.size() >= sizeof(dnsheader)) {
- memcpy(&d_cleartextDH, reinterpret_cast<const dnsheader*>(d_buffer.data()), sizeof(d_cleartextDH));
+ dnsheader_aligned header(d_buffer.data());
+ memcpy(&d_cleartextDH, header.get(), sizeof(d_cleartextDH));
+ }
+ else {
+ memset(&d_cleartextDH, 0, sizeof(d_cleartextDH));
+ }
+ }
+
+ TCPResponse(TCPQuery&& query) :
+ TCPQuery(std::move(query))
+ {
+ if (d_buffer.size() >= sizeof(dnsheader)) {
+ dnsheader_aligned header(d_buffer.data());
+ memcpy(&d_cleartextDH, header.get(), sizeof(d_cleartextDH));
}
else {
memset(&d_cleartextDH, 0, sizeof(d_cleartextDH));
virtual bool active() const = 0;
virtual void handleResponse(const struct timeval& now, TCPResponse&& response) = 0;
virtual void handleXFRResponse(const struct timeval& now, TCPResponse&& response) = 0;
- virtual void notifyIOError(InternalQueryState&& query, const struct timeval& now) = 0;
+ virtual void notifyIOError(const struct timeval& now, TCPResponse&& response) = 0;
/* whether the connection should be automatically released to the pool after handleResponse()
has been called */
InternalQuery query;
std::shared_ptr<DownstreamState> downstream{nullptr};
- size_t proxyProtocolPayloadSize{0};
bool d_isResponse{false};
};
class TCPClientCollection
{
public:
- TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpStates);
+ TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpAcceptStates);
bool passConnectionToThread(std::unique_ptr<ConnectionInfo>&& conn)
{
}
uint64_t pos = d_pos++;
- auto pipe = d_tcpclientthreads.at(pos % d_numthreads).d_newConnectionPipe.getHandle();
- auto tmp = conn.release();
-
/* we need to increment this counter _before_ writing to the pipe,
otherwise there is a very real possiblity that the other end
decrement the counter before we can increment it, leading to an underflow */
++d_queued;
- if (write(pipe, &tmp, sizeof(tmp)) != sizeof(tmp)) {
+ if (!d_tcpclientthreads.at(pos % d_numthreads).d_querySender.send(std::move(conn))) {
--d_queued;
- ++g_stats.tcpQueryPipeFull;
- delete tmp;
- tmp = nullptr;
+ ++dnsdist::metrics::g_stats.tcpQueryPipeFull;
return false;
}
+
return true;
}
}
uint64_t pos = d_pos++;
- auto pipe = d_tcpclientthreads.at(pos % d_numthreads).d_crossProtocolQueriesPipe.getHandle();
- auto tmp = cpq.release();
-
- if (write(pipe, &tmp, sizeof(tmp)) != sizeof(tmp)) {
- ++g_stats.tcpCrossProtocolQueryPipeFull;
- delete tmp;
- tmp = nullptr;
+ if (!d_tcpclientthreads.at(pos % d_numthreads).d_crossProtocolQuerySender.send(std::move(cpq))) {
+ ++dnsdist::metrics::g_stats.tcpCrossProtocolQueryPipeFull;
return false;
}
{
}
- TCPWorkerThread(int newConnPipe, int crossProtocolQueriesPipe, int crossProtocolResponsesPipe) :
- d_newConnectionPipe(newConnPipe), d_crossProtocolQueriesPipe(crossProtocolQueriesPipe), d_crossProtocolResponsesPipe(crossProtocolResponsesPipe)
+ TCPWorkerThread(pdns::channel::Sender<ConnectionInfo>&& querySender, pdns::channel::Sender<CrossProtocolQuery>&& crossProtocolQuerySender) :
+ d_querySender(std::move(querySender)), d_crossProtocolQuerySender(std::move(crossProtocolQuerySender))
{
}
TCPWorkerThread(const TCPWorkerThread& rhs) = delete;
TCPWorkerThread& operator=(const TCPWorkerThread&) = delete;
- FDWrapper d_newConnectionPipe;
- FDWrapper d_crossProtocolQueriesPipe;
- FDWrapper d_crossProtocolResponsesPipe;
+ pdns::channel::Sender<ConnectionInfo> d_querySender;
+ pdns::channel::Sender<CrossProtocolQuery> d_crossProtocolQuerySender;
};
std::vector<TCPWorkerThread> d_tcpclientthreads;
extern std::unique_ptr<TCPClientCollection> g_tcpclientthreads;
-std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dq);
+std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion);
# eventual consistency is fine
race:DownstreamState::stop
race:DownstreamState::submitHealthCheckResult
+race:DownstreamState::healthCheckRequired
race:carbonDumpThread
+# There is a slight race when we detect an error and
+# re-connect a backend, where the UDP responder thread
+# can still be looking at the existing socket descriptors.
+# Actually writing to these is protected by a mutex, though.
+race:DownstreamState::reconnect
+++ /dev/null
-../dnsdist-web.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <boost/format.hpp>
+#include <sstream>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <thread>
+
+#include "ext/json11/json11.hpp"
+#include <yahttp/yahttp.hpp>
+
+#include "base64.hh"
+#include "connection-management.hh"
+#include "dnsdist.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-healthchecks.hh"
+#include "dnsdist-metrics.hh"
+#include "dnsdist-prometheus.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-rule-chains.hh"
+#include "dnsdist-web.hh"
+#include "dolog.hh"
+#include "gettime.hh"
+#include "threadname.hh"
+#include "sstuff.hh"
+
+struct WebserverConfig
+{
+ WebserverConfig()
+ {
+ acl.toMasks("127.0.0.1, ::1");
+ }
+
+ NetmaskGroup acl;
+ std::unique_ptr<CredentialsHolder> password;
+ std::unique_ptr<CredentialsHolder> apiKey;
+ boost::optional<std::unordered_map<std::string, std::string>> customHeaders;
+ bool apiRequiresAuthentication{true};
+ bool dashboardRequiresAuthentication{true};
+ bool statsRequireAuthentication{true};
+};
+
+bool g_apiReadWrite{false};
+LockGuarded<WebserverConfig> g_webserverConfig;
+std::string g_apiConfigDirectory;
+
+static ConcurrentConnectionManager s_connManager(100);
+
+std::string getWebserverConfig()
+{
+ ostringstream out;
+
+ {
+ auto config = g_webserverConfig.lock();
+ out << "Current web server configuration:" << endl;
+ out << "ACL: " << config->acl.toString() << endl;
+ out << "Custom headers: ";
+ if (config->customHeaders) {
+ out << endl;
+ for (const auto& header : *config->customHeaders) {
+ out << " - " << header.first << ": " << header.second << endl;
+ }
+ }
+ else {
+ out << "None" << endl;
+ }
+ out << "API requires authentication: " << (config->apiRequiresAuthentication ? "yes" : "no") << endl;
+ out << "Dashboard requires authentication: " << (config->dashboardRequiresAuthentication ? "yes" : "no") << endl;
+ out << "Statistics require authentication: " << (config->statsRequireAuthentication ? "yes" : "no") << endl;
+ out << "Password: " << (config->password ? "set" : "unset") << endl;
+ out << "API key: " << (config->apiKey ? "set" : "unset") << endl;
+ }
+ out << "API writable: " << (g_apiReadWrite ? "yes" : "no") << endl;
+ out << "API configuration directory: " << g_apiConfigDirectory << endl;
+ out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl;
+
+ return out.str();
+}
+
+class WebClientConnection
+{
+public:
+ WebClientConnection(const ComboAddress& client, int socketDesc) :
+ d_client(client), d_socket(socketDesc)
+ {
+ if (!s_connManager.registerConnection()) {
+ throw std::runtime_error("Too many concurrent web client connections");
+ }
+ }
+ WebClientConnection(WebClientConnection&& rhs) noexcept :
+ d_client(rhs.d_client), d_socket(std::move(rhs.d_socket))
+ {
+ }
+ WebClientConnection(const WebClientConnection&) = delete;
+ WebClientConnection& operator=(const WebClientConnection&) = delete;
+ WebClientConnection& operator=(WebClientConnection&& rhs) noexcept
+ {
+ d_client = rhs.d_client;
+ d_socket = std::move(rhs.d_socket);
+ return *this;
+ }
+
+ ~WebClientConnection()
+ {
+ if (d_socket.getHandle() != -1) {
+ s_connManager.releaseConnection();
+ }
+ }
+
+ [[nodiscard]] const Socket& getSocket() const
+ {
+ return d_socket;
+ }
+
+ [[nodiscard]] const ComboAddress& getClient() const
+ {
+ return d_client;
+ }
+
+private:
+ ComboAddress d_client;
+ Socket d_socket;
+};
+
+#ifndef DISABLE_PROMETHEUS
+static MetricDefinitionStorage s_metricDefinitions;
+
+std::map<std::string, MetricDefinition> MetricDefinitionStorage::metrics{
+ {"responses", MetricDefinition(PrometheusMetricType::counter, "Number of responses received from backends")},
+ {"servfail-responses", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received from backends")},
+ {"queries", MetricDefinition(PrometheusMetricType::counter, "Number of received queries")},
+ {"frontend-nxdomain", MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers sent to clients")},
+ {"frontend-servfail", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers sent to clients")},
+ {"frontend-noerror", MetricDefinition(PrometheusMetricType::counter, "Number of NoError answers sent to clients")},
+ {"acl-drops", MetricDefinition(PrometheusMetricType::counter, "Number of packets dropped because of the ACL")},
+ {"rule-drop", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a rule")},
+ {"rule-nxdomain", MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers returned because of a rule")},
+ {"rule-refused", MetricDefinition(PrometheusMetricType::counter, "Number of Refused answers returned because of a rule")},
+ {"rule-servfail", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received because of a rule")},
+ {"rule-truncated", MetricDefinition(PrometheusMetricType::counter, "Number of truncated answers returned because of a rule")},
+ {"self-answered", MetricDefinition(PrometheusMetricType::counter, "Number of self-answered responses")},
+ {"downstream-timeouts", MetricDefinition(PrometheusMetricType::counter, "Number of queries not answered in time by a backend")},
+ {"downstream-send-errors", MetricDefinition(PrometheusMetricType::counter, "Number of errors when sending a query to a backend")},
+ {"trunc-failures", MetricDefinition(PrometheusMetricType::counter, "Number of errors encountered while truncating an answer")},
+ {"no-policy", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because no server was available")},
+ {"latency0-1", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in less than 1ms")},
+ {"latency1-10", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 1-10 ms")},
+ {"latency10-50", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 10-50 ms")},
+ {"latency50-100", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 50-100 ms")},
+ {"latency100-1000", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 100-1000 ms")},
+ {"latency-slow", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in more than 1 second")},
+ {"latency-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 100 packets")},
+ {"latency-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 1000 packets")},
+ {"latency-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 10000 packets")},
+ {"latency-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 1000000 packets")},
+ {"latency-tcp-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over TCP")},
+ {"latency-tcp-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over TCP")},
+ {"latency-tcp-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over TCP")},
+ {"latency-tcp-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over TCP")},
+ {"latency-dot-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoT")},
+ {"latency-dot-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoT")},
+ {"latency-dot-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoT")},
+ {"latency-dot-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoT")},
+ {"latency-doh-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoH")},
+ {"latency-doh-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoH")},
+ {"latency-doh-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoH")},
+ {"latency-doh-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoH")},
+ {"latency-doq-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoQ")},
+ {"latency-doq-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoQ")},
+ {"latency-doq-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoQ")},
+ {"latency-doq-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoQ")},
+ {"uptime", MetricDefinition(PrometheusMetricType::gauge, "Uptime of the dnsdist process in seconds")},
+ {"real-memory-usage", MetricDefinition(PrometheusMetricType::gauge, "Current memory usage in bytes")},
+ {"noncompliant-queries", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped as non-compliant")},
+ {"noncompliant-responses", MetricDefinition(PrometheusMetricType::counter, "Number of answers from a backend dropped as non-compliant")},
+ {"rdqueries", MetricDefinition(PrometheusMetricType::counter, "Number of received queries with the recursion desired bit set")},
+ {"empty-queries", MetricDefinition(PrometheusMetricType::counter, "Number of empty queries received from clients")},
+ {"cache-hits", MetricDefinition(PrometheusMetricType::counter, "Number of times an answer was retrieved from cache")},
+ {"cache-misses", MetricDefinition(PrometheusMetricType::counter, "Number of times an answer not found in the cache")},
+ {"cpu-iowait", MetricDefinition(PrometheusMetricType::counter, "Time waiting for I/O to complete by the whole system, in units of USER_HZ")},
+ {"cpu-user-msec", MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the user state")},
+ {"cpu-steal", MetricDefinition(PrometheusMetricType::counter, "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ")},
+ {"cpu-sys-msec", MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the system state")},
+ {"fd-usage", MetricDefinition(PrometheusMetricType::gauge, "Number of currently used file descriptors")},
+ {"dyn-blocked", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a dynamic block")},
+ {"dyn-block-nmg-size", MetricDefinition(PrometheusMetricType::gauge, "Number of dynamic blocks entries")},
+ {"security-status", MetricDefinition(PrometheusMetricType::gauge, "Security status of this software. 0=unknown, 1=OK, 2=upgrade recommended, 3=upgrade mandatory")},
+ {"doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of DoH queries dropped because the internal pipe used to distribute queries was full")},
+ {"doh-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of DoH responses dropped because the internal pipe used to distribute responses was full")},
+ {"outgoing-doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of outgoing DoH queries dropped because the internal pipe used to distribute queries was full")},
+ {"tcp-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP queries dropped because the internal pipe used to distribute queries was full")},
+ {"tcp-cross-protocol-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol queries dropped because the internal pipe used to distribute queries was full")},
+ {"tcp-cross-protocol-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol responses dropped because the internal pipe used to distribute queries was full")},
+ {"udp-in-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InErrors")},
+ {"udp-noport-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp NoPorts")},
+ {"udp-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp RcvbufErrors")},
+ {"udp-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp SndbufErrors")},
+ {"udp-in-csum-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InCsumErrors")},
+ {"udp6-in-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InErrors")},
+ {"udp6-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6RcvbufErrors")},
+ {"udp6-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6SndbufErrors")},
+ {"udp6-noport-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6NoPorts")},
+ {"udp6-in-csum-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InCsumErrors")},
+ {"tcp-listen-overflows", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/netstat ListenOverflows")},
+ {"proxy-protocol-invalid", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of an invalid Proxy Protocol header")},
+};
+#endif /* DISABLE_PROMETHEUS */
+
+bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def)
+{
+#ifndef DISABLE_PROMETHEUS
+ return MetricDefinitionStorage::addMetricDefinition(def);
+#else
+ return true;
+#endif /* DISABLE_PROMETHEUS */
+}
+
+#ifndef DISABLE_WEB_CONFIG
+static bool apiWriteConfigFile(const string& filebasename, const string& content)
+{
+ if (!g_apiReadWrite) {
+ warnlog("Not writing content to %s since the API is read-only", filebasename);
+ return false;
+ }
+
+ if (g_apiConfigDirectory.empty()) {
+ vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename);
+ return false;
+ }
+
+ string filename = g_apiConfigDirectory + "/" + filebasename + ".conf";
+ ofstream ofconf(filename.c_str());
+ if (!ofconf) {
+ errlog("Could not open configuration fragment file '%s' for writing: %s", filename, stringerror());
+ return false;
+ }
+ ofconf << "-- Generated by the REST API, DO NOT EDIT" << endl;
+ ofconf << content << endl;
+ ofconf.close();
+ return true;
+}
+
+static void apiSaveACL(const NetmaskGroup& nmg)
+{
+ auto aclEntries = nmg.toStringVector();
+
+ string acl;
+ for (const auto& entry : aclEntries) {
+ if (!acl.empty()) {
+ acl += ", ";
+ }
+ acl += "\"" + entry + "\"";
+ }
+
+ string content = "setACL({" + acl + "})";
+ apiWriteConfigFile("acl", content);
+}
+#endif /* DISABLE_WEB_CONFIG */
+
+static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& apiKey)
+{
+ if (!apiKey) {
+ return false;
+ }
+
+ const auto header = req.headers.find("x-api-key");
+ if (header != req.headers.end()) {
+ return apiKey->matches(header->second);
+ }
+
+ return false;
+}
+
+static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& password, bool dashboardRequiresAuthentication)
+{
+ if (!dashboardRequiresAuthentication) {
+ return true;
+ }
+
+ static const std::array<char, 7> basicStr{'b', 'a', 's', 'i', 'c', ' ', '\0'};
+
+ const auto header = req.headers.find("authorization");
+
+ if (header != req.headers.end() && toLower(header->second).find(basicStr.data()) == 0) {
+ string cookie = header->second.substr(basicStr.size() - 1);
+
+ string plain;
+ B64Decode(cookie, plain);
+
+ vector<string> cparts;
+ stringtok(cparts, plain, ":");
+
+ if (cparts.size() == 2) {
+ if (password) {
+ return password->matches(cparts.at(1));
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool isAnAPIRequest(const YaHTTP::Request& req)
+{
+ return req.url.path.find("/api/") == 0;
+}
+
+static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request& req)
+{
+ return req.url.path == "/api/v1/servers/localhost";
+}
+
+static bool isAStatsRequest(const YaHTTP::Request& req)
+{
+ return req.url.path == "/jsonstat" || req.url.path == "/metrics";
+}
+
+static bool handleAuthorization(const YaHTTP::Request& req)
+{
+ auto config = g_webserverConfig.lock();
+
+ if (isAStatsRequest(req)) {
+ if (config->statsRequireAuthentication) {
+ /* Access to the stats is allowed for both API and Web users */
+ return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+ }
+ return true;
+ }
+
+ if (isAnAPIRequest(req)) {
+ /* Access to the API requires a valid API key */
+ if (!config->apiRequiresAuthentication || checkAPIKey(req, config->apiKey)) {
+ return true;
+ }
+
+ return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+ }
+
+ return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+}
+
+static bool isMethodAllowed(const YaHTTP::Request& req)
+{
+ if (req.method == "GET") {
+ return true;
+ }
+ if (req.method == "PUT" && g_apiReadWrite) {
+ if (req.url.path == "/api/v1/servers/localhost/config/allow-from") {
+ return true;
+ }
+ }
+#ifndef DISABLE_WEB_CACHE_MANAGEMENT
+ if (req.method == "DELETE") {
+ if (req.url.path == "/api/v1/cache") {
+ return true;
+ }
+ }
+#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
+ return false;
+}
+
+static bool isClientAllowedByACL(const ComboAddress& remote)
+{
+ return g_webserverConfig.lock()->acl.match(remote);
+}
+
+static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ const auto origin = req.headers.find("Origin");
+ if (origin != req.headers.end()) {
+ if (req.method == "OPTIONS") {
+ /* Pre-flight request */
+ if (g_apiReadWrite) {
+ resp.headers["Access-Control-Allow-Methods"] = "GET, PUT";
+ }
+ else {
+ resp.headers["Access-Control-Allow-Methods"] = "GET";
+ }
+ resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
+ }
+
+ resp.headers["Access-Control-Allow-Origin"] = origin->second;
+
+ if (isAStatsRequest(req) || isAnAPIRequestAllowedWithWebAuth(req)) {
+ resp.headers["Access-Control-Allow-Credentials"] = "true";
+ }
+ }
+}
+
+static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
+{
+ static const std::vector<std::pair<std::string, std::string>> headers = {
+ {"X-Content-Type-Options", "nosniff"},
+ {"X-Frame-Options", "deny"},
+ {"X-Permitted-Cross-Domain-Policies", "none"},
+ {"X-XSS-Protection", "1; mode=block"},
+ {"Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'"},
+ };
+
+ for (const auto& header : headers) {
+ if (customHeaders) {
+ const auto& custom = customHeaders->find(header.first);
+ if (custom != customHeaders->end()) {
+ continue;
+ }
+ }
+ resp.headers[header.first] = header.second;
+ }
+}
+
+static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
+{
+ if (!customHeaders) {
+ return;
+ }
+
+ for (const auto& custom : *customHeaders) {
+ if (!custom.second.empty()) {
+ resp.headers[custom.first] = custom.second;
+ }
+ }
+}
+
+template <typename T>
+static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>* someResponseRules)
+{
+ using namespace json11;
+ Json::array responseRules;
+ int num = 0;
+ auto localResponseRules = someResponseRules->getLocal();
+ responseRules.reserve(localResponseRules->size());
+ for (const auto& rule : *localResponseRules) {
+ responseRules.emplace_back(Json::object{
+ {"id", num++},
+ {"creationOrder", static_cast<double>(rule.d_creationOrder)},
+ {"uuid", boost::uuids::to_string(rule.d_id)},
+ {"name", rule.d_name},
+ {"matches", static_cast<double>(rule.d_rule->d_matches)},
+ {"rule", rule.d_rule->toString()},
+ {"action", rule.d_action->toString()},
+ });
+ }
+ return responseRules;
+}
+
+#ifndef DISABLE_PROMETHEUS
+template <typename T>
+static void addRulesToPrometheusOutput(std::ostringstream& output, GlobalStateHolder<vector<T>>& rules)
+{
+ auto localRules = rules.getLocal();
+ for (const auto& entry : *localRules) {
+ std::string identifier = !entry.d_name.empty() ? entry.d_name : boost::uuids::to_string(entry.d_id);
+ output << "dnsdist_rule_hits{id=\"" << identifier << "\"} " << entry.d_rule->d_matches << "\n";
+ }
+}
+
+static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+ resp.status = 200;
+
+ std::ostringstream output;
+ static const std::set<std::string> metricBlacklist = {"special-memory-usage", "latency-count", "latency-sum"};
+ {
+ auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+ for (const auto& entry : *entries) {
+ const auto& metricName = entry.d_name;
+
+ if (metricBlacklist.count(metricName) != 0) {
+ continue;
+ }
+
+ MetricDefinition metricDetails;
+ if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
+ vinfolog("Do not have metric details for %s", metricName);
+ continue;
+ }
+
+ const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
+ if (prometheusTypeName.empty()) {
+ vinfolog("Unknown Prometheus type for %s", metricName);
+ continue;
+ }
+
+ // Prometheus suggest using '_' instead of '-'
+ std::string prometheusMetricName;
+ if (metricDetails.customName.empty()) {
+ prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
+ }
+ else {
+ prometheusMetricName = metricDetails.customName;
+ }
+
+ // for these we have the help and types encoded in the sources
+ // but we need to be careful about labels in custom metrics
+ std::string helpName = prometheusMetricName.substr(0, prometheusMetricName.find('{'));
+ output << "# HELP " << helpName << " " << metricDetails.description << "\n";
+ output << "# TYPE " << helpName << " " << prometheusTypeName << "\n";
+ output << prometheusMetricName << " ";
+
+ if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+ output << (*val)->load();
+ }
+ else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+ output << (*adval)->load();
+ }
+ else if (const auto& dval = std::get_if<double*>(&entry.d_value)) {
+ output << **dval;
+ }
+ else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
+ output << (*func)(entry.d_name);
+ }
+
+ output << "\n";
+ }
+ }
+
+ // Latency histogram buckets
+ output << "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n";
+ output << "# TYPE dnsdist_latency histogram\n";
+ uint64_t latency_amounts = dnsdist::metrics::g_stats.latency0_1;
+ output << "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts << "\n";
+ latency_amounts += dnsdist::metrics::g_stats.latency1_10;
+ output << "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts << "\n";
+ latency_amounts += dnsdist::metrics::g_stats.latency10_50;
+ output << "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts << "\n";
+ latency_amounts += dnsdist::metrics::g_stats.latency50_100;
+ output << "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts << "\n";
+ latency_amounts += dnsdist::metrics::g_stats.latency100_1000;
+ output << "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts << "\n";
+ latency_amounts += dnsdist::metrics::g_stats.latencySlow; // Should be the same as latency_count
+ output << "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts << "\n";
+ output << "dnsdist_latency_sum " << dnsdist::metrics::g_stats.latencySum << "\n";
+ output << "dnsdist_latency_count " << dnsdist::metrics::g_stats.latencyCount << "\n";
+
+ auto states = g_dstates.getLocal();
+ const string statesbase = "dnsdist_server_";
+
+ // clang-format off
+ output << "# HELP " << statesbase << "status " << "Whether this backend is up (1) or down (0)" << "\n";
+ output << "# TYPE " << statesbase << "status " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "queries " << "Amount of queries relayed to server" << "\n";
+ output << "# TYPE " << statesbase << "queries " << "counter" << "\n";
+ output << "# HELP " << statesbase << "responses " << "Amount of responses received from this server" << "\n";
+ output << "# TYPE " << statesbase << "responses " << "counter" << "\n";
+ output << "# HELP " << statesbase << "noncompliantresponses " << "Amount of non-compliant responses received from this server" << "\n";
+ output << "# TYPE " << statesbase << "noncompliantresponses " << "counter" << "\n";
+ output << "# HELP " << statesbase << "drops " << "Amount of queries not answered by server" << "\n";
+ output << "# TYPE " << statesbase << "drops " << "counter" << "\n";
+ output << "# HELP " << statesbase << "latency " << "Server's latency when answering questions in milliseconds" << "\n";
+ output << "# TYPE " << statesbase << "latency " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "senderrors " << "Total number of OS send errors while relaying queries" << "\n";
+ output << "# TYPE " << statesbase << "senderrors " << "counter" << "\n";
+ output << "# HELP " << statesbase << "outstanding " << "Current number of queries that are waiting for a backend response" << "\n";
+ output << "# TYPE " << statesbase << "outstanding " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "order " << "The order in which this server is picked" << "\n";
+ output << "# TYPE " << statesbase << "order " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "weight " << "The weight within the order in which this server is picked" << "\n";
+ output << "# TYPE " << statesbase << "weight " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "tcpdiedsendingquery " << "The number of TCP I/O errors while sending the query" << "\n";
+ output << "# TYPE " << statesbase << "tcpdiedsendingquery " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpdiedreadingresponse " << "The number of TCP I/O errors while reading the response" << "\n";
+ output << "# TYPE " << statesbase << "tcpdiedreadingresponse " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpgaveup " << "The number of TCP connections failing after too many attempts" << "\n";
+ output << "# TYPE " << statesbase << "tcpgaveup " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpconnecttimeouts " << "The number of TCP connect timeouts" << "\n";
+ output << "# TYPE " << statesbase << "tcpconnecttimeouts " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpreadtimeouts " << "The number of TCP read timeouts" << "\n";
+ output << "# TYPE " << statesbase << "tcpreadtimeouts " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpwritetimeouts " << "The number of TCP write timeouts" << "\n";
+ output << "# TYPE " << statesbase << "tcpwritetimeouts " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpcurrentconnections " << "The number of current TCP connections" << "\n";
+ output << "# TYPE " << statesbase << "tcpcurrentconnections " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "tcpmaxconcurrentconnections " << "The maximum number of concurrent TCP connections" << "\n";
+ output << "# TYPE " << statesbase << "tcpmaxconcurrentconnections " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcptoomanyconcurrentconnections " << "Number of times we had to enforce the maximum number of concurrent TCP connections" << "\n";
+ output << "# TYPE " << statesbase << "tcptoomanyconcurrentconnections " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpnewconnections " << "The number of established TCP connections in total" << "\n";
+ output << "# TYPE " << statesbase << "tcpnewconnections " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpreusedconnections " << "The number of times a TCP connection has been reused" << "\n";
+ output << "# TYPE " << statesbase << "tcpreusedconnections " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcpavgqueriesperconn " << "The average number of queries per TCP connection" << "\n";
+ output << "# TYPE " << statesbase << "tcpavgqueriesperconn " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "tcpavgconnduration " << "The average duration of a TCP connection (ms)" << "\n";
+ output << "# TYPE " << statesbase << "tcpavgconnduration " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "tlsresumptions " << "The number of times a TLS session has been resumed" << "\n";
+ output << "# TYPE " << statesbase << "tlsresumptions " << "counter" << "\n";
+ output << "# HELP " << statesbase << "tcplatency " << "Server's latency when answering TCP questions in milliseconds" << "\n";
+ output << "# TYPE " << statesbase << "tcplatency " << "gauge" << "\n";
+ output << "# HELP " << statesbase << "healthcheckfailures " << "Number of health check attempts that failed (total)" << "\n";
+ output << "# TYPE " << statesbase << "healthcheckfailures " << "counter" << "\n";
+ output << "# HELP " << statesbase << "healthcheckfailuresparsing " << "Number of health check attempts where the response could not be parsed" << "\n";
+ output << "# TYPE " << statesbase << "healthcheckfailuresparsing " << "counter" << "\n";
+ output << "# HELP " << statesbase << "healthcheckfailurestimeout " << "Number of health check attempts where the response did not arrive in time" << "\n";
+ output << "# TYPE " << statesbase << "healthcheckfailurestimeout " << "counter" << "\n";
+ output << "# HELP " << statesbase << "healthcheckfailuresnetwork " << "Number of health check attempts that experienced a network issue" << "\n";
+ output << "# TYPE " << statesbase << "healthcheckfailuresnetwork " << "counter" << "\n";
+ output << "# HELP " << statesbase << "healthcheckfailuresmismatch " << "Number of health check attempts where the response did not match the query" << "\n";
+ output << "# TYPE " << statesbase << "healthcheckfailuresmismatch " << "counter" << "\n";
+ output << "# HELP " << statesbase << "healthcheckfailuresinvalid " << "Number of health check attempts where the DNS response was invalid" << "\n";
+ output << "# TYPE " << statesbase << "healthcheckfailuresinvalid " << "counter" << "\n";
+
+ for (const auto& state : *states) {
+ string serverName;
+
+ if (state->getName().empty()) {
+ serverName = state->d_config.remote.toStringWithPort();
+ }
+ else {
+ serverName = state->getName();
+ }
+
+ boost::replace_all(serverName, ".", "_");
+
+ const std::string label = boost::str(boost::format(R"({server="%1%",address="%2%"})")
+ % serverName % state->d_config.remote.toStringWithPort());
+
+ output << statesbase << "status" << label << " " << (state->isUp() ? "1" : "0") << "\n";
+ output << statesbase << "queries" << label << " " << state->queries.load() << "\n";
+ output << statesbase << "responses" << label << " " << state->responses.load() << "\n";
+ output << statesbase << "noncompliantresponses" << label << " " << state->nonCompliantResponses.load() << "\n";
+ output << statesbase << "drops" << label << " " << state->reuseds.load() << "\n";
+ if (state->isUp()) {
+ output << statesbase << "latency" << label << " " << state->latencyUsec/1000.0 << "\n";
+ output << statesbase << "tcplatency" << label << " " << state->latencyUsecTCP/1000.0 << "\n";
+ }
+ output << statesbase << "senderrors" << label << " " << state->sendErrors.load() << "\n";
+ output << statesbase << "outstanding" << label << " " << state->outstanding.load() << "\n";
+ output << statesbase << "order" << label << " " << state->d_config.order << "\n";
+ output << statesbase << "weight" << label << " " << state->d_config.d_weight << "\n";
+ output << statesbase << "tcpdiedsendingquery" << label << " " << state->tcpDiedSendingQuery << "\n";
+ output << statesbase << "tcpdiedreadingresponse" << label << " " << state->tcpDiedReadingResponse << "\n";
+ output << statesbase << "tcpgaveup" << label << " " << state->tcpGaveUp << "\n";
+ output << statesbase << "tcpreadtimeouts" << label << " " << state->tcpReadTimeouts << "\n";
+ output << statesbase << "tcpwritetimeouts" << label << " " << state->tcpWriteTimeouts << "\n";
+ output << statesbase << "tcpconnecttimeouts" << label << " " << state->tcpConnectTimeouts << "\n";
+ output << statesbase << "tcpcurrentconnections" << label << " " << state->tcpCurrentConnections << "\n";
+ output << statesbase << "tcpmaxconcurrentconnections" << label << " " << state->tcpMaxConcurrentConnections << "\n";
+ output << statesbase << "tcptoomanyconcurrentconnections" << label << " " << state->tcpTooManyConcurrentConnections << "\n";
+ output << statesbase << "tcpnewconnections" << label << " " << state->tcpNewConnections << "\n";
+ output << statesbase << "tcpreusedconnections" << label << " " << state->tcpReusedConnections << "\n";
+ output << statesbase << "tcpavgqueriesperconn" << label << " " << state->tcpAvgQueriesPerConnection << "\n";
+ output << statesbase << "tcpavgconnduration" << label << " " << state->tcpAvgConnectionDuration << "\n";
+ output << statesbase << "tlsresumptions" << label << " " << state->tlsResumptions << "\n";
+ output << statesbase << "healthcheckfailures" << label << " " << state->d_healthCheckMetrics.d_failures << "\n";
+ output << statesbase << "healthcheckfailuresparsing" << label << " " << state->d_healthCheckMetrics.d_parseErrors << "\n";
+ output << statesbase << "healthcheckfailurestimeout" << label << " " << state->d_healthCheckMetrics.d_timeOuts << "\n";
+ output << statesbase << "healthcheckfailuresnetwork" << label << " " << state->d_healthCheckMetrics.d_networkErrors << "\n";
+ output << statesbase << "healthcheckfailuresmismatch" << label << " " << state->d_healthCheckMetrics.d_mismatchErrors << "\n";
+ output << statesbase << "healthcheckfailuresinvalid" << label << " " << state->d_healthCheckMetrics.d_invalidResponseErrors << "\n";
+ }
+
+ const string frontsbase = "dnsdist_frontend_";
+ output << "# HELP " << frontsbase << "queries " << "Amount of queries received by this frontend" << "\n";
+ output << "# TYPE " << frontsbase << "queries " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "noncompliantqueries " << "Amount of non-compliant queries received by this frontend" << "\n";
+ output << "# TYPE " << frontsbase << "noncompliantqueries " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "responses " << "Amount of responses sent by this frontend" << "\n";
+ output << "# TYPE " << frontsbase << "responses " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n";
+ output << "# TYPE " << frontsbase << "tcpdiedreadingquery " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n";
+ output << "# TYPE " << frontsbase << "tcpdiedsendingresponse " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tcpgaveup " << "Amount of TCP connections terminated after too many attempts to get a connection to the backend" << "\n";
+ output << "# TYPE " << frontsbase << "tcpgaveup " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tcpclienttimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n";
+ output << "# TYPE " << frontsbase << "tcpclienttimeouts " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tcpdownstreamtimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n";
+ output << "# TYPE " << frontsbase << "tcpdownstreamtimeouts " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n";
+ output << "# TYPE " << frontsbase << "tcpcurrentconnections " << "gauge" << "\n";
+ output << "# HELP " << frontsbase << "tcpmaxconcurrentconnections " << "Maximum number of concurrent incoming TCP connections from clients" << "\n";
+ output << "# TYPE " << frontsbase << "tcpmaxconcurrentconnections " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n";
+ output << "# TYPE " << frontsbase << "tcpavgqueriesperconnection " << "gauge" << "\n";
+ output << "# HELP " << frontsbase << "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n";
+ output << "# TYPE " << frontsbase << "tcpavgconnectionduration " << "gauge" << "\n";
+ output << "# HELP " << frontsbase << "tlsqueries " << "Number of queries received by dnsdist over TLS, by TLS version" << "\n";
+ output << "# TYPE " << frontsbase << "tlsqueries " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tlsnewsessions " << "Amount of new TLS sessions negotiated" << "\n";
+ output << "# TYPE " << frontsbase << "tlsnewsessions " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tlsresumptions " << "Amount of TLS sessions resumed" << "\n";
+ output << "# TYPE " << frontsbase << "tlsresumptions " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tlsunknownticketkeys " << "Amount of attempts to resume TLS session from an unknown key (possibly expired)" << "\n";
+ output << "# TYPE " << frontsbase << "tlsunknownticketkeys " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n";
+ output << "# TYPE " << frontsbase << "tlsinactiveticketkeys " << "counter" << "\n";
+ output << "# HELP " << frontsbase << "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n";
+ output << "# TYPE " << frontsbase << "tlshandshakefailures " << "counter" << "\n";
+
+ std::map<std::string,uint64_t> frontendDuplicates;
+ for (const auto& front : g_frontends) {
+ if (front->udpFD == -1 && front->tcpFD == -1) {
+ continue;
+ }
+
+ const string frontName = front->local.toStringWithPort();
+ const string proto = front->getType();
+ string fullName = frontName;
+ fullName += "_";
+ fullName += proto;
+ uint64_t threadNumber = 0;
+ auto dupPair = frontendDuplicates.emplace(fullName, 1);
+ if (!dupPair.second) {
+ threadNumber = dupPair.first->second;
+ ++(dupPair.first->second);
+ }
+ const std::string label = boost::str(boost::format(R"({frontend="%1%",proto="%2%",thread="%3%"} )")
+ % frontName % proto % threadNumber);
+
+ output << frontsbase << "queries" << label << front->queries.load() << "\n";
+ output << frontsbase << "noncompliantqueries" << label << front->nonCompliantQueries.load() << "\n";
+ output << frontsbase << "responses" << label << front->responses.load() << "\n";
+ if (front->isTCP()) {
+ output << frontsbase << "tcpdiedreadingquery" << label << front->tcpDiedReadingQuery.load() << "\n";
+ output << frontsbase << "tcpdiedsendingresponse" << label << front->tcpDiedSendingResponse.load() << "\n";
+ output << frontsbase << "tcpgaveup" << label << front->tcpGaveUp.load() << "\n";
+ output << frontsbase << "tcpclienttimeouts" << label << front->tcpClientTimeouts.load() << "\n";
+ output << frontsbase << "tcpdownstreamtimeouts" << label << front->tcpDownstreamTimeouts.load() << "\n";
+ output << frontsbase << "tcpcurrentconnections" << label << front->tcpCurrentConnections.load() << "\n";
+ output << frontsbase << "tcpmaxconcurrentconnections" << label << front->tcpMaxConcurrentConnections.load() << "\n";
+ output << frontsbase << "tcpavgqueriesperconnection" << label << front->tcpAvgQueriesPerConnection.load() << "\n";
+ output << frontsbase << "tcpavgconnectionduration" << label << front->tcpAvgConnectionDuration.load() << "\n";
+ if (front->hasTLS()) {
+ output << frontsbase << "tlsnewsessions" << label << front->tlsNewSessions.load() << "\n";
+ output << frontsbase << "tlsresumptions" << label << front->tlsResumptions.load() << "\n";
+ output << frontsbase << "tlsunknownticketkeys" << label << front->tlsUnknownTicketKey.load() << "\n";
+ output << frontsbase << "tlsinactiveticketkeys" << label << front->tlsInactiveTicketKey.load() << "\n";
+
+ output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls10"} )" << front->tls10queries.load() << "\n";
+ output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls11"} )" << front->tls11queries.load() << "\n";
+ output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls12"} )" << front->tls12queries.load() << "\n";
+ output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls13"} )" << front->tls13queries.load() << "\n";
+ output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="unknown"} )" << front->tlsUnknownqueries.load() << "\n";
+
+ const TLSErrorCounters* errorCounters = nullptr;
+ if (front->tlsFrontend != nullptr) {
+ errorCounters = &front->tlsFrontend->d_tlsCounters;
+ }
+ else if (front->dohFrontend != nullptr) {
+ errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters;
+ }
+
+ if (errorCounters != nullptr) {
+ output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="dhKeyTooSmall"} )" << errorCounters->d_dhKeyTooSmall << "\n";
+ output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="inappropriateFallBack"} )" << errorCounters->d_inappropriateFallBack << "\n";
+ output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="noSharedCipher"} )" << errorCounters->d_noSharedCipher << "\n";
+ output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownCipherType"} )" << errorCounters->d_unknownCipherType << "\n";
+ output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownKeyExchangeType"} )" << errorCounters->d_unknownKeyExchangeType << "\n";
+ output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownProtocol"} )" << errorCounters->d_unknownProtocol << "\n";
+ output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unsupportedEC"} )" << errorCounters->d_unsupportedEC << "\n";
+ output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unsupportedProtocol"} )" << errorCounters->d_unsupportedProtocol << "\n";
+ }
+ }
+ }
+ }
+
+ output << "# HELP " << frontsbase << "http_connects " << "Number of DoH TCP connections established to this frontend" << "\n";
+ output << "# TYPE " << frontsbase << "http_connects " << "counter" << "\n";
+
+ output << "# HELP " << frontsbase << "doh_http_method_queries " << "Number of DoH queries received by dnsdist, by HTTP method" << "\n";
+ output << "# TYPE " << frontsbase << "doh_http_method_queries " << "counter" << "\n";
+
+ output << "# HELP " << frontsbase << "doh_http_version_queries " << "Number of DoH queries received by dnsdist, by HTTP version" << "\n";
+ output << "# TYPE " << frontsbase << "doh_http_version_queries " << "counter" << "\n";
+
+ output << "# HELP " << frontsbase << "doh_bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n";
+ output << "# TYPE " << frontsbase << "doh_bad_requests " << "counter" << "\n";
+
+ output << "# HELP " << frontsbase << "doh_responses " << "Number of responses sent, by type" << "\n";
+ output << "# TYPE " << frontsbase << "doh_responses " << "counter" << "\n";
+
+ output << "# HELP " << frontsbase << "doh_version_status_responses " << "Number of requests that could not be converted to a DNS query" << "\n";
+ output << "# TYPE " << frontsbase << "doh_version_status_responses " << "counter" << "\n";
+
+#ifdef HAVE_DNS_OVER_HTTPS
+ std::map<std::string,uint64_t> dohFrontendDuplicates;
+ for(const auto& doh : g_dohlocals) {
+ const string frontName = doh->d_tlsContext.d_addr.toStringWithPort();
+ uint64_t threadNumber = 0;
+ auto dupPair = frontendDuplicates.emplace(frontName, 1);
+ if (!dupPair.second) {
+ threadNumber = dupPair.first->second;
+ ++(dupPair.first->second);
+ }
+ const std::string addrlabel = boost::str(boost::format(R"(frontend="%1%",thread="%2%")") % frontName % threadNumber);
+ const std::string label = "{" + addrlabel + "} ";
+
+ output << frontsbase << "http_connects" << label << doh->d_httpconnects << "\n";
+ output << frontsbase << "doh_http_method_queries{method=\"get\"," << addrlabel << "} " << doh->d_getqueries << "\n";
+ output << frontsbase << "doh_http_method_queries{method=\"post\"," << addrlabel << "} " << doh->d_postqueries << "\n";
+
+ output << frontsbase << "doh_http_version_queries{version=\"1\"," << addrlabel << "} " << doh->d_http1Stats.d_nbQueries << "\n";
+ output << frontsbase << "doh_http_version_queries{version=\"2\"," << addrlabel << "} " << doh->d_http2Stats.d_nbQueries << "\n";
+
+ output << frontsbase << "doh_bad_requests{" << addrlabel << "} " << doh->d_badrequests << "\n";
+
+ output << frontsbase << "doh_responses{type=\"error\"," << addrlabel << "} " << doh->d_errorresponses << "\n";
+ output << frontsbase << "doh_responses{type=\"redirect\"," << addrlabel << "} " << doh->d_redirectresponses << "\n";
+ output << frontsbase << "doh_responses{type=\"valid\"," << addrlabel << "} " << doh->d_validresponses << "\n";
+
+ output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="200",)" << addrlabel << "} " << doh->d_http1Stats.d_nb200Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="400",)" << addrlabel << "} " << doh->d_http1Stats.d_nb400Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="403",)" << addrlabel << "} " << doh->d_http1Stats.d_nb403Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="500",)" << addrlabel << "} " << doh->d_http1Stats.d_nb500Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="502",)" << addrlabel << "} " << doh->d_http1Stats.d_nb502Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="other",)" << addrlabel << "} " << doh->d_http1Stats.d_nbOtherResponses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="200",)" << addrlabel << "} " << doh->d_http2Stats.d_nb200Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="400",)" << addrlabel << "} " << doh->d_http2Stats.d_nb400Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="403",)" << addrlabel << "} " << doh->d_http2Stats.d_nb403Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="500",)" << addrlabel << "} " << doh->d_http2Stats.d_nb500Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="502",)" << addrlabel << "} " << doh->d_http2Stats.d_nb502Responses << "\n";
+ output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="other",)" << addrlabel << "} " << doh->d_http2Stats.d_nbOtherResponses << "\n";
+ }
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+ auto localPools = g_pools.getLocal();
+ const string cachebase = "dnsdist_pool_";
+ output << "# HELP dnsdist_pool_servers " << "Number of servers in that pool" << "\n";
+ output << "# TYPE dnsdist_pool_servers " << "gauge" << "\n";
+ output << "# HELP dnsdist_pool_active_servers " << "Number of available servers in that pool" << "\n";
+ output << "# TYPE dnsdist_pool_active_servers " << "gauge" << "\n";
+
+ output << "# HELP dnsdist_pool_cache_size " << "Maximum number of entries that this cache can hold" << "\n";
+ output << "# TYPE dnsdist_pool_cache_size " << "gauge" << "\n";
+ output << "# HELP dnsdist_pool_cache_entries " << "Number of entries currently present in that cache" << "\n";
+ output << "# TYPE dnsdist_pool_cache_entries " << "gauge" << "\n";
+ output << "# HELP dnsdist_pool_cache_hits " << "Number of hits from that cache" << "\n";
+ output << "# TYPE dnsdist_pool_cache_hits " << "counter" << "\n";
+ output << "# HELP dnsdist_pool_cache_misses " << "Number of misses from that cache" << "\n";
+ output << "# TYPE dnsdist_pool_cache_misses " << "counter" << "\n";
+ output << "# HELP dnsdist_pool_cache_deferred_inserts " << "Number of insertions into that cache skipped because it was already locked" << "\n";
+ output << "# TYPE dnsdist_pool_cache_deferred_inserts " << "counter" << "\n";
+ output << "# HELP dnsdist_pool_cache_deferred_lookups " << "Number of lookups into that cache skipped because it was already locked" << "\n";
+ output << "# TYPE dnsdist_pool_cache_deferred_lookups " << "counter" << "\n";
+ output << "# HELP dnsdist_pool_cache_lookup_collisions " << "Number of lookups into that cache that triggered a collision (same hash but different entry)" << "\n";
+ output << "# TYPE dnsdist_pool_cache_lookup_collisions " << "counter" << "\n";
+ output << "# HELP dnsdist_pool_cache_insert_collisions " << "Number of insertions into that cache that triggered a collision (same hash but different entry)" << "\n";
+ output << "# TYPE dnsdist_pool_cache_insert_collisions " << "counter" << "\n";
+ output << "# HELP dnsdist_pool_cache_ttl_too_shorts " << "Number of insertions into that cache skipped because the TTL of the answer was not long enough" << "\n";
+ output << "# TYPE dnsdist_pool_cache_ttl_too_shorts " << "counter" << "\n";
+ output << "# HELP dnsdist_pool_cache_cleanup_count_total " << "Number of times the cache has been scanned to remove expired entries, if any" << "\n";
+ output << "# TYPE dnsdist_pool_cache_cleanup_count_total " << "counter" << "\n";
+
+ for (const auto& entry : *localPools) {
+ string poolName = entry.first;
+
+ if (poolName.empty()) {
+ poolName = "_default_";
+ }
+ const string label = "{pool=\"" + poolName + "\"}";
+ const std::shared_ptr<ServerPool> pool = entry.second;
+ output << "dnsdist_pool_servers" << label << " " << pool->countServers(false) << "\n";
+ output << "dnsdist_pool_active_servers" << label << " " << pool->countServers(true) << "\n";
+
+ if (pool->packetCache != nullptr) {
+ const auto& cache = pool->packetCache;
+
+ output << cachebase << "cache_size" <<label << " " << cache->getMaxEntries() << "\n";
+ output << cachebase << "cache_entries" <<label << " " << cache->getEntriesCount() << "\n";
+ output << cachebase << "cache_hits" <<label << " " << cache->getHits() << "\n";
+ output << cachebase << "cache_misses" <<label << " " << cache->getMisses() << "\n";
+ output << cachebase << "cache_deferred_inserts" <<label << " " << cache->getDeferredInserts() << "\n";
+ output << cachebase << "cache_deferred_lookups" <<label << " " << cache->getDeferredLookups() << "\n";
+ output << cachebase << "cache_lookup_collisions" <<label << " " << cache->getLookupCollisions() << "\n";
+ output << cachebase << "cache_insert_collisions" <<label << " " << cache->getInsertCollisions() << "\n";
+ output << cachebase << "cache_ttl_too_shorts" <<label << " " << cache->getTTLTooShorts() << "\n";
+ output << cachebase << "cache_cleanup_count_total" <<label << " " << cache->getCleanupCount() << "\n";
+ }
+ }
+
+ output << "# HELP dnsdist_rule_hits " << "Number of hits of that rule" << "\n";
+ output << "# TYPE dnsdist_rule_hits " << "counter" << "\n";
+ addRulesToPrometheusOutput(output, dnsdist::rules::g_ruleactions);
+ for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+ addRulesToPrometheusOutput(output, chain.holder);
+ }
+
+#ifndef DISABLE_DYNBLOCKS
+ output << "# HELP dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "Number of hits per second blocked by Dynamic Blocks (netmasks) for the top offenders, averaged over the last 60s" << "\n";
+ output << "# TYPE dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "gauge" << "\n";
+ auto topNetmasksByReason = DynBlockMaintenance::getHitsForTopNetmasks();
+ for (const auto& entry : topNetmasksByReason) {
+ for (const auto& netmask : entry.second) {
+ output << "dnsdist_dynblocks_nmg_top_offenders_hits_per_second{reason=\"" << entry.first << "\",netmask=\"" << netmask.first.toString() << "\"} " << netmask.second << "\n";
+ }
+ }
+
+ output << "# HELP dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "Number of this per second blocked by Dynamic Blocks (suffixes) for the top offenders, averaged over the last 60s" << "\n";
+ output << "# TYPE dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "gauge" << "\n";
+ auto topSuffixesByReason = DynBlockMaintenance::getHitsForTopSuffixes();
+ for (const auto& entry : topSuffixesByReason) {
+ for (const auto& suffix : entry.second) {
+ output << "dnsdist_dynblocks_smt_top_offenders_hits_per_second{reason=\"" << entry.first << "\",suffix=\"" << suffix.first.toString() << "\"} " << suffix.second << "\n";
+ }
+ }
+#endif /* DISABLE_DYNBLOCKS */
+
+ output << "# HELP dnsdist_info " << "Info from dnsdist, value is always 1" << "\n";
+ output << "# TYPE dnsdist_info " << "gauge" << "\n";
+ output << "dnsdist_info{version=\"" << VERSION << "\"} " << "1" << "\n";
+
+ resp.body = output.str();
+ resp.headers["Content-Type"] = "text/plain";
+ // clang-format on
+}
+#endif /* DISABLE_PROMETHEUS */
+
+using namespace json11;
+
+static void addStatsToJSONObject(Json::object& obj)
+{
+ auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+ for (const auto& entry : *entries) {
+ if (entry.d_name == "special-memory-usage") {
+ continue; // Too expensive for get-all
+ }
+ if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+ obj.emplace(entry.d_name, (double)(*val)->load());
+ }
+ else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+ obj.emplace(entry.d_name, (*adval)->load());
+ }
+ else if (const auto& dval = std::get_if<double*>(&entry.d_value)) {
+ obj.emplace(entry.d_name, (**dval));
+ }
+ else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
+ obj.emplace(entry.d_name, (double)(*func)(entry.d_name));
+ }
+ }
+}
+
+#ifndef DISABLE_BUILTIN_HTML
+static void handleJSONStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+ resp.status = 200;
+
+ if (req.getvars.count("command") == 0) {
+ resp.status = 404;
+ return;
+ }
+
+ const string& command = req.getvars.at("command");
+
+ if (command == "stats") {
+ auto obj = Json::object{
+ {"packetcache-hits", 0},
+ {"packetcache-misses", 0},
+ {"over-capacity-drops", 0},
+ {"too-old-drops", 0},
+ {"server-policy", g_policy.getLocal()->getName()}};
+
+ addStatsToJSONObject(obj);
+
+ Json my_json = obj;
+ resp.body = my_json.dump();
+ resp.headers["Content-Type"] = "application/json";
+ }
+ else if (command == "dynblocklist") {
+ Json::object obj;
+#ifndef DISABLE_DYNBLOCKS
+ auto nmg = g_dynblockNMG.getLocal();
+ timespec now{};
+ gettime(&now);
+ for (const auto& entry : *nmg) {
+ if (!(now < entry.second.until)) {
+ continue;
+ }
+ uint64_t counter = entry.second.blocks;
+ if (entry.second.bpf && g_defaultBPFFilter) {
+ counter += g_defaultBPFFilter->getHits(entry.first.getNetwork());
+ }
+ Json::object thing{
+ {"reason", entry.second.reason},
+ {"seconds", static_cast<double>(entry.second.until.tv_sec - now.tv_sec)},
+ {"blocks", static_cast<double>(counter)},
+ {"action", DNSAction::typeToString(entry.second.action != DNSAction::Action::None ? entry.second.action : g_dynBlockAction)},
+ {"warning", entry.second.warning},
+ {"ebpf", entry.second.bpf}};
+ obj.emplace(entry.first.toString(), thing);
+ }
+
+ auto smt = g_dynblockSMT.getLocal();
+ smt->visit([&now, &obj](const SuffixMatchTree<DynBlock>& node) {
+ if (!(now < node.d_value.until)) {
+ return;
+ }
+ string dom("empty");
+ if (!node.d_value.domain.empty()) {
+ dom = node.d_value.domain.toString();
+ }
+ Json::object thing{
+ {"reason", node.d_value.reason},
+ {"seconds", static_cast<double>(node.d_value.until.tv_sec - now.tv_sec)},
+ {"blocks", static_cast<double>(node.d_value.blocks)},
+ {"action", DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction)},
+ {"ebpf", node.d_value.bpf}};
+ obj.emplace(dom, thing);
+ });
+#endif /* DISABLE_DYNBLOCKS */
+ Json my_json = obj;
+ resp.body = my_json.dump();
+ resp.headers["Content-Type"] = "application/json";
+ }
+ else if (command == "ebpfblocklist") {
+ Json::object obj;
+#ifdef HAVE_EBPF
+ timespec now{};
+ gettime(&now);
+ for (const auto& dynbpf : g_dynBPFFilters) {
+ std::vector<std::tuple<ComboAddress, uint64_t, struct timespec>> addrStats = dynbpf->getAddrStats();
+ for (const auto& entry : addrStats) {
+ Json::object thing{
+ {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)},
+ {"blocks", (double)(std::get<1>(entry))}};
+ obj.emplace(std::get<0>(entry).toString(), thing);
+ }
+ }
+ if (g_defaultBPFFilter) {
+ auto nmg = g_dynblockNMG.getLocal();
+ for (const auto& entry : *nmg) {
+ if (!(now < entry.second.until) || !entry.second.bpf) {
+ continue;
+ }
+ uint64_t counter = entry.second.blocks + g_defaultBPFFilter->getHits(entry.first.getNetwork());
+ Json::object thing{
+ {"reason", entry.second.reason},
+ {"seconds", static_cast<double>(entry.second.until.tv_sec - now.tv_sec)},
+ {"blocks", static_cast<double>(counter)},
+ {"action", DNSAction::typeToString(entry.second.action != DNSAction::Action::None ? entry.second.action : g_dynBlockAction)},
+ {"warning", entry.second.warning},
+ };
+ obj.emplace(entry.first.toString(), thing);
+ }
+ }
+#endif /* HAVE_EBPF */
+ Json my_json = obj;
+ resp.body = my_json.dump();
+ resp.headers["Content-Type"] = "application/json";
+ }
+ else {
+ resp.status = 404;
+ }
+}
+#endif /* DISABLE_BUILTIN_HTML */
+
+static void addServerToJSON(Json::array& servers, int identifier, const std::shared_ptr<DownstreamState>& backend)
+{
+ string status;
+ if (backend->d_config.availability == DownstreamState::Availability::Up) {
+ status = "UP";
+ }
+ else if (backend->d_config.availability == DownstreamState::Availability::Down) {
+ status = "DOWN";
+ }
+ else {
+ status = (backend->upStatus ? "up" : "down");
+ }
+
+ Json::array pools;
+ pools.reserve(backend->d_config.pools.size());
+ for (const auto& pool : backend->d_config.pools) {
+ pools.emplace_back(pool);
+ }
+
+ Json::object server{
+ {"id", identifier},
+ {"name", backend->getName()},
+ {"address", backend->d_config.remote.toStringWithPort()},
+ {"state", status},
+ {"protocol", backend->getProtocol().toPrettyString()},
+ {"qps", (double)backend->queryLoad},
+ {"qpsLimit", (double)backend->qps.getRate()},
+ {"outstanding", (double)backend->outstanding},
+ {"reuseds", (double)backend->reuseds},
+ {"weight", (double)backend->d_config.d_weight},
+ {"order", (double)backend->d_config.order},
+ {"pools", std::move(pools)},
+ {"latency", (double)(backend->latencyUsec / 1000.0)},
+ {"queries", (double)backend->queries},
+ {"responses", (double)backend->responses},
+ {"nonCompliantResponses", (double)backend->nonCompliantResponses},
+ {"sendErrors", (double)backend->sendErrors},
+ {"tcpDiedSendingQuery", (double)backend->tcpDiedSendingQuery},
+ {"tcpDiedReadingResponse", (double)backend->tcpDiedReadingResponse},
+ {"tcpGaveUp", (double)backend->tcpGaveUp},
+ {"tcpConnectTimeouts", (double)backend->tcpConnectTimeouts},
+ {"tcpReadTimeouts", (double)backend->tcpReadTimeouts},
+ {"tcpWriteTimeouts", (double)backend->tcpWriteTimeouts},
+ {"tcpCurrentConnections", (double)backend->tcpCurrentConnections},
+ {"tcpMaxConcurrentConnections", (double)backend->tcpMaxConcurrentConnections},
+ {"tcpTooManyConcurrentConnections", (double)backend->tcpTooManyConcurrentConnections},
+ {"tcpNewConnections", (double)backend->tcpNewConnections},
+ {"tcpReusedConnections", (double)backend->tcpReusedConnections},
+ {"tcpAvgQueriesPerConnection", (double)backend->tcpAvgQueriesPerConnection},
+ {"tcpAvgConnectionDuration", (double)backend->tcpAvgConnectionDuration},
+ {"tlsResumptions", (double)backend->tlsResumptions},
+ {"tcpLatency", (double)(backend->latencyUsecTCP / 1000.0)},
+ {"healthCheckFailures", (double)(backend->d_healthCheckMetrics.d_failures)},
+ {"healthCheckFailuresParsing", (double)(backend->d_healthCheckMetrics.d_parseErrors)},
+ {"healthCheckFailuresTimeout", (double)(backend->d_healthCheckMetrics.d_timeOuts)},
+ {"healthCheckFailuresNetwork", (double)(backend->d_healthCheckMetrics.d_networkErrors)},
+ {"healthCheckFailuresMismatch", (double)(backend->d_healthCheckMetrics.d_mismatchErrors)},
+ {"healthCheckFailuresInvalid", (double)(backend->d_healthCheckMetrics.d_invalidResponseErrors)},
+ {"dropRate", (double)backend->dropRate}};
+
+ /* sending a latency for a DOWN server doesn't make sense */
+ if (backend->d_config.availability == DownstreamState::Availability::Down) {
+ server["latency"] = nullptr;
+ server["tcpLatency"] = nullptr;
+ }
+
+ servers.emplace_back(std::move(server));
+}
+
+static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+ resp.status = 200;
+
+ int num = 0;
+
+ Json::array servers;
+ {
+ auto localServers = g_dstates.getLocal();
+ servers.reserve(localServers->size());
+ for (const auto& server : *localServers) {
+ addServerToJSON(servers, num++, server);
+ }
+ }
+
+ Json::array frontends;
+ num = 0;
+ frontends.reserve(g_frontends.size());
+ for (const auto& front : g_frontends) {
+ if (front->udpFD == -1 && front->tcpFD == -1) {
+ continue;
+ }
+ Json::object frontend{
+ {"id", num++},
+ {"address", front->local.toStringWithPort()},
+ {"udp", front->udpFD >= 0},
+ {"tcp", front->tcpFD >= 0},
+ {"type", front->getType()},
+ {"queries", (double)front->queries.load()},
+ {"nonCompliantQueries", (double)front->nonCompliantQueries.load()},
+ {"responses", (double)front->responses.load()},
+ {"tcpDiedReadingQuery", (double)front->tcpDiedReadingQuery.load()},
+ {"tcpDiedSendingResponse", (double)front->tcpDiedSendingResponse.load()},
+ {"tcpGaveUp", (double)front->tcpGaveUp.load()},
+ {"tcpClientTimeouts", (double)front->tcpClientTimeouts},
+ {"tcpDownstreamTimeouts", (double)front->tcpDownstreamTimeouts},
+ {"tcpCurrentConnections", (double)front->tcpCurrentConnections},
+ {"tcpMaxConcurrentConnections", (double)front->tcpMaxConcurrentConnections},
+ {"tcpAvgQueriesPerConnection", (double)front->tcpAvgQueriesPerConnection},
+ {"tcpAvgConnectionDuration", (double)front->tcpAvgConnectionDuration},
+ {"tlsNewSessions", (double)front->tlsNewSessions},
+ {"tlsResumptions", (double)front->tlsResumptions},
+ {"tlsUnknownTicketKey", (double)front->tlsUnknownTicketKey},
+ {"tlsInactiveTicketKey", (double)front->tlsInactiveTicketKey},
+ {"tls10Queries", (double)front->tls10queries},
+ {"tls11Queries", (double)front->tls11queries},
+ {"tls12Queries", (double)front->tls12queries},
+ {"tls13Queries", (double)front->tls13queries},
+ {"tlsUnknownQueries", (double)front->tlsUnknownqueries},
+ };
+ const TLSErrorCounters* errorCounters = nullptr;
+ if (front->tlsFrontend != nullptr) {
+ errorCounters = &front->tlsFrontend->d_tlsCounters;
+ }
+ else if (front->dohFrontend != nullptr) {
+ errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters;
+ }
+ if (errorCounters != nullptr) {
+ frontend["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters->d_dhKeyTooSmall;
+ frontend["tlsHandshakeFailuresInappropriateFallBack"] = (double)errorCounters->d_inappropriateFallBack;
+ frontend["tlsHandshakeFailuresNoSharedCipher"] = (double)errorCounters->d_noSharedCipher;
+ frontend["tlsHandshakeFailuresUnknownCipher"] = (double)errorCounters->d_unknownCipherType;
+ frontend["tlsHandshakeFailuresUnknownKeyExchangeType"] = (double)errorCounters->d_unknownKeyExchangeType;
+ frontend["tlsHandshakeFailuresUnknownProtocol"] = (double)errorCounters->d_unknownProtocol;
+ frontend["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters->d_unsupportedEC;
+ frontend["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters->d_unsupportedProtocol;
+ }
+ frontends.emplace_back(std::move(frontend));
+ }
+
+ Json::array dohs;
+#ifdef HAVE_DNS_OVER_HTTPS
+ {
+ dohs.reserve(g_dohlocals.size());
+ num = 0;
+ for (const auto& doh : g_dohlocals) {
+ dohs.emplace_back(Json::object{
+ {"id", num++},
+ {"address", doh->d_tlsContext.d_addr.toStringWithPort()},
+ {"http-connects", (double)doh->d_httpconnects},
+ {"http1-queries", (double)doh->d_http1Stats.d_nbQueries},
+ {"http2-queries", (double)doh->d_http2Stats.d_nbQueries},
+ {"http1-200-responses", (double)doh->d_http1Stats.d_nb200Responses},
+ {"http2-200-responses", (double)doh->d_http2Stats.d_nb200Responses},
+ {"http1-400-responses", (double)doh->d_http1Stats.d_nb400Responses},
+ {"http2-400-responses", (double)doh->d_http2Stats.d_nb400Responses},
+ {"http1-403-responses", (double)doh->d_http1Stats.d_nb403Responses},
+ {"http2-403-responses", (double)doh->d_http2Stats.d_nb403Responses},
+ {"http1-500-responses", (double)doh->d_http1Stats.d_nb500Responses},
+ {"http2-500-responses", (double)doh->d_http2Stats.d_nb500Responses},
+ {"http1-502-responses", (double)doh->d_http1Stats.d_nb502Responses},
+ {"http2-502-responses", (double)doh->d_http2Stats.d_nb502Responses},
+ {"http1-other-responses", (double)doh->d_http1Stats.d_nbOtherResponses},
+ {"http2-other-responses", (double)doh->d_http2Stats.d_nbOtherResponses},
+ {"get-queries", (double)doh->d_getqueries},
+ {"post-queries", (double)doh->d_postqueries},
+ {"bad-requests", (double)doh->d_badrequests},
+ {"error-responses", (double)doh->d_errorresponses},
+ {"redirect-responses", (double)doh->d_redirectresponses},
+ {"valid-responses", (double)doh->d_validresponses}});
+ }
+ }
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+ Json::array pools;
+ {
+ auto localPools = g_pools.getLocal();
+ num = 0;
+ pools.reserve(localPools->size());
+ for (const auto& pool : *localPools) {
+ const auto& cache = pool.second->packetCache;
+ Json::object entry{
+ {"id", num++},
+ {"name", pool.first},
+ {"serversCount", (double)pool.second->countServers(false)},
+ {"cacheSize", (double)(cache ? cache->getMaxEntries() : 0)},
+ {"cacheEntries", (double)(cache ? cache->getEntriesCount() : 0)},
+ {"cacheHits", (double)(cache ? cache->getHits() : 0)},
+ {"cacheMisses", (double)(cache ? cache->getMisses() : 0)},
+ {"cacheDeferredInserts", (double)(cache ? cache->getDeferredInserts() : 0)},
+ {"cacheDeferredLookups", (double)(cache ? cache->getDeferredLookups() : 0)},
+ {"cacheLookupCollisions", (double)(cache ? cache->getLookupCollisions() : 0)},
+ {"cacheInsertCollisions", (double)(cache ? cache->getInsertCollisions() : 0)},
+ {"cacheTTLTooShorts", (double)(cache ? cache->getTTLTooShorts() : 0)},
+ {"cacheCleanupCount", (double)(cache ? cache->getCleanupCount() : 0)}};
+ pools.emplace_back(std::move(entry));
+ }
+ }
+
+ Json::array rules;
+ /* unfortunately DNSActions have getStats(),
+ and DNSResponseActions do not. */
+ {
+ auto localRules = dnsdist::rules::g_ruleactions.getLocal();
+ num = 0;
+ rules.reserve(localRules->size());
+ for (const auto& lrule : *localRules) {
+ Json::object rule{
+ {"id", num++},
+ {"creationOrder", (double)lrule.d_creationOrder},
+ {"uuid", boost::uuids::to_string(lrule.d_id)},
+ {"name", lrule.d_name},
+ {"matches", (double)lrule.d_rule->d_matches},
+ {"rule", lrule.d_rule->toString()},
+ {"action", lrule.d_action->toString()},
+ {"action-stats", lrule.d_action->getStats()}};
+ rules.emplace_back(std::move(rule));
+ }
+ }
+
+ string acl;
+ {
+ auto aclEntries = g_ACL.getLocal()->toStringVector();
+
+ for (const auto& entry : aclEntries) {
+ if (!acl.empty()) {
+ acl += ", ";
+ }
+ acl += entry;
+ }
+ }
+
+ string localaddressesStr;
+ {
+ std::set<std::string> localaddresses;
+ for (const auto& front : g_frontends) {
+ localaddresses.insert(front->local.toStringWithPort());
+ }
+ for (const auto& addr : localaddresses) {
+ if (!localaddressesStr.empty()) {
+ localaddressesStr += ", ";
+ }
+ localaddressesStr += addr;
+ }
+ }
+
+ Json::object stats;
+ addStatsToJSONObject(stats);
+
+ Json::object responseObject{{"daemon_type", "dnsdist"},
+ {"version", VERSION},
+ {"servers", std::move(servers)},
+ {"frontends", std::move(frontends)},
+ {"pools", std::move(pools)},
+ {"rules", std::move(rules)},
+ {"acl", std::move(acl)},
+ {"local", std::move(localaddressesStr)},
+ {"dohFrontends", std::move(dohs)},
+ {"statistics", std::move(stats)}};
+
+ for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+ auto responseRules = someResponseRulesToJson(&chain.holder);
+ responseObject[chain.metricName] = std::move(responseRules);
+ }
+
+ resp.headers["Content-Type"] = "application/json";
+ resp.body = Json(responseObject).dump();
+}
+
+static void handlePoolStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+ const auto poolName = req.getvars.find("name");
+ if (poolName == req.getvars.end()) {
+ resp.status = 400;
+ return;
+ }
+
+ resp.status = 200;
+ Json::array doc;
+
+ auto localPools = g_pools.getLocal();
+ const auto poolIt = localPools->find(poolName->second);
+ if (poolIt == localPools->end()) {
+ resp.status = 404;
+ return;
+ }
+
+ const auto& pool = poolIt->second;
+ const auto& cache = pool->packetCache;
+ Json::object entry{
+ {"name", poolName->second},
+ {"serversCount", (double)pool->countServers(false)},
+ {"cacheSize", (double)(cache ? cache->getMaxEntries() : 0)},
+ {"cacheEntries", (double)(cache ? cache->getEntriesCount() : 0)},
+ {"cacheHits", (double)(cache ? cache->getHits() : 0)},
+ {"cacheMisses", (double)(cache ? cache->getMisses() : 0)},
+ {"cacheDeferredInserts", (double)(cache ? cache->getDeferredInserts() : 0)},
+ {"cacheDeferredLookups", (double)(cache ? cache->getDeferredLookups() : 0)},
+ {"cacheLookupCollisions", (double)(cache ? cache->getLookupCollisions() : 0)},
+ {"cacheInsertCollisions", (double)(cache ? cache->getInsertCollisions() : 0)},
+ {"cacheTTLTooShorts", (double)(cache ? cache->getTTLTooShorts() : 0)},
+ {"cacheCleanupCount", (double)(cache ? cache->getCleanupCount() : 0)}};
+
+ Json::array servers;
+ int num = 0;
+ for (const auto& server : *pool->getServers()) {
+ addServerToJSON(servers, num, server.second);
+ num++;
+ }
+
+ resp.headers["Content-Type"] = "application/json";
+ Json my_json = Json::object{
+ {"stats", entry},
+ {"servers", servers}};
+
+ resp.body = my_json.dump();
+}
+
+static void handleStatsOnly(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+ resp.status = 200;
+
+ Json::array doc;
+ {
+ auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+ for (const auto& item : *entries) {
+ if (item.d_name == "special-memory-usage") {
+ continue; // Too expensive for get-all
+ }
+
+ if (const auto& val = std::get_if<pdns::stat_t*>(&item.d_value)) {
+ doc.emplace_back(Json::object{
+ {"type", "StatisticItem"},
+ {"name", item.d_name},
+ {"value", (double)(*val)->load()}});
+ }
+ else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&item.d_value)) {
+ doc.emplace_back(Json::object{
+ {"type", "StatisticItem"},
+ {"name", item.d_name},
+ {"value", (*adval)->load()}});
+ }
+ else if (const auto& dval = std::get_if<double*>(&item.d_value)) {
+ doc.emplace_back(Json::object{
+ {"type", "StatisticItem"},
+ {"name", item.d_name},
+ {"value", (**dval)}});
+ }
+ else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&item.d_value)) {
+ doc.emplace_back(Json::object{
+ {"type", "StatisticItem"},
+ {"name", item.d_name},
+ {"value", (double)(*func)(item.d_name)}});
+ }
+ }
+ }
+ Json my_json = doc;
+ resp.body = my_json.dump();
+ resp.headers["Content-Type"] = "application/json";
+}
+
+#ifndef DISABLE_WEB_CONFIG
+static void handleConfigDump(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+ resp.status = 200;
+
+ Json::array doc;
+ typedef boost::variant<bool, double, std::string> configentry_t;
+ std::vector<std::pair<std::string, configentry_t>> configEntries{
+ {"acl", g_ACL.getLocal()->toString()},
+ {"allow-empty-response", g_allowEmptyResponse},
+ {"control-socket", g_serverControl.toStringWithPort()},
+ {"ecs-override", g_ECSOverride},
+ {"ecs-source-prefix-v4", (double)g_ECSSourcePrefixV4},
+ {"ecs-source-prefix-v6", (double)g_ECSSourcePrefixV6},
+ {"fixup-case", g_fixupCase},
+ {"max-outstanding", (double)g_maxOutstanding},
+ {"server-policy", g_policy.getLocal()->getName()},
+ {"stale-cache-entries-ttl", (double)g_staleCacheEntriesTTL},
+ {"tcp-recv-timeout", (double)g_tcpRecvTimeout},
+ {"tcp-send-timeout", (double)g_tcpSendTimeout},
+ {"truncate-tc", g_truncateTC},
+ {"verbose", g_verbose},
+ {"verbose-health-checks", g_verboseHealthChecks}};
+ for (const auto& item : configEntries) {
+ if (const auto& bval = boost::get<bool>(&item.second)) {
+ doc.emplace_back(Json::object{
+ {"type", "ConfigSetting"},
+ {"name", item.first},
+ {"value", *bval}});
+ }
+ else if (const auto& sval = boost::get<string>(&item.second)) {
+ doc.emplace_back(Json::object{
+ {"type", "ConfigSetting"},
+ {"name", item.first},
+ {"value", *sval}});
+ }
+ else if (const auto& dval = boost::get<double>(&item.second)) {
+ doc.emplace_back(Json::object{
+ {"type", "ConfigSetting"},
+ {"name", item.first},
+ {"value", *dval}});
+ }
+ }
+ Json my_json = doc;
+ resp.body = my_json.dump();
+ resp.headers["Content-Type"] = "application/json";
+}
+
+static void handleAllowFrom(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+
+ resp.headers["Content-Type"] = "application/json";
+ resp.status = 200;
+
+ if (req.method == "PUT") {
+ std::string err;
+ Json doc = Json::parse(req.body, err);
+
+ if (!doc.is_null()) {
+ NetmaskGroup nmg;
+ auto aclList = doc["value"];
+ if (aclList.is_array()) {
+
+ for (const auto& value : aclList.array_items()) {
+ try {
+ nmg.addMask(value.string_value());
+ }
+ catch (NetmaskException& e) {
+ resp.status = 400;
+ break;
+ }
+ }
+
+ if (resp.status == 200) {
+ infolog("Updating the ACL via the API to %s", nmg.toString());
+ g_ACL.setState(nmg);
+ apiSaveACL(nmg);
+ }
+ }
+ else {
+ resp.status = 400;
+ }
+ }
+ else {
+ resp.status = 400;
+ }
+ }
+ if (resp.status == 200) {
+ auto aclEntries = g_ACL.getLocal()->toStringVector();
+
+ Json::object obj{
+ {"type", "ConfigSetting"},
+ {"name", "allow-from"},
+ {"value", aclEntries}};
+ Json my_json = obj;
+ resp.body = my_json.dump();
+ }
+}
+#endif /* DISABLE_WEB_CONFIG */
+
+#ifndef DISABLE_WEB_CACHE_MANAGEMENT
+static void handleCacheManagement(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+
+ resp.headers["Content-Type"] = "application/json";
+ resp.status = 200;
+
+ if (req.method != "DELETE") {
+ resp.status = 400;
+ Json::object obj{
+ {"status", "denied"},
+ {"error", "invalid method"}};
+ resp.body = Json(obj).dump();
+ return;
+ }
+
+ const auto poolName = req.getvars.find("pool");
+ const auto expungeName = req.getvars.find("name");
+ const auto expungeType = req.getvars.find("type");
+ const auto suffix = req.getvars.find("suffix");
+ if (poolName == req.getvars.end() || expungeName == req.getvars.end()) {
+ resp.status = 400;
+ Json::object obj{
+ {"status", "denied"},
+ {"error", "missing 'pool' or 'name' parameter"},
+ };
+ resp.body = Json(obj).dump();
+ return;
+ }
+
+ DNSName name;
+ QType type(QType::ANY);
+ try {
+ name = DNSName(expungeName->second);
+ }
+ catch (const std::exception& e) {
+ resp.status = 400;
+ Json::object obj{
+ {"status", "error"},
+ {"error", "unable to parse the requested name"},
+ };
+ resp.body = Json(obj).dump();
+ return;
+ }
+ if (expungeType != req.getvars.end()) {
+ type = QType::chartocode(expungeType->second.c_str());
+ }
+
+ std::shared_ptr<ServerPool> pool;
+ try {
+ pool = getPool(g_pools.getCopy(), poolName->second);
+ }
+ catch (const std::exception& e) {
+ resp.status = 404;
+ Json::object obj{
+ {"status", "not found"},
+ {"error", "the requested pool does not exist"},
+ };
+ resp.body = Json(obj).dump();
+ return;
+ }
+
+ auto cache = pool->getCache();
+ if (cache == nullptr) {
+ resp.status = 404;
+ Json::object obj{
+ {"status", "not found"},
+ {"error", "there is no cache associated with the requested pool"},
+ };
+ resp.body = Json(obj).dump();
+ return;
+ }
+
+ auto removed = cache->expungeByName(name, type.getCode(), suffix != req.getvars.end());
+
+ Json::object obj{
+ {"status", "purged"},
+ {"count", std::to_string(removed)}};
+ resp.body = Json(obj).dump();
+}
+#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
+
+template <typename T>
+static void addRingEntryToList(const struct timespec& now, Json::array& list, const T& entry)
+{
+ constexpr bool response = std::is_same_v<T, Rings::Response>;
+ Json::object tmp{
+ {"age", static_cast<double>(DiffTime(entry.when, now))},
+ {"id", ntohs(entry.dh.id)},
+ {"name", entry.name.toString()},
+ {"requestor", entry.requestor.toStringWithPort()},
+ {"size", static_cast<int>(entry.size)},
+ {"qtype", entry.qtype},
+ {"protocol", entry.protocol.toString()},
+ {"rd", static_cast<bool>(entry.dh.rd)},
+ };
+ if constexpr (!response) {
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ tmp.emplace("mac", entry.hasmac ? std::string(reinterpret_cast<const char*>(entry.macaddress.data()), entry.macaddress.size()) : std::string());
+#endif
+ }
+ else {
+ tmp.emplace("latency", static_cast<double>(entry.usec));
+ tmp.emplace("rcode", static_cast<uint8_t>(entry.dh.rcode));
+ tmp.emplace("tc", static_cast<bool>(entry.dh.tc));
+ tmp.emplace("aa", static_cast<bool>(entry.dh.aa));
+ tmp.emplace("answers", ntohs(entry.dh.ancount));
+ auto server = entry.ds.toStringWithPort();
+ tmp.emplace("backend", server != "0.0.0.0:0" ? std::move(server) : "Cache");
+ }
+ list.emplace_back(std::move(tmp));
+}
+
+static void handleRings(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ handleCORS(req, resp);
+
+ std::optional<size_t> maxNumberOfQueries{std::nullopt};
+ std::optional<size_t> maxNumberOfResponses{std::nullopt};
+
+ const auto maxQueries = req.getvars.find("maxQueries");
+ if (maxQueries != req.getvars.end()) {
+ try {
+ maxNumberOfQueries = pdns::checked_stoi<size_t>(maxQueries->second);
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Error parsing the 'maxQueries' value from rings HTTP GET query: %s", exp.what());
+ }
+ }
+
+ const auto maxResponses = req.getvars.find("maxResponses");
+ if (maxResponses != req.getvars.end()) {
+ try {
+ maxNumberOfResponses = pdns::checked_stoi<size_t>(maxResponses->second);
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Error parsing the 'maxResponses' value from rings HTTP GET query: %s", exp.what());
+ }
+ }
+
+ resp.status = 200;
+
+ Json::object doc;
+ size_t numberOfQueries = 0;
+ size_t numberOfResponses = 0;
+ Json::array queries;
+ Json::array responses;
+ struct timespec now
+ {
+ };
+ gettime(&now);
+
+ for (const auto& shard : g_rings.d_shards) {
+ if (!maxNumberOfQueries || numberOfQueries < *maxNumberOfQueries) {
+ auto queryRing = shard->queryRing.lock();
+ for (const auto& entry : *queryRing) {
+ addRingEntryToList(now, queries, entry);
+ numberOfQueries++;
+ if (maxNumberOfQueries && numberOfQueries >= *maxNumberOfQueries) {
+ break;
+ }
+ }
+ }
+ if (!maxNumberOfResponses || numberOfResponses < *maxNumberOfResponses) {
+ auto responseRing = shard->respRing.lock();
+ for (const auto& entry : *responseRing) {
+ addRingEntryToList(now, responses, entry);
+ numberOfResponses++;
+ if (maxNumberOfResponses && numberOfResponses >= *maxNumberOfResponses) {
+ break;
+ }
+ }
+ }
+ }
+ doc.emplace("queries", std::move(queries));
+ doc.emplace("responses", std::move(responses));
+ Json my_json = doc;
+ resp.body = my_json.dump();
+ resp.headers["Content-Type"] = "application/json";
+}
+
+static std::unordered_map<std::string, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)>> s_webHandlers;
+
+void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler);
+
+void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler)
+{
+ s_webHandlers[endpoint] = std::move(handler);
+}
+
+void clearWebHandlers()
+{
+ s_webHandlers.clear();
+}
+
+#ifndef DISABLE_BUILTIN_HTML
+#include "htmlfiles.h"
+
+static void redirectToIndex(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ const string charset = "; charset=utf-8";
+ resp.body.assign(s_urlmap.at("index.html"));
+ resp.headers["Content-Type"] = "text/html" + charset;
+ resp.status = 200;
+}
+
+static void handleBuiltInFiles(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+ if (req.url.path.empty()) {
+ resp.status = 404;
+ return;
+ }
+ const auto url = std::string_view(req.url.path).substr(1);
+ auto urlMapIt = s_urlmap.find(url);
+ if (urlMapIt == s_urlmap.end()) {
+ resp.status = 404;
+ return;
+ }
+
+ resp.body.assign(urlMapIt->second);
+
+ vector<string> parts;
+ stringtok(parts, req.url.path, ".");
+ static const std::unordered_map<std::string, std::string> contentTypeMap = {
+ {"html", "text/html"},
+ {"css", "text/css"},
+ {"js", "application/javascript"},
+ {"png", "image/png"},
+ };
+
+ const auto& contentTypeIt = contentTypeMap.find(parts.back());
+ if (contentTypeIt != contentTypeMap.end()) {
+ const string charset = "; charset=utf-8";
+ resp.headers["Content-Type"] = contentTypeIt->second + charset;
+ }
+
+ resp.status = 200;
+}
+#endif /* DISABLE_BUILTIN_HTML */
+
+void registerBuiltInWebHandlers()
+{
+#ifndef DISABLE_BUILTIN_HTML
+ registerWebHandler("/jsonstat", handleJSONStats);
+#endif /* DISABLE_BUILTIN_HTML */
+#ifndef DISABLE_PROMETHEUS
+ registerWebHandler("/metrics", handlePrometheus);
+#endif /* DISABLE_PROMETHEUS */
+ registerWebHandler("/api/v1/servers/localhost", handleStats);
+ registerWebHandler("/api/v1/servers/localhost/pool", handlePoolStats);
+ registerWebHandler("/api/v1/servers/localhost/statistics", handleStatsOnly);
+ registerWebHandler("/api/v1/servers/localhost/rings", handleRings);
+#ifndef DISABLE_WEB_CONFIG
+ registerWebHandler("/api/v1/servers/localhost/config", handleConfigDump);
+ registerWebHandler("/api/v1/servers/localhost/config/allow-from", handleAllowFrom);
+#endif /* DISABLE_WEB_CONFIG */
+#ifndef DISABLE_WEB_CACHE_MANAGEMENT
+ registerWebHandler("/api/v1/cache", handleCacheManagement);
+#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
+#ifndef DISABLE_BUILTIN_HTML
+ registerWebHandler("/", redirectToIndex);
+
+ for (const auto& path : s_urlmap) {
+ registerWebHandler("/" + path.first, handleBuiltInFiles);
+ }
+#endif /* DISABLE_BUILTIN_HTML */
+}
+
+static void connectionThread(WebClientConnection&& conn)
+{
+ setThreadName("dnsdist/webConn");
+
+ vinfolog("Webserver handling connection from %s", conn.getClient().toStringWithPort());
+
+ try {
+ YaHTTP::AsyncRequestLoader yarl;
+ YaHTTP::Request req;
+ bool finished = false;
+
+ std::string buf;
+ yarl.initialize(&req);
+ while (!finished) {
+ ssize_t bytes{0};
+ buf.resize(1024);
+ bytes = read(conn.getSocket().getHandle(), buf.data(), buf.size());
+ if (bytes > 0) {
+ buf.resize(static_cast<size_t>(bytes));
+ finished = yarl.feed(buf);
+ }
+ else {
+ // read error OR EOF
+ break;
+ }
+ }
+ yarl.finalize();
+
+ req.getvars.erase("_"); // jQuery cache buster
+
+ YaHTTP::Response resp;
+ resp.version = req.version;
+
+ {
+ auto config = g_webserverConfig.lock();
+
+ addCustomHeaders(resp, config->customHeaders);
+ addSecurityHeaders(resp, config->customHeaders);
+ }
+ /* indicate that the connection will be closed after completion of the response */
+ resp.headers["Connection"] = "close";
+
+ /* no need to send back the API key if any */
+ resp.headers.erase("X-API-Key");
+
+ if (req.method == "OPTIONS") {
+ /* the OPTIONS method should not require auth, otherwise it breaks CORS */
+ handleCORS(req, resp);
+ resp.status = 200;
+ }
+ else if (!handleAuthorization(req)) {
+ auto header = req.headers.find("authorization");
+ if (header != req.headers.end()) {
+ vinfolog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, conn.getClient().toStringWithPort());
+ }
+ resp.status = 401;
+ resp.body = "<h1>Unauthorized</h1>";
+ resp.headers["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
+ }
+ else if (!isMethodAllowed(req)) {
+ resp.status = 405;
+ }
+ else {
+ const auto webHandlersIt = s_webHandlers.find(req.url.path);
+ if (webHandlersIt != s_webHandlers.end()) {
+ webHandlersIt->second(req, resp);
+ }
+ else {
+ resp.status = 404;
+ }
+ }
+
+ std::ostringstream ofs;
+ ofs << resp;
+ string done = ofs.str();
+ writen2(conn.getSocket().getHandle(), done.c_str(), done.size());
+ }
+ catch (const YaHTTP::ParseError& e) {
+ vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", conn.getClient().toStringWithPort(), e.what());
+ }
+ catch (const std::exception& e) {
+ vinfolog("Webserver thread died with exception while processing a request from %s: %s", conn.getClient().toStringWithPort(), e.what());
+ }
+ catch (...) {
+ vinfolog("Webserver thread died with exception while processing a request from %s", conn.getClient().toStringWithPort());
+ }
+}
+
+void setWebserverAPIKey(std::unique_ptr<CredentialsHolder>&& apiKey)
+{
+ auto config = g_webserverConfig.lock();
+
+ if (apiKey) {
+ config->apiKey = std::move(apiKey);
+ }
+ else {
+ config->apiKey.reset();
+ }
+}
+
+void setWebserverPassword(std::unique_ptr<CredentialsHolder>&& password)
+{
+ g_webserverConfig.lock()->password = std::move(password);
+}
+
+void setWebserverACL(const std::string& acl)
+{
+ NetmaskGroup newACL;
+ newACL.toMasks(acl);
+
+ g_webserverConfig.lock()->acl = std::move(newACL);
+}
+
+void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
+{
+ g_webserverConfig.lock()->customHeaders = customHeaders;
+}
+
+void setWebserverStatsRequireAuthentication(bool require)
+{
+ g_webserverConfig.lock()->statsRequireAuthentication = require;
+}
+
+void setWebserverAPIRequiresAuthentication(bool require)
+{
+ g_webserverConfig.lock()->apiRequiresAuthentication = require;
+}
+
+void setWebserverDashboardRequiresAuthentication(bool require)
+{
+ g_webserverConfig.lock()->dashboardRequiresAuthentication = require;
+}
+
+void setWebserverMaxConcurrentConnections(size_t max)
+{
+ s_connManager.setMaxConcurrentConnections(max);
+}
+
+void dnsdistWebserverThread(int sock, const ComboAddress& local)
+{
+ setThreadName("dnsdist/webserv");
+ infolog("Webserver launched on %s", local.toStringWithPort());
+
+ {
+ auto config = g_webserverConfig.lock();
+ if (!config->password && config->dashboardRequiresAuthentication) {
+ warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
+ }
+ }
+
+ for (;;) {
+ try {
+ ComboAddress remote(local);
+ int fileDesc = SAccept(sock, remote);
+
+ if (!isClientAllowedByACL(remote)) {
+ vinfolog("Connection to webserver from client %s is not allowed, closing", remote.toStringWithPort());
+ close(fileDesc);
+ continue;
+ }
+
+ WebClientConnection conn(remote, fileDesc);
+ vinfolog("Got a connection to the webserver from %s", remote.toStringWithPort());
+
+ std::thread connThr(connectionThread, std::move(conn));
+ connThr.detach();
+ }
+ catch (const std::exception& e) {
+ vinfolog("Had an error accepting new webserver connection: %s", e.what());
+ }
+ }
+}
#pragma once
#include "credentials.hh"
+#include "dnsdist-prometheus.hh"
void setWebserverAPIKey(std::unique_ptr<CredentialsHolder>&& apiKey);
void setWebserverPassword(std::unique_ptr<CredentialsHolder>&& password);
void setWebserverACL(const std::string& acl);
-void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string> > customHeaders);
+void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string> >& customHeaders);
void setWebserverAPIRequiresAuthentication(bool);
void setWebserverDashboardRequiresAuthentication(bool);
void setWebserverStatsRequireAuthentication(bool);
void registerBuiltInWebHandlers();
void clearWebHandlers();
-bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customPrometheusName);
-
std::string getWebserverConfig();
+
+bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def);
+++ /dev/null
-../dnsdist-xpf.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dnsdist-xpf.hh"
+
+#include "dnsdist-dnsparser.hh"
+#include "dnsparser.hh"
+#include "xpf.hh"
+
+bool addXPF(DNSQuestion& dnsQuestion, uint16_t optionCode)
+{
+ std::string payload = generateXPFPayload(dnsQuestion.overTCP(), dnsQuestion.ids.origRemote, dnsQuestion.ids.origDest);
+ uint8_t root = '\0';
+ dnsrecordheader drh{};
+ drh.d_type = htons(optionCode);
+ drh.d_class = htons(QClass::IN);
+ drh.d_ttl = 0;
+ drh.d_clen = htons(payload.size());
+ size_t recordHeaderLen = sizeof(root) + sizeof(drh);
+
+ if (!dnsQuestion.hasRoomFor(payload.size() + recordHeaderLen)) {
+ return false;
+ }
+
+ size_t xpfSize = sizeof(root) + sizeof(drh) + payload.size();
+ auto& data = dnsQuestion.getMutableData();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(data.data()), data.size());
+ data.resize(realPacketLen + xpfSize);
+
+ size_t pos = realPacketLen;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ memcpy(reinterpret_cast<char*>(&data.at(pos)), &root, sizeof(root));
+ pos += sizeof(root);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ memcpy(reinterpret_cast<char*>(&data.at(pos)), &drh, sizeof(drh));
+ pos += sizeof(drh);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ memcpy(reinterpret_cast<char*>(&data.at(pos)), payload.data(), payload.size());
+ pos += payload.size();
+ (void)pos;
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.arcount = htons(ntohs(header.arcount) + 1);
+ return true;
+ });
+ return true;
+}
+++ /dev/null
-../dnsdist-xpf.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "dnsdist.hh"
+
+bool addXPF(DNSQuestion& dnsQuestion, uint16_t optionCode);
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dnsdist.hh"
+#include "dnsdist-xsk.hh"
+
+#ifdef HAVE_XSK
+#include <sys/poll.h>
+
+#include "dolog.hh"
+#include "dnsdist-metrics.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "threadname.hh"
+#include "xsk.hh"
+
+namespace dnsdist::xsk
+{
+std::vector<std::shared_ptr<XskSocket>> g_xsk;
+
+void XskResponderThread(std::shared_ptr<DownstreamState> dss, std::shared_ptr<XskWorker> xskInfo)
+{
+ try {
+ setThreadName("dnsdist/XskResp");
+ auto localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+ auto localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+ auto pollfds = getPollFdsForWorker(*xskInfo);
+ while (!dss->isStopped()) {
+ poll(pollfds.data(), pollfds.size(), -1);
+ bool needNotify = false;
+ if ((pollfds[0].revents & POLLIN) != 0) {
+ needNotify = true;
+ xskInfo->cleanSocketNotification();
+#if defined(__SANITIZE_THREAD__)
+ xskInfo->incomingPacketsQueue.lock()->consume_all([&](XskPacket& packet) {
+#else
+ xskInfo->incomingPacketsQueue.consume_all([&](XskPacket& packet) {
+#endif
+ if (packet.getDataLen() < sizeof(dnsheader)) {
+ xskInfo->markAsFree(packet);
+ return;
+ }
+ const dnsheader_aligned dnsHeader(packet.getPayloadData());
+ const auto queryId = dnsHeader->id;
+ auto ids = dss->getState(queryId);
+ if (ids) {
+ if (!ids->isXSK()) {
+ dss->restoreState(queryId, std::move(*ids));
+ ids = std::nullopt;
+ }
+ }
+ if (!ids) {
+ xskInfo->markAsFree(packet);
+ return;
+ }
+ auto response = packet.clonePacketBuffer();
+ if (response.size() > packet.getCapacity()) {
+ /* fallback to sending the packet via normal socket */
+ ids->xskPacketHeader.clear();
+ }
+ if (!processResponderPacket(dss, response, *localRespRuleActions, *localCacheInsertedRespRuleActions, std::move(*ids))) {
+ xskInfo->markAsFree(packet);
+ infolog("XSK packet pushed to queue because processResponderPacket failed");
+ return;
+ }
+ if (response.size() > packet.getCapacity()) {
+ /* fallback to sending the packet via normal socket */
+ sendUDPResponse(ids->cs->udpFD, response, ids->delayMsec, ids->hopLocal, ids->hopRemote);
+ infolog("XSK packet falling back because packet is too large");
+ xskInfo->markAsFree(packet);
+ return;
+ }
+ packet.setHeader(ids->xskPacketHeader);
+ if (!packet.setPayload(response)) {
+ infolog("Unable to set XSK payload !");
+ }
+ if (ids->delayMsec > 0) {
+ packet.addDelay(ids->delayMsec);
+ }
+ packet.updatePacket();
+ xskInfo->pushToSendQueue(packet);
+ });
+ }
+ if (needNotify) {
+ xskInfo->notifyXskSocket();
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ errlog("XSK responder thread died because of exception: %s", e.what());
+ }
+ catch (const PDNSException& e) {
+ errlog("XSK responder thread died because of PowerDNS exception: %s", e.reason);
+ }
+ catch (...) {
+ errlog("XSK responder thread died because of an exception: %s", "unknown");
+ }
+}
+
+bool XskIsQueryAcceptable(const XskPacket& packet, ClientState& clientState, LocalHolders& holders, bool& expectProxyProtocol)
+{
+ const auto& from = packet.getFromAddr();
+ expectProxyProtocol = expectProxyProtocolFrom(from);
+ if (!holders.acl->match(from) && !expectProxyProtocol) {
+ vinfolog("Query from %s dropped because of ACL", from.toStringWithPort());
+ ++dnsdist::metrics::g_stats.aclDrops;
+ return false;
+ }
+ clientState.queries++;
+ ++dnsdist::metrics::g_stats.queries;
+
+ return true;
+}
+
+void XskRouter(std::shared_ptr<XskSocket> xsk)
+{
+ setThreadName("dnsdist/XskRouter");
+ uint32_t failed = 0;
+ // packets to be submitted for sending
+ vector<XskPacket> fillInTx;
+ const auto& fds = xsk->getDescriptors();
+ // list of workers that need to be notified
+ std::set<int> needNotify;
+ while (true) {
+ try {
+ auto ready = xsk->wait(-1);
+ // descriptor 0 gets incoming AF_XDP packets
+ if ((fds.at(0).revents & POLLIN) != 0) {
+ auto packets = xsk->recv(64, &failed);
+ dnsdist::metrics::g_stats.nonCompliantQueries += failed;
+ for (auto& packet : packets) {
+ const auto dest = packet.getToAddr();
+ auto worker = xsk->getWorkerByDestination(dest);
+ if (!worker) {
+ xsk->markAsFree(packet);
+ continue;
+ }
+ worker->pushToProcessingQueue(packet);
+ needNotify.insert(worker->workerWaker.getHandle());
+ }
+ for (auto socket : needNotify) {
+ uint64_t value = 1;
+ auto written = write(socket, &value, sizeof(value));
+ if (written != sizeof(value)) {
+ // oh, well, the worker is clearly overloaded
+ // but there is nothing we can do about it,
+ // and hopefully the queue will be processed eventually
+ }
+ }
+ needNotify.clear();
+ ready--;
+ }
+ for (size_t fdIndex = 1; fdIndex < fds.size() && ready > 0; fdIndex++) {
+ if ((fds.at(fdIndex).revents & POLLIN) != 0) {
+ ready--;
+ const auto& info = xsk->getWorkerByDescriptor(fds.at(fdIndex).fd);
+#if defined(__SANITIZE_THREAD__)
+ info->outgoingPacketsQueue.lock()->consume_all([&](XskPacket& packet) {
+#else
+ info->outgoingPacketsQueue.consume_all([&](XskPacket& packet) {
+#endif
+ if ((packet.getFlags() & XskPacket::UPDATE) == 0) {
+ xsk->markAsFree(packet);
+ return;
+ }
+ if ((packet.getFlags() & XskPacket::DELAY) != 0) {
+ xsk->pushDelayed(packet);
+ return;
+ }
+ fillInTx.push_back(packet);
+ });
+ info->cleanWorkerNotification();
+ }
+ }
+ xsk->pickUpReadyPacket(fillInTx);
+ xsk->recycle(4096);
+ xsk->fillFq();
+ xsk->send(fillInTx);
+ }
+ catch (...) {
+ vinfolog("Exception in XSK router loop");
+ }
+ }
+}
+
+void XskClientThread(ClientState* clientState)
+{
+ setThreadName("dnsdist/xskClient");
+ auto xskInfo = clientState->xskInfo;
+ LocalHolders holders;
+
+ for (;;) {
+#if defined(__SANITIZE_THREAD__)
+ while (xskInfo->incomingPacketsQueue.lock()->read_available() == 0U) {
+#else
+ while (xskInfo->incomingPacketsQueue.read_available() == 0U) {
+#endif
+ xskInfo->waitForXskSocket();
+ }
+#if defined(__SANITIZE_THREAD__)
+ xskInfo->incomingPacketsQueue.lock()->consume_all([&](XskPacket& packet) {
+#else
+ xskInfo->incomingPacketsQueue.consume_all([&](XskPacket& packet) {
+#endif
+ if (XskProcessQuery(*clientState, holders, packet)) {
+ packet.updatePacket();
+ xskInfo->pushToSendQueue(packet);
+ }
+ else {
+ xskInfo->markAsFree(packet);
+ }
+ });
+ xskInfo->notifyXskSocket();
+ }
+}
+
+static std::string getDestinationMap(bool isV6)
+{
+ return !isV6 ? "/sys/fs/bpf/dnsdist/xsk-destinations-v4" : "/sys/fs/bpf/dnsdist/xsk-destinations-v6";
+}
+
+void addDestinationAddress(const ComboAddress& addr)
+{
+ auto map = getDestinationMap(addr.isIPv6());
+ XskSocket::addDestinationAddress(map, addr);
+}
+
+void removeDestinationAddress(const ComboAddress& addr)
+{
+ auto map = getDestinationMap(addr.isIPv6());
+ XskSocket::removeDestinationAddress(map, addr);
+}
+
+void clearDestinationAddresses()
+{
+ auto map = getDestinationMap(false);
+ XskSocket::clearDestinationMap(map, false);
+ map = getDestinationMap(true);
+ XskSocket::clearDestinationMap(map, true);
+}
+
+}
+#endif /* HAVE_XSK */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "config.h"
+
+#ifdef HAVE_XSK
+class XskPacket;
+class XskSocket;
+class XskWorker;
+
+#include <memory>
+
+namespace dnsdist::xsk
+{
+void XskResponderThread(std::shared_ptr<DownstreamState> dss, std::shared_ptr<XskWorker> xskInfo);
+bool XskIsQueryAcceptable(const XskPacket& packet, ClientState& clientState, LocalHolders& holders, bool& expectProxyProtocol);
+bool XskProcessQuery(ClientState& clientState, LocalHolders& holders, XskPacket& packet);
+void XskRouter(std::shared_ptr<XskSocket> xsk);
+void XskClientThread(ClientState* clientState);
+void addDestinationAddress(const ComboAddress& addr);
+void removeDestinationAddress(const ComboAddress& addr);
+void clearDestinationAddresses();
+
+extern std::vector<std::shared_ptr<XskSocket>> g_xsk;
+}
+#endif /* HAVE_XSK */
+++ /dev/null
-../dnsdist.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <cstdint>
+#include <fstream>
+#include <getopt.h>
+#include <grp.h>
+#include <limits>
+#include <netinet/tcp.h>
+#include <pwd.h>
+#include <set>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#ifdef HAVE_LIBEDIT
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+// If this is not undeffed, __attribute__ wil be redefined by /usr/include/readline/rlstdc.h
+#undef __STRICT_ANSI__
+#include <readline/readline.h>
+#else
+#include <editline/readline.h>
+#endif
+#endif /* HAVE_LIBEDIT */
+
+#include "dnsdist-systemd.hh"
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+#include "dnsdist.hh"
+#include "dnsdist-async.hh"
+#include "dnsdist-cache.hh"
+#include "dnsdist-carbon.hh"
+#include "dnsdist-console.hh"
+#include "dnsdist-crypto.hh"
+#include "dnsdist-discovery.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
+#include "dnsdist-healthchecks.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-lua-hooks.hh"
+#include "dnsdist-nghttp2.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-random.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-secpoll.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-web.hh"
+#include "dnsdist-xpf.hh"
+#include "dnsdist-xsk.hh"
+
+#include "base64.hh"
+#include "capabilities.hh"
+#include "coverage.hh"
+#include "delaypipe.hh"
+#include "doh.hh"
+#include "dolog.hh"
+#include "dnsname.hh"
+#include "dnsparser.hh"
+#include "ednsoptions.hh"
+#include "gettime.hh"
+#include "lock.hh"
+#include "misc.hh"
+#include "sstuff.hh"
+#include "threadname.hh"
+#include "xsk.hh"
+
+/* Known sins:
+
+ Receiver is currently single threaded
+ not *that* bad actually, but now that we are thread safe, might want to scale
+*/
+
+/* the RuleAction plan
+ Set of Rules, if one matches, it leads to an Action
+ Both rules and actions could conceivably be Lua based.
+ On the C++ side, both could be inherited from a class Rule and a class Action,
+ on the Lua side we can't do that. */
+
+using std::thread;
+bool g_verbose;
+
+uint16_t g_maxOutstanding{std::numeric_limits<uint16_t>::max()};
+uint32_t g_staleCacheEntriesTTL{0};
+bool g_allowEmptyResponse{false};
+
+GlobalStateHolder<NetmaskGroup> g_ACL;
+string g_outputBuffer;
+
+std::vector<std::shared_ptr<TLSFrontend>> g_tlslocals;
+std::vector<std::shared_ptr<DOHFrontend>> g_dohlocals;
+std::vector<std::shared_ptr<DOQFrontend>> g_doqlocals;
+std::vector<std::shared_ptr<DOH3Frontend>> g_doh3locals;
+std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
+
+shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
+std::vector<std::shared_ptr<DynBPFFilter>> g_dynBPFFilters;
+
+std::vector<std::unique_ptr<ClientState>> g_frontends;
+GlobalStateHolder<pools_t> g_pools;
+size_t g_udpVectorSize{1};
+std::vector<uint32_t> g_TCPFastOpenKey;
+/* UDP: the grand design. Per socket we listen on for incoming queries there is one thread.
+ Then we have a bunch of connected sockets for talking to downstream servers.
+ We send directly to those sockets.
+
+ For the return path, per downstream server we have a thread that listens to responses.
+
+ Per socket there is an array of 2^16 states, when we send out a packet downstream, we note
+ there the original requestor and the original id. The new ID is the offset in the array.
+
+ When an answer comes in on a socket, we look up the offset by the id, and lob it to the
+ original requestor.
+
+ IDs are assigned by atomic increments of the socket offset.
+ */
+
+Rings g_rings;
+QueryCount g_qcount;
+
+GlobalStateHolder<servers_t> g_dstates;
+
+bool g_servFailOnNoPolicy{false};
+bool g_truncateTC{false};
+bool g_fixupCase{false};
+bool g_dropEmptyQueries{false};
+uint32_t g_socketUDPSendBuffer{0};
+uint32_t g_socketUDPRecvBuffer{0};
+
+std::set<std::string> g_capabilitiesToRetain;
+
+// we are not willing to receive a bigger UDP response than that, no matter what
+static constexpr size_t s_maxUDPResponsePacketSize{4096U};
+static size_t const s_initialUDPPacketBufferSize = s_maxUDPResponsePacketSize + DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE;
+static_assert(s_initialUDPPacketBufferSize <= UINT16_MAX, "Packet size should fit in a uint16_t");
+
+static ssize_t sendfromto(int sock, const void* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& dest)
+{
+ if (from.sin4.sin_family == 0) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ return sendto(sock, data, len, flags, reinterpret_cast<const struct sockaddr*>(&dest), dest.getSocklen());
+ }
+ msghdr msgh{};
+ iovec iov{};
+ cmsgbuf_aligned cbuf;
+
+ /* Set up iov and msgh structures. */
+ memset(&msgh, 0, sizeof(struct msghdr));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): it's the API
+ iov.iov_base = const_cast<void*>(data);
+ iov.iov_len = len;
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
+ msgh.msg_name = const_cast<sockaddr*>(reinterpret_cast<const sockaddr*>(&dest));
+ msgh.msg_namelen = dest.getSocklen();
+
+ if (from.sin4.sin_family != 0) {
+ addCMsgSrcAddr(&msgh, &cbuf, &from, 0);
+ }
+ else {
+ msgh.msg_control = nullptr;
+ }
+ return sendmsg(sock, &msgh, flags);
+}
+
+static void truncateTC(PacketBuffer& packet, size_t maximumSize, unsigned int qnameWireLength)
+{
+ try {
+ bool hadEDNS = false;
+ uint16_t payloadSize = 0;
+ uint16_t zValue = 0;
+
+ if (g_addEDNSToSelfGeneratedResponses) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &zValue);
+ }
+
+ packet.resize(static_cast<uint16_t>(sizeof(dnsheader) + qnameWireLength + DNS_TYPE_SIZE + DNS_CLASS_SIZE));
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+ header.ancount = 0;
+ header.arcount = 0;
+ header.nscount = 0;
+ return true;
+ });
+
+ if (hadEDNS) {
+ addEDNS(packet, maximumSize, (zValue & EDNS_HEADER_FLAG_DO) != 0, payloadSize, 0);
+ }
+ }
+ catch (...) {
+ ++dnsdist::metrics::g_stats.truncFail;
+ }
+}
+
+#ifndef DISABLE_DELAY_PIPE
+struct DelayedPacket
+{
+ int fd{-1};
+ PacketBuffer packet;
+ ComboAddress destination;
+ ComboAddress origDest;
+ void operator()()
+ {
+ ssize_t res = sendfromto(fd, packet.data(), packet.size(), 0, origDest, destination);
+ if (res == -1) {
+ int err = errno;
+ vinfolog("Error sending delayed response to %s: %s", destination.toStringWithPort(), stringerror(err));
+ }
+ }
+};
+
+static std::unique_ptr<DelayPipe<DelayedPacket>> g_delay{nullptr};
+#endif /* DISABLE_DELAY_PIPE */
+
+std::string DNSQuestion::getTrailingData() const
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ const auto* message = reinterpret_cast<const char*>(this->getData().data());
+ const uint16_t messageLen = getDNSPacketLength(message, this->getData().size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ return {message + messageLen, this->getData().size() - messageLen};
+}
+
+bool DNSQuestion::setTrailingData(const std::string& tail)
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ const char* message = reinterpret_cast<const char*>(this->data.data());
+ const uint16_t messageLen = getDNSPacketLength(message, this->data.size());
+ this->data.resize(messageLen);
+ if (!tail.empty()) {
+ if (!hasRoomFor(tail.size())) {
+ return false;
+ }
+ this->data.insert(this->data.end(), tail.begin(), tail.end());
+ }
+ return true;
+}
+
+bool DNSQuestion::editHeader(const std::function<bool(dnsheader&)>& editFunction)
+{
+ if (data.size() < sizeof(dnsheader)) {
+ throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
+ }
+ return dnsdist::PacketMangling::editDNSHeaderFromPacket(data, editFunction);
+}
+
+static void doLatencyStats(dnsdist::Protocol protocol, double udiff)
+{
+ constexpr auto doAvg = [](double& var, double n, double weight) {
+ var = (weight - 1) * var / weight + n / weight;
+ };
+
+ if (protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP) {
+ if (udiff < 1000) {
+ ++dnsdist::metrics::g_stats.latency0_1;
+ }
+ else if (udiff < 10000) {
+ ++dnsdist::metrics::g_stats.latency1_10;
+ }
+ else if (udiff < 50000) {
+ ++dnsdist::metrics::g_stats.latency10_50;
+ }
+ else if (udiff < 100000) {
+ ++dnsdist::metrics::g_stats.latency50_100;
+ }
+ else if (udiff < 1000000) {
+ ++dnsdist::metrics::g_stats.latency100_1000;
+ }
+ else {
+ ++dnsdist::metrics::g_stats.latencySlow;
+ }
+
+ dnsdist::metrics::g_stats.latencySum += static_cast<unsigned long>(udiff) / 1000;
+ ++dnsdist::metrics::g_stats.latencyCount;
+
+ doAvg(dnsdist::metrics::g_stats.latencyAvg100, udiff, 100);
+ doAvg(dnsdist::metrics::g_stats.latencyAvg1000, udiff, 1000);
+ doAvg(dnsdist::metrics::g_stats.latencyAvg10000, udiff, 10000);
+ doAvg(dnsdist::metrics::g_stats.latencyAvg1000000, udiff, 1000000);
+ }
+ else if (protocol == dnsdist::Protocol::DoTCP || protocol == dnsdist::Protocol::DNSCryptTCP) {
+ doAvg(dnsdist::metrics::g_stats.latencyTCPAvg100, udiff, 100);
+ doAvg(dnsdist::metrics::g_stats.latencyTCPAvg1000, udiff, 1000);
+ doAvg(dnsdist::metrics::g_stats.latencyTCPAvg10000, udiff, 10000);
+ doAvg(dnsdist::metrics::g_stats.latencyTCPAvg1000000, udiff, 1000000);
+ }
+ else if (protocol == dnsdist::Protocol::DoT) {
+ doAvg(dnsdist::metrics::g_stats.latencyDoTAvg100, udiff, 100);
+ doAvg(dnsdist::metrics::g_stats.latencyDoTAvg1000, udiff, 1000);
+ doAvg(dnsdist::metrics::g_stats.latencyDoTAvg10000, udiff, 10000);
+ doAvg(dnsdist::metrics::g_stats.latencyDoTAvg1000000, udiff, 1000000);
+ }
+ else if (protocol == dnsdist::Protocol::DoH) {
+ doAvg(dnsdist::metrics::g_stats.latencyDoHAvg100, udiff, 100);
+ doAvg(dnsdist::metrics::g_stats.latencyDoHAvg1000, udiff, 1000);
+ doAvg(dnsdist::metrics::g_stats.latencyDoHAvg10000, udiff, 10000);
+ doAvg(dnsdist::metrics::g_stats.latencyDoHAvg1000000, udiff, 1000000);
+ }
+ else if (protocol == dnsdist::Protocol::DoQ) {
+ doAvg(dnsdist::metrics::g_stats.latencyDoQAvg100, udiff, 100);
+ doAvg(dnsdist::metrics::g_stats.latencyDoQAvg1000, udiff, 1000);
+ doAvg(dnsdist::metrics::g_stats.latencyDoQAvg10000, udiff, 10000);
+ doAvg(dnsdist::metrics::g_stats.latencyDoQAvg1000000, udiff, 1000000);
+ }
+ else if (protocol == dnsdist::Protocol::DoH3) {
+ doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg100, udiff, 100);
+ doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg1000, udiff, 1000);
+ doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg10000, udiff, 10000);
+ doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg1000000, udiff, 1000000);
+ }
+}
+
+bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote)
+{
+ if (response.size() < sizeof(dnsheader)) {
+ return false;
+ }
+
+ const dnsheader_aligned dnsHeader(response.data());
+ if (dnsHeader->qr == 0) {
+ ++dnsdist::metrics::g_stats.nonCompliantResponses;
+ if (remote) {
+ ++remote->nonCompliantResponses;
+ }
+ return false;
+ }
+
+ if (dnsHeader->qdcount == 0) {
+ if ((dnsHeader->rcode != RCode::NoError && dnsHeader->rcode != RCode::NXDomain) || g_allowEmptyResponse) {
+ return true;
+ }
+
+ ++dnsdist::metrics::g_stats.nonCompliantResponses;
+ if (remote) {
+ ++remote->nonCompliantResponses;
+ }
+ return false;
+ }
+
+ uint16_t rqtype{};
+ uint16_t rqclass{};
+ DNSName rqname;
+ try {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ rqname = DNSName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, &rqtype, &rqclass);
+ }
+ catch (const std::exception& e) {
+ if (remote && !response.empty() && static_cast<size_t>(response.size()) > sizeof(dnsheader)) {
+ infolog("Backend %s sent us a response with id %d that did not parse: %s", remote->d_config.remote.toStringWithPort(), ntohs(dnsHeader->id), e.what());
+ }
+ ++dnsdist::metrics::g_stats.nonCompliantResponses;
+ if (remote) {
+ ++remote->nonCompliantResponses;
+ }
+ return false;
+ }
+
+ return rqtype == qtype && rqclass == qclass && rqname == qname;
+}
+
+static void restoreFlags(struct dnsheader* dnsHeader, uint16_t origFlags)
+{
+ static const uint16_t rdMask = 1 << FLAGS_RD_OFFSET;
+ static const uint16_t cdMask = 1 << FLAGS_CD_OFFSET;
+ static const uint16_t restoreFlagsMask = UINT16_MAX & ~(rdMask | cdMask);
+ uint16_t* flags = getFlagsFromDNSHeader(dnsHeader);
+ /* clear the flags we are about to restore */
+ *flags &= restoreFlagsMask;
+ /* only keep the flags we want to restore */
+ origFlags &= ~restoreFlagsMask;
+ /* set the saved flags as they were */
+ *flags |= origFlags;
+}
+
+static bool fixUpQueryTurnedResponse(DNSQuestion& dnsQuestion, const uint16_t origFlags)
+{
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [origFlags](dnsheader& header) {
+ restoreFlags(&header, origFlags);
+ return true;
+ });
+
+ return addEDNSToQueryTurnedResponse(dnsQuestion);
+}
+
+static bool fixUpResponse(PacketBuffer& response, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, bool* zeroScope)
+{
+ if (response.size() < sizeof(dnsheader)) {
+ return false;
+ }
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [origFlags](dnsheader& header) {
+ restoreFlags(&header, origFlags);
+ return true;
+ });
+
+ if (response.size() == sizeof(dnsheader)) {
+ return true;
+ }
+
+ if (g_fixupCase) {
+ const auto& realname = qname.getStorage();
+ if (response.size() >= (sizeof(dnsheader) + realname.length())) {
+ memcpy(&response.at(sizeof(dnsheader)), realname.c_str(), realname.length());
+ }
+ }
+
+ if (ednsAdded || ecsAdded) {
+ uint16_t optStart{};
+ size_t optLen = 0;
+ bool last = false;
+
+ int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+
+ if (res == 0) {
+ if (zeroScope != nullptr) { // this finds if an EDNS Client Subnet scope was set, and if it is 0
+ size_t optContentStart = 0;
+ uint16_t optContentLen = 0;
+ /* we need at least 4 bytes after the option length (family: 2, source prefix-length: 1, scope prefix-length: 1) */
+ if (isEDNSOptionInOpt(response, optStart, optLen, EDNSOptionCode::ECS, &optContentStart, &optContentLen) && optContentLen >= 4) {
+ /* see if the EDNS Client Subnet SCOPE PREFIX-LENGTH byte in position 3 is set to 0, which is the only thing
+ we care about. */
+ *zeroScope = response.at(optContentStart + 3) == 0;
+ }
+ }
+
+ if (ednsAdded) {
+ /* we added the entire OPT RR,
+ therefore we need to remove it entirely */
+ if (last) {
+ /* simply remove the last AR */
+ response.resize(response.size() - optLen);
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [](dnsheader& header) {
+ uint16_t arcount = ntohs(header.arcount);
+ arcount--;
+ header.arcount = htons(arcount);
+ return true;
+ });
+ }
+ else {
+ /* Removing an intermediary RR could lead to compression error */
+ PacketBuffer rewrittenResponse;
+ if (rewriteResponseWithoutEDNS(response, rewrittenResponse) == 0) {
+ response = std::move(rewrittenResponse);
+ }
+ else {
+ warnlog("Error rewriting content");
+ }
+ }
+ }
+ else {
+ /* the OPT RR was already present, but without ECS,
+ we need to remove the ECS option if any */
+ if (last) {
+ /* nothing after the OPT RR, we can simply remove the
+ ECS option */
+ size_t existingOptLen = optLen;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+ response.resize(response.size() - (existingOptLen - optLen));
+ }
+ else {
+ PacketBuffer rewrittenResponse;
+ /* Removing an intermediary RR could lead to compression error */
+ if (rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, rewrittenResponse) == 0) {
+ response = std::move(rewrittenResponse);
+ }
+ else {
+ warnlog("Error rewriting content");
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+#ifdef HAVE_DNSCRYPT
+static bool encryptResponse(PacketBuffer& response, size_t maximumSize, bool tcp, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery)
+{
+ if (dnsCryptQuery) {
+ int res = dnsCryptQuery->encryptResponse(response, maximumSize, tcp);
+ if (res != 0) {
+ /* dropping response */
+ vinfolog("Error encrypting the response, dropping.");
+ return false;
+ }
+ }
+ return true;
+}
+#endif /* HAVE_DNSCRYPT */
+
+static bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, DNSResponse& dnsResponse)
+{
+ DNSResponseAction::Action action = DNSResponseAction::Action::None;
+ std::string ruleresult;
+ for (const auto& rrule : respRuleActions) {
+ if (rrule.d_rule->matches(&dnsResponse)) {
+ ++rrule.d_rule->d_matches;
+ action = (*rrule.d_action)(&dnsResponse, &ruleresult);
+ switch (action) {
+ case DNSResponseAction::Action::Allow:
+ return true;
+ break;
+ case DNSResponseAction::Action::Drop:
+ return false;
+ break;
+ case DNSResponseAction::Action::HeaderModify:
+ return true;
+ break;
+ case DNSResponseAction::Action::ServFail:
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
+ header.rcode = RCode::ServFail;
+ return true;
+ });
+ return true;
+ break;
+ case DNSResponseAction::Action::Truncate:
+ if (!dnsResponse.overTCP()) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
+ header.tc = true;
+ header.qr = true;
+ return true;
+ });
+ truncateTC(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength());
+ ++dnsdist::metrics::g_stats.ruleTruncated;
+ return true;
+ }
+ break;
+ /* non-terminal actions follow */
+ case DNSResponseAction::Action::Delay:
+ pdns::checked_stoi_into(dnsResponse.ids.delayMsec, ruleresult); // sorry
+ break;
+ case DNSResponseAction::Action::None:
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool processResponseAfterRules(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted)
+{
+ bool zeroScope = false;
+ if (!fixUpResponse(response, dnsResponse.ids.qname, dnsResponse.ids.origFlags, dnsResponse.ids.ednsAdded, dnsResponse.ids.ecsAdded, dnsResponse.ids.useZeroScope ? &zeroScope : nullptr)) {
+ return false;
+ }
+
+ if (dnsResponse.ids.packetCache && !dnsResponse.ids.selfGenerated && !dnsResponse.ids.skipCache && (!dnsResponse.ids.forwardedOverUDP || response.size() <= s_maxUDPResponsePacketSize)) {
+ if (!dnsResponse.ids.useZeroScope) {
+ /* if the query was not suitable for zero-scope, for
+ example because it had an existing ECS entry so the hash is
+ not really 'no ECS', so just insert it for the existing subnet
+ since:
+ - we don't have the correct hash for a non-ECS query
+ - inserting with hash computed before the ECS replacement but with
+ the subnet extracted _after_ the replacement would not work.
+ */
+ zeroScope = false;
+ }
+ uint32_t cacheKey = dnsResponse.ids.cacheKey;
+ if (dnsResponse.ids.protocol == dnsdist::Protocol::DoH && dnsResponse.ids.forwardedOverUDP) {
+ cacheKey = dnsResponse.ids.cacheKeyUDP;
+ }
+ else if (zeroScope) {
+ // if zeroScope, pass the pre-ECS hash-key and do not pass the subnet to the cache
+ cacheKey = dnsResponse.ids.cacheKeyNoECS;
+ }
+
+ dnsResponse.ids.packetCache->insert(cacheKey, zeroScope ? boost::none : dnsResponse.ids.subnet, dnsResponse.ids.cacheFlags, dnsResponse.ids.dnssecOK, dnsResponse.ids.qname, dnsResponse.ids.qtype, dnsResponse.ids.qclass, response, dnsResponse.ids.forwardedOverUDP, dnsResponse.getHeader()->rcode, dnsResponse.ids.tempFailureTTL);
+
+ if (!applyRulesToResponse(cacheInsertedRespRuleActions, dnsResponse)) {
+ return false;
+ }
+ }
+
+ if (dnsResponse.ids.ttlCap > 0) {
+ std::string result;
+ LimitTTLResponseAction lrac(0, dnsResponse.ids.ttlCap, {});
+ lrac(&dnsResponse, &result);
+ }
+
+ if (dnsResponse.ids.d_extendedError) {
+ dnsdist::edns::addExtendedDNSError(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.d_extendedError->infoCode, dnsResponse.ids.d_extendedError->extraText);
+ }
+
+#ifdef HAVE_DNSCRYPT
+ if (!muted) {
+ if (!encryptResponse(response, dnsResponse.getMaximumSize(), dnsResponse.overTCP(), dnsResponse.ids.dnsCryptQuery)) {
+ return false;
+ }
+ }
+#endif /* HAVE_DNSCRYPT */
+
+ return true;
+}
+
+bool processResponse(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted)
+{
+ if (!applyRulesToResponse(respRuleActions, dnsResponse)) {
+ return false;
+ }
+
+ if (dnsResponse.isAsynchronous()) {
+ return true;
+ }
+
+ return processResponseAfterRules(response, cacheInsertedRespRuleActions, dnsResponse, muted);
+}
+
+static size_t getInitialUDPPacketBufferSize(bool expectProxyProtocol)
+{
+ static_assert(s_udpIncomingBufferSize <= s_initialUDPPacketBufferSize, "The incoming buffer size should not be larger than s_initialUDPPacketBufferSize");
+
+ if (!expectProxyProtocol || g_proxyProtocolACL.empty()) {
+ return s_initialUDPPacketBufferSize;
+ }
+
+ return s_initialUDPPacketBufferSize + g_proxyProtocolMaximumSize;
+}
+
+static size_t getMaximumIncomingPacketSize(const ClientState& clientState)
+{
+ if (clientState.dnscryptCtx) {
+ return getInitialUDPPacketBufferSize(clientState.d_enableProxyProtocol);
+ }
+
+ if (!clientState.d_enableProxyProtocol || g_proxyProtocolACL.empty()) {
+ return s_udpIncomingBufferSize;
+ }
+
+ return s_udpIncomingBufferSize + g_proxyProtocolMaximumSize;
+}
+
+bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
+{
+#ifndef DISABLE_DELAY_PIPE
+ if (delayMsec > 0 && g_delay != nullptr) {
+ DelayedPacket delayed{origFD, response, origRemote, origDest};
+ g_delay->submit(delayed, delayMsec);
+ return true;
+ }
+#endif /* DISABLE_DELAY_PIPE */
+ // NOLINTNEXTLINE(readability-suspicious-call-argument)
+ ssize_t res = sendfromto(origFD, response.data(), response.size(), 0, origDest, origRemote);
+ if (res == -1) {
+ int err = errno;
+ vinfolog("Error sending response to %s: %s", origRemote.toStringWithPort(), stringerror(err));
+ }
+
+ return true;
+}
+
+void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend)
+{
+ handleResponseSent(ids.qname, ids.qtype, udiff, client, backend, size, cleartextDH, outgoingProtocol, ids.protocol, fromBackend);
+}
+
+void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend)
+{
+ if (g_rings.shouldRecordResponses()) {
+ timespec now{};
+ gettime(&now);
+ g_rings.insertResponse(now, client, qname, qtype, static_cast<unsigned int>(udiff), size, cleartextDH, backend, outgoingProtocol);
+ }
+
+ switch (cleartextDH.rcode) {
+ case RCode::NXDomain:
+ ++dnsdist::metrics::g_stats.frontendNXDomain;
+ break;
+ case RCode::ServFail:
+ if (fromBackend) {
+ ++dnsdist::metrics::g_stats.servfailResponses;
+ }
+ ++dnsdist::metrics::g_stats.frontendServFail;
+ break;
+ case RCode::NoError:
+ ++dnsdist::metrics::g_stats.frontendNoError;
+ break;
+ }
+
+ doLatencyStats(incomingProtocol, udiff);
+}
+
+static void handleResponseForUDPClient(InternalQueryState& ids, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, const std::shared_ptr<DownstreamState>& backend, bool isAsync, bool selfGenerated)
+{
+ DNSResponse dnsResponse(ids, response, backend);
+
+ if (ids.udpPayloadSize > 0 && response.size() > ids.udpPayloadSize) {
+ vinfolog("Got a response of size %d while the initial UDP payload size was %d, truncating", response.size(), ids.udpPayloadSize);
+ truncateTC(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength());
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
+ header.tc = true;
+ return true;
+ });
+ }
+ else if (dnsResponse.getHeader()->tc && g_truncateTC) {
+ truncateTC(response, dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength());
+ }
+
+ /* when the answer is encrypted in place, we need to get a copy
+ of the original header before encryption to fill the ring buffer */
+ dnsheader cleartextDH{};
+ memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
+
+ if (!isAsync) {
+ if (!processResponse(response, respRuleActions, cacheInsertedRespRuleActions, dnsResponse, ids.cs != nullptr && ids.cs->muted)) {
+ return;
+ }
+
+ if (dnsResponse.isAsynchronous()) {
+ return;
+ }
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ if (ids.cs != nullptr) {
+ ++ids.cs->responses;
+ }
+
+ bool muted = true;
+ if (ids.cs != nullptr && !ids.cs->muted && !ids.isXSK()) {
+ sendUDPResponse(ids.cs->udpFD, response, dnsResponse.ids.delayMsec, ids.hopLocal, ids.hopRemote);
+ muted = false;
+ }
+
+ if (!selfGenerated) {
+ double udiff = ids.queryRealTime.udiff();
+ if (!muted) {
+ vinfolog("Got answer from %s, relayed to %s (UDP), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
+ }
+ else {
+ if (!ids.isXSK()) {
+ vinfolog("Got answer from %s, NOT relayed to %s (UDP) since that frontend is muted, took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
+ }
+ else {
+ vinfolog("Got answer from %s, relayed to %s (UDP via XSK), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
+ }
+ }
+
+ handleResponseSent(ids, udiff, dnsResponse.ids.origRemote, backend->d_config.remote, response.size(), cleartextDH, backend->getProtocol(), true);
+ }
+ else {
+ handleResponseSent(ids, 0., dnsResponse.ids.origRemote, ComboAddress(), response.size(), cleartextDH, dnsdist::Protocol::DoUDP, false);
+ }
+}
+
+bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& localRespRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids)
+{
+
+ const dnsheader_aligned dnsHeader(response.data());
+ auto queryId = dnsHeader->id;
+
+ if (!responseContentMatches(response, ids.qname, ids.qtype, ids.qclass, dss)) {
+ dss->restoreState(queryId, std::move(ids));
+ return false;
+ }
+
+ auto dohUnit = std::move(ids.du);
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [&ids](dnsheader& header) {
+ header.id = ids.origID;
+ return true;
+ });
+ ++dss->responses;
+
+ double udiff = ids.queryRealTime.udiff();
+ // do that _before_ the processing, otherwise it's not fair to the backend
+ dss->latencyUsec = (127.0 * dss->latencyUsec / 128.0) + udiff / 128.0;
+ dss->reportResponse(dnsHeader->rcode);
+
+ /* don't call processResponse for DOH */
+ if (dohUnit) {
+#ifdef HAVE_DNS_OVER_HTTPS
+ // DoH query, we cannot touch dohUnit after that
+ DOHUnitInterface::handleUDPResponse(std::move(dohUnit), std::move(response), std::move(ids), dss);
+#endif
+ return false;
+ }
+
+ handleResponseForUDPClient(ids, response, localRespRuleActions, cacheInsertedRespRuleActions, dss, false, false);
+ return true;
+}
+
+// listens on a dedicated socket, lobs answers from downstream servers to original requestors
+void responderThread(std::shared_ptr<DownstreamState> dss)
+{
+ try {
+ setThreadName("dnsdist/respond");
+ auto localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+ auto localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+ const size_t initialBufferSize = getInitialUDPPacketBufferSize(false);
+ /* allocate one more byte so we can detect truncation */
+ PacketBuffer response(initialBufferSize + 1);
+ uint16_t queryId = 0;
+ std::vector<int> sockets;
+ sockets.reserve(dss->sockets.size());
+
+ for (;;) {
+ try {
+ if (dss->isStopped()) {
+ break;
+ }
+
+ if (!dss->connected) {
+ /* the sockets are not connected yet, likely because we detected a problem,
+ tried to reconnect and it failed. We will try to reconnect after the next
+ successful health-check (unless reconnectOnUp is false), or when trying
+ to send in the UDP listener thread, but until then we simply need to wait. */
+ dss->waitUntilConnected();
+ continue;
+ }
+
+ dss->pickSocketsReadyForReceiving(sockets);
+
+ /* check a second time here because we might have waited quite a bit
+ since the first check */
+ if (dss->isStopped()) {
+ break;
+ }
+
+ for (const auto& sockDesc : sockets) {
+ /* allocate one more byte so we can detect truncation */
+ // NOLINTNEXTLINE(bugprone-use-after-move): resizing a vector has no preconditions so it is valid to do so after moving it
+ response.resize(initialBufferSize + 1);
+ ssize_t got = recv(sockDesc, response.data(), response.size(), 0);
+
+ if (got == 0 && dss->isStopped()) {
+ break;
+ }
+
+ if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader) || static_cast<size_t>(got) == (initialBufferSize + 1)) {
+ continue;
+ }
+
+ response.resize(static_cast<size_t>(got));
+ const dnsheader_aligned dnsHeader(response.data());
+ queryId = dnsHeader->id;
+
+ auto ids = dss->getState(queryId);
+ if (!ids) {
+ continue;
+ }
+
+ if (!ids->isXSK() && sockDesc != ids->backendFD) {
+ dss->restoreState(queryId, std::move(*ids));
+ continue;
+ }
+
+ if (processResponderPacket(dss, response, *localRespRuleActions, *localCacheInsertedRespRuleActions, std::move(*ids)) && ids->isXSK() && ids->cs->xskInfo) {
+#ifdef HAVE_XSK
+ auto& xskInfo = ids->cs->xskInfo;
+ auto xskPacket = xskInfo->getEmptyFrame();
+ if (!xskPacket) {
+ continue;
+ }
+ xskPacket->setHeader(ids->xskPacketHeader);
+ if (!xskPacket->setPayload(response)) {
+ }
+ if (ids->delayMsec > 0) {
+ xskPacket->addDelay(ids->delayMsec);
+ }
+ xskPacket->updatePacket();
+ xskInfo->pushToSendQueue(*xskPacket);
+ xskInfo->notifyXskSocket();
+#endif /* HAVE_XSK */
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got an error in UDP responder thread while parsing a response from %s, id %d: %s", dss->d_config.remote.toStringWithPort(), queryId, e.what());
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ errlog("UDP responder thread died because of exception: %s", e.what());
+ }
+ catch (const PDNSException& e) {
+ errlog("UDP responder thread died because of PowerDNS exception: %s", e.reason);
+ }
+ catch (...) {
+ errlog("UDP responder thread died because of an exception: %s", "unknown");
+ }
+}
+
+LockGuarded<LuaContext> g_lua{LuaContext()};
+ComboAddress g_serverControl{"127.0.0.1:5199"};
+
+static void spoofResponseFromString(DNSQuestion& dnsQuestion, const string& spoofContent, bool raw)
+{
+ string result;
+
+ if (raw) {
+ std::vector<std::string> raws;
+ stringtok(raws, spoofContent, ",");
+ SpoofAction tempSpoofAction(raws, std::nullopt);
+ tempSpoofAction(&dnsQuestion, &result);
+ }
+ else {
+ std::vector<std::string> addrs;
+ stringtok(addrs, spoofContent, " ,");
+
+ if (addrs.size() == 1) {
+ try {
+ ComboAddress spoofAddr(spoofContent);
+ SpoofAction tempSpoofAction({spoofAddr});
+ tempSpoofAction(&dnsQuestion, &result);
+ }
+ catch (const PDNSException& e) {
+ DNSName cname(spoofContent);
+ SpoofAction tempSpoofAction(cname); // CNAME then
+ tempSpoofAction(&dnsQuestion, &result);
+ }
+ }
+ else {
+ std::vector<ComboAddress> cas;
+ for (const auto& addr : addrs) {
+ try {
+ cas.emplace_back(addr);
+ }
+ catch (...) {
+ }
+ }
+ SpoofAction tempSpoofAction(cas);
+ tempSpoofAction(&dnsQuestion, &result);
+ }
+ }
+}
+
+static void spoofPacketFromString(DNSQuestion& dnsQuestion, const string& spoofContent)
+{
+ string result;
+
+ SpoofAction tempSpoofAction(spoofContent.c_str(), spoofContent.size());
+ tempSpoofAction(&dnsQuestion, &result);
+}
+
+bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dnsQuestion, std::string& ruleresult, bool& drop)
+{
+ if (dnsQuestion.isAsynchronous()) {
+ return false;
+ }
+
+ auto setRCode = [&dnsQuestion](uint8_t rcode) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [rcode](dnsheader& header) {
+ header.rcode = rcode;
+ header.qr = true;
+ return true;
+ });
+ };
+
+ switch (action) {
+ case DNSAction::Action::Allow:
+ return true;
+ break;
+ case DNSAction::Action::Drop:
+ ++dnsdist::metrics::g_stats.ruleDrop;
+ drop = true;
+ return true;
+ break;
+ case DNSAction::Action::Nxdomain:
+ setRCode(RCode::NXDomain);
+ return true;
+ break;
+ case DNSAction::Action::Refused:
+ setRCode(RCode::Refused);
+ return true;
+ break;
+ case DNSAction::Action::ServFail:
+ setRCode(RCode::ServFail);
+ return true;
+ break;
+ case DNSAction::Action::Spoof:
+ spoofResponseFromString(dnsQuestion, ruleresult, false);
+ return true;
+ break;
+ case DNSAction::Action::SpoofPacket:
+ spoofPacketFromString(dnsQuestion, ruleresult);
+ return true;
+ break;
+ case DNSAction::Action::SpoofRaw:
+ spoofResponseFromString(dnsQuestion, ruleresult, true);
+ return true;
+ break;
+ case DNSAction::Action::Truncate:
+ if (!dnsQuestion.overTCP()) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.tc = true;
+ header.qr = true;
+ header.ra = header.rd;
+ header.aa = false;
+ header.ad = false;
+ return true;
+ });
+ ++dnsdist::metrics::g_stats.ruleTruncated;
+ return true;
+ }
+ break;
+ case DNSAction::Action::HeaderModify:
+ return true;
+ break;
+ case DNSAction::Action::Pool:
+ /* we need to keep this because a custom Lua action can return
+ DNSAction.Spoof, 'poolname' */
+ dnsQuestion.ids.poolName = ruleresult;
+ return true;
+ break;
+ case DNSAction::Action::NoRecurse:
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.rd = false;
+ return true;
+ });
+ return true;
+ break;
+ /* non-terminal actions follow */
+ case DNSAction::Action::Delay:
+ pdns::checked_stoi_into(dnsQuestion.ids.delayMsec, ruleresult); // sorry
+ break;
+ case DNSAction::Action::None:
+ /* fall-through */
+ case DNSAction::Action::NoOp:
+ break;
+ }
+
+ /* false means that we don't stop the processing */
+ return false;
+}
+
+static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dnsQuestion, const struct timespec& now)
+{
+ if (g_rings.shouldRecordQueries()) {
+ g_rings.insertQuery(now, dnsQuestion.ids.origRemote, dnsQuestion.ids.qname, dnsQuestion.ids.qtype, dnsQuestion.getData().size(), *dnsQuestion.getHeader(), dnsQuestion.getProtocol());
+ }
+
+ if (g_qcount.enabled) {
+ string qname = dnsQuestion.ids.qname.toLogString();
+ bool countQuery{true};
+ if (g_qcount.filter) {
+ auto lock = g_lua.lock();
+ std::tie(countQuery, qname) = g_qcount.filter(&dnsQuestion);
+ }
+
+ if (countQuery) {
+ auto records = g_qcount.records.write_lock();
+ if (records->count(qname) == 0) {
+ (*records)[qname] = 0;
+ }
+ (*records)[qname]++;
+ }
+ }
+
+#ifndef DISABLE_DYNBLOCKS
+ auto setRCode = [&dnsQuestion](uint8_t rcode) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [rcode](dnsheader& header) {
+ header.rcode = rcode;
+ header.qr = true;
+ return true;
+ });
+ };
+
+ /* the Dynamic Block mechanism supports address and port ranges, so we need to pass the full address and port */
+ if (auto* got = holders.dynNMGBlock->lookup(AddressAndPortRange(dnsQuestion.ids.origRemote, dnsQuestion.ids.origRemote.isIPv4() ? 32 : 128, 16))) {
+ auto updateBlockStats = [&got]() {
+ ++dnsdist::metrics::g_stats.dynBlocked;
+ got->second.blocks++;
+ };
+
+ if (now < got->second.until) {
+ DNSAction::Action action = got->second.action;
+ if (action == DNSAction::Action::None) {
+ action = g_dynBlockAction;
+ }
+
+ switch (action) {
+ case DNSAction::Action::NoOp:
+ /* do nothing */
+ break;
+
+ case DNSAction::Action::Nxdomain:
+ vinfolog("Query from %s turned into NXDomain because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+ updateBlockStats();
+
+ setRCode(RCode::NXDomain);
+ return true;
+
+ case DNSAction::Action::Refused:
+ vinfolog("Query from %s refused because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+ updateBlockStats();
+
+ setRCode(RCode::Refused);
+ return true;
+
+ case DNSAction::Action::Truncate:
+ if (!dnsQuestion.overTCP()) {
+ updateBlockStats();
+ vinfolog("Query from %s truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.tc = true;
+ header.qr = true;
+ header.ra = header.rd;
+ header.aa = false;
+ header.ad = false;
+ return true;
+ });
+ return true;
+ }
+ else {
+ vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+ }
+ break;
+ case DNSAction::Action::NoRecurse:
+ updateBlockStats();
+ vinfolog("Query from %s setting rd=0 because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.rd = false;
+ return true;
+ });
+ return true;
+ default:
+ updateBlockStats();
+ vinfolog("Query from %s dropped because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+ return false;
+ }
+ }
+ }
+
+ if (auto* got = holders.dynSMTBlock->lookup(dnsQuestion.ids.qname)) {
+ auto updateBlockStats = [&got]() {
+ ++dnsdist::metrics::g_stats.dynBlocked;
+ got->blocks++;
+ };
+
+ if (now < got->until) {
+ DNSAction::Action action = got->action;
+ if (action == DNSAction::Action::None) {
+ action = g_dynBlockAction;
+ }
+ switch (action) {
+ case DNSAction::Action::NoOp:
+ /* do nothing */
+ break;
+ case DNSAction::Action::Nxdomain:
+ vinfolog("Query from %s for %s turned into NXDomain because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+ updateBlockStats();
+
+ setRCode(RCode::NXDomain);
+ return true;
+ case DNSAction::Action::Refused:
+ vinfolog("Query from %s for %s refused because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+ updateBlockStats();
+
+ setRCode(RCode::Refused);
+ return true;
+ case DNSAction::Action::Truncate:
+ if (!dnsQuestion.overTCP()) {
+ updateBlockStats();
+
+ vinfolog("Query from %s for %s truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.tc = true;
+ header.qr = true;
+ header.ra = header.rd;
+ header.aa = false;
+ header.ad = false;
+ return true;
+ });
+ return true;
+ }
+ else {
+ vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+ }
+ break;
+ case DNSAction::Action::NoRecurse:
+ updateBlockStats();
+ vinfolog("Query from %s setting rd=0 because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.rd = false;
+ return true;
+ });
+ return true;
+ default:
+ updateBlockStats();
+ vinfolog("Query from %s for %s dropped because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+ return false;
+ }
+ }
+ }
+#endif /* DISABLE_DYNBLOCKS */
+
+ DNSAction::Action action = DNSAction::Action::None;
+ string ruleresult;
+ bool drop = false;
+ for (const auto& rule : *holders.ruleactions) {
+ if (rule.d_rule->matches(&dnsQuestion)) {
+ rule.d_rule->d_matches++;
+ action = (*rule.d_action)(&dnsQuestion, &ruleresult);
+ if (processRulesResult(action, dnsQuestion, ruleresult, drop)) {
+ break;
+ }
+ }
+ }
+
+ return !drop;
+}
+
+ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& backend, const int socketDesc, const PacketBuffer& request, bool healthCheck)
+{
+ ssize_t result = 0;
+
+ if (backend->d_config.sourceItf == 0) {
+ result = send(socketDesc, request.data(), request.size(), 0);
+ }
+ else {
+ msghdr msgh{};
+ iovec iov{};
+ cmsgbuf_aligned cbuf;
+ ComboAddress remote(backend->d_config.remote);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
+ fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), const_cast<char*>(reinterpret_cast<const char*>(request.data())), request.size(), &remote);
+ addCMsgSrcAddr(&msgh, &cbuf, &backend->d_config.sourceAddr, static_cast<int>(backend->d_config.sourceItf));
+ result = sendmsg(socketDesc, &msgh, 0);
+ }
+
+ if (result == -1) {
+ int savederrno = errno;
+ vinfolog("Error sending request to backend %s: %s", backend->d_config.remote.toStringWithPort(), stringerror(savederrno));
+
+ /* This might sound silly, but on Linux send() might fail with EINVAL
+ if the interface the socket was bound to doesn't exist anymore.
+ We don't want to reconnect the real socket if the healthcheck failed,
+ because it's not using the same socket.
+ */
+ if (!healthCheck) {
+ if (savederrno == EINVAL || savederrno == ENODEV || savederrno == ENETUNREACH || savederrno == EHOSTUNREACH || savederrno == EBADF) {
+ backend->reconnect();
+ }
+ backend->reportTimeoutOrError();
+ }
+ }
+
+ return result;
+}
+
+static bool isUDPQueryAcceptable(ClientState& clientState, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, bool& expectProxyProtocol)
+{
+ if ((msgh->msg_flags & MSG_TRUNC) != 0) {
+ /* message was too large for our buffer */
+ vinfolog("Dropping message too large for our buffer");
+ ++clientState.nonCompliantQueries;
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ return false;
+ }
+
+ expectProxyProtocol = clientState.d_enableProxyProtocol && expectProxyProtocolFrom(remote);
+ if (!holders.acl->match(remote) && !expectProxyProtocol) {
+ vinfolog("Query from %s dropped because of ACL", remote.toStringWithPort());
+ ++dnsdist::metrics::g_stats.aclDrops;
+ return false;
+ }
+
+ if (HarvestDestinationAddress(msgh, &dest)) {
+ /* so it turns out that sometimes the kernel lies to us:
+ the address is set to 0.0.0.0:0 which makes our sendfromto() use
+ the wrong address. In that case it's better to let the kernel
+ do the work by itself and use sendto() instead.
+ This is indicated by setting the family to 0 which is acted upon
+ in sendUDPResponse() and DelayedPacket::().
+ */
+ const ComboAddress bogusV4("0.0.0.0:0");
+ const ComboAddress bogusV6("[::]:0");
+ if ((dest.sin4.sin_family == AF_INET && dest == bogusV4) || (dest.sin4.sin_family == AF_INET6 && dest == bogusV6)) {
+ dest.sin4.sin_family = 0;
+ }
+ else {
+ /* we don't get the port, only the address */
+ dest.sin4.sin_port = clientState.local.sin4.sin_port;
+ }
+ }
+ else {
+ dest.sin4.sin_family = 0;
+ }
+
+ ++clientState.queries;
+ ++dnsdist::metrics::g_stats.queries;
+
+ return true;
+}
+
+bool checkDNSCryptQuery(const ClientState& clientState, PacketBuffer& query, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp)
+{
+ if (clientState.dnscryptCtx) {
+#ifdef HAVE_DNSCRYPT
+ PacketBuffer response;
+ dnsCryptQuery = std::make_unique<DNSCryptQuery>(clientState.dnscryptCtx);
+
+ bool decrypted = handleDNSCryptQuery(query, *dnsCryptQuery, tcp, now, response);
+
+ if (!decrypted) {
+ if (!response.empty()) {
+ query = std::move(response);
+ return true;
+ }
+ throw std::runtime_error("Unable to decrypt DNSCrypt query, dropping.");
+ }
+#endif /* HAVE_DNSCRYPT */
+ }
+ return false;
+}
+
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState)
+{
+ if (dnsHeader.qr) { // don't respond to responses
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ return false;
+ }
+
+ if (dnsHeader.qdcount == 0) {
+ ++dnsdist::metrics::g_stats.emptyQueries;
+ if (g_dropEmptyQueries) {
+ return false;
+ }
+ }
+
+ if (dnsHeader.rd) {
+ ++dnsdist::metrics::g_stats.rdQueries;
+ }
+
+ return true;
+}
+
+#if !defined(DISABLE_RECVMMSG) && defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+static void queueResponse(const ClientState& clientState, const PacketBuffer& response, const ComboAddress& dest, const ComboAddress& remote, struct mmsghdr& outMsg, struct iovec* iov, cmsgbuf_aligned* cbuf)
+{
+ outMsg.msg_len = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): API
+ fillMSGHdr(&outMsg.msg_hdr, iov, nullptr, 0, const_cast<char*>(reinterpret_cast<const char*>(&response.at(0))), response.size(), const_cast<ComboAddress*>(&remote));
+
+ if (dest.sin4.sin_family == 0) {
+ outMsg.msg_hdr.msg_control = nullptr;
+ }
+ else {
+ addCMsgSrcAddr(&outMsg.msg_hdr, cbuf, &dest, 0);
+ }
+}
+#elif !defined(HAVE_RECVMMSG)
+struct mmsghdr
+{
+ msghdr msg_hdr;
+ unsigned int msg_len{0};
+};
+#endif
+
+/* self-generated responses or cache hits */
+static bool prepareOutgoingResponse(LocalHolders& holders, const ClientState& clientState, DNSQuestion& dnsQuestion, bool cacheHit)
+{
+ std::shared_ptr<DownstreamState> backend{nullptr};
+ DNSResponse dnsResponse(dnsQuestion.ids, dnsQuestion.getMutableData(), backend);
+ dnsResponse.d_incomingTCPState = dnsQuestion.d_incomingTCPState;
+ dnsResponse.ids.selfGenerated = true;
+
+ if (!applyRulesToResponse(cacheHit ? *holders.cacheHitRespRuleactions : *holders.selfAnsweredRespRuleactions, dnsResponse)) {
+ return false;
+ }
+
+ if (dnsResponse.ids.ttlCap > 0) {
+ std::string result;
+ LimitTTLResponseAction ltrac(0, dnsResponse.ids.ttlCap, {});
+ ltrac(&dnsResponse, &result);
+ }
+
+ if (dnsResponse.ids.d_extendedError) {
+ dnsdist::edns::addExtendedDNSError(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.d_extendedError->infoCode, dnsResponse.ids.d_extendedError->extraText);
+ }
+
+ if (cacheHit) {
+ ++dnsdist::metrics::g_stats.cacheHits;
+ }
+
+ if (dnsResponse.isAsynchronous()) {
+ return false;
+ }
+
+#ifdef HAVE_DNSCRYPT
+ if (!clientState.muted) {
+ if (!encryptResponse(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnsQuestion.overTCP(), dnsQuestion.ids.dnsCryptQuery)) {
+ return false;
+ }
+ }
+#endif /* HAVE_DNSCRYPT */
+
+ return true;
+}
+
+ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+{
+ const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
+
+ try {
+ if (dnsQuestion.getHeader()->qr) { // something turned it into a response
+ fixUpQueryTurnedResponse(dnsQuestion, dnsQuestion.ids.origFlags);
+
+ if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, false)) {
+ return ProcessQueryResult::Drop;
+ }
+
+ const auto rcode = dnsQuestion.getHeader()->rcode;
+ if (rcode == RCode::NXDomain) {
+ ++dnsdist::metrics::g_stats.ruleNXDomain;
+ }
+ else if (rcode == RCode::Refused) {
+ ++dnsdist::metrics::g_stats.ruleRefused;
+ }
+ else if (rcode == RCode::ServFail) {
+ ++dnsdist::metrics::g_stats.ruleServFail;
+ }
+
+ ++dnsdist::metrics::g_stats.selfAnswered;
+ ++dnsQuestion.ids.cs->responses;
+ return ProcessQueryResult::SendAnswer;
+ }
+ std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dnsQuestion.ids.poolName);
+ std::shared_ptr<ServerPolicy> poolPolicy = serverPool->policy;
+ dnsQuestion.ids.packetCache = serverPool->packetCache;
+ const auto& policy = poolPolicy != nullptr ? *poolPolicy : *(holders.policy);
+ const auto servers = serverPool->getServers();
+ selectedBackend = policy.getSelectedBackend(*servers, dnsQuestion);
+
+ uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL;
+
+ if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache) {
+ dnsQuestion.ids.dnssecOK = (getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO) != 0;
+ }
+
+ if (dnsQuestion.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool->getECS()))) {
+ // we special case our cache in case a downstream explicitly gave us a universally valid response with a 0 scope
+ // we need ECS parsing (parseECS) to be true so we can be sure that the initial incoming query did not have an existing
+ // ECS option, which would make it unsuitable for the zero-scope feature.
+ if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dnsQuestion.ids.packetCache->isECSParsingEnabled()) {
+ if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyNoECS, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, !dnsQuestion.overTCP(), allowExpired, false, true, false)) {
+
+ vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
+
+ if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, true)) {
+ return ProcessQueryResult::Drop;
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ ++dnsQuestion.ids.cs->responses;
+ return ProcessQueryResult::SendAnswer;
+ }
+
+ if (!dnsQuestion.ids.subnet) {
+ /* there was no existing ECS on the query, enable the zero-scope feature */
+ dnsQuestion.ids.useZeroScope = true;
+ }
+ }
+
+ if (!handleEDNSClientSubnet(dnsQuestion, dnsQuestion.ids.ednsAdded, dnsQuestion.ids.ecsAdded)) {
+ vinfolog("Dropping query from %s because we couldn't insert the ECS value", dnsQuestion.ids.origRemote.toStringWithPort());
+ return ProcessQueryResult::Drop;
+ }
+ }
+
+ if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache) {
+ bool forwardedOverUDP = !dnsQuestion.overTCP();
+ if (selectedBackend && selectedBackend->isTCPOnly()) {
+ forwardedOverUDP = false;
+ }
+
+ /* we do not record a miss for queries received over DoH and forwarded over TCP
+ yet, as we will do a second-lookup */
+ if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKey, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, forwardedOverUDP, allowExpired, false, true, dnsQuestion.ids.protocol != dnsdist::Protocol::DoH || forwardedOverUDP)) {
+
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [flags = dnsQuestion.ids.origFlags](dnsheader& header) {
+ restoreFlags(&header, flags);
+ return true;
+ });
+
+ vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
+
+ if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, true)) {
+ return ProcessQueryResult::Drop;
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ ++dnsQuestion.ids.cs->responses;
+ return ProcessQueryResult::SendAnswer;
+ }
+ if (dnsQuestion.ids.protocol == dnsdist::Protocol::DoH && !forwardedOverUDP) {
+ /* do a second-lookup for UDP responses, but we do not want TC=1 answers */
+ if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyUDP, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, true, allowExpired, false, false, true)) {
+ if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, true)) {
+ return ProcessQueryResult::Drop;
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ ++dnsQuestion.ids.cs->responses;
+ return ProcessQueryResult::SendAnswer;
+ }
+ }
+
+ vinfolog("Packet cache miss for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
+
+ ++dnsdist::metrics::g_stats.cacheMisses;
+ }
+
+ if (!selectedBackend) {
+ ++dnsdist::metrics::g_stats.noPolicy;
+
+ vinfolog("%s query for %s|%s from %s, no downstream server available", g_servFailOnNoPolicy ? "ServFailed" : "Dropped", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort());
+ if (g_servFailOnNoPolicy) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+ header.rcode = RCode::ServFail;
+ header.qr = true;
+ return true;
+ });
+
+ fixUpQueryTurnedResponse(dnsQuestion, dnsQuestion.ids.origFlags);
+
+ if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, false)) {
+ return ProcessQueryResult::Drop;
+ }
+ ++dnsdist::metrics::g_stats.responses;
+ ++dnsQuestion.ids.cs->responses;
+ // no response-only statistics counter to update.
+ return ProcessQueryResult::SendAnswer;
+ }
+
+ return ProcessQueryResult::Drop;
+ }
+
+ /* save the DNS flags as sent to the backend so we can cache the answer with the right flags later */
+ dnsQuestion.ids.cacheFlags = *getFlagsFromDNSHeader(dnsQuestion.getHeader().get());
+
+ if (dnsQuestion.addXPF && selectedBackend->d_config.xpfRRCode != 0) {
+ addXPF(dnsQuestion, selectedBackend->d_config.xpfRRCode);
+ }
+
+ if (selectedBackend->d_config.useProxyProtocol && dnsQuestion.getProtocol().isEncrypted() && selectedBackend->d_config.d_proxyProtocolAdvertiseTLS) {
+ if (!dnsQuestion.proxyProtocolValues) {
+ dnsQuestion.proxyProtocolValues = std::make_unique<std::vector<ProxyProtocolValue>>();
+ }
+ dnsQuestion.proxyProtocolValues->push_back(ProxyProtocolValue{"", static_cast<uint8_t>(ProxyProtocolValue::Types::PP_TLV_SSL)});
+ }
+
+ selectedBackend->incQueriesCount();
+ return ProcessQueryResult::PassToBackend;
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got an error while parsing a %s query (after applying rules) from %s, id %d: %s", (dnsQuestion.overTCP() ? "TCP" : "UDP"), dnsQuestion.ids.origRemote.toStringWithPort(), queryId, e.what());
+ }
+ return ProcessQueryResult::Drop;
+}
+
+class UDPTCPCrossQuerySender : public TCPQuerySender
+{
+public:
+ UDPTCPCrossQuerySender() = default;
+ UDPTCPCrossQuerySender(const UDPTCPCrossQuerySender&) = delete;
+ UDPTCPCrossQuerySender& operator=(const UDPTCPCrossQuerySender&) = delete;
+ UDPTCPCrossQuerySender(UDPTCPCrossQuerySender&&) = default;
+ UDPTCPCrossQuerySender& operator=(UDPTCPCrossQuerySender&&) = default;
+ ~UDPTCPCrossQuerySender() override = default;
+
+ [[nodiscard]] bool active() const override
+ {
+ return true;
+ }
+
+ void handleResponse(const struct timeval& now, TCPResponse&& response) override
+ {
+ if (!response.d_ds && !response.d_idstate.selfGenerated) {
+ throw std::runtime_error("Passing a cross-protocol answer originated from UDP without a valid downstream");
+ }
+
+ auto& ids = response.d_idstate;
+
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+
+ handleResponseForUDPClient(ids, response.d_buffer, *localRespRuleActions, *localCacheInsertedRespRuleActions, response.d_ds, response.isAsync(), response.d_idstate.selfGenerated);
+ }
+
+ void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+ {
+ return handleResponse(now, std::move(response));
+ }
+
+ void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
+ {
+ // nothing to do
+ }
+};
+
+class UDPCrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+ UDPCrossProtocolQuery(PacketBuffer&& buffer_, InternalQueryState&& ids_, std::shared_ptr<DownstreamState> backend) :
+ CrossProtocolQuery(InternalQuery(std::move(buffer_), std::move(ids_)), backend)
+ {
+ auto& ids = query.d_idstate;
+ const auto& buffer = query.d_buffer;
+
+ if (ids.udpPayloadSize == 0) {
+ uint16_t zValue = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(buffer.data()), buffer.size(), &ids.udpPayloadSize, &zValue);
+ if (ids.udpPayloadSize < 512) {
+ ids.udpPayloadSize = 512;
+ }
+ }
+ }
+ UDPCrossProtocolQuery(const UDPCrossProtocolQuery&) = delete;
+ UDPCrossProtocolQuery& operator=(const UDPCrossProtocolQuery&) = delete;
+ UDPCrossProtocolQuery(UDPCrossProtocolQuery&&) = delete;
+ UDPCrossProtocolQuery& operator=(UDPCrossProtocolQuery&&) = delete;
+ ~UDPCrossProtocolQuery() override = default;
+
+ std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+ {
+ return s_sender;
+ }
+
+private:
+ static std::shared_ptr<UDPTCPCrossQuerySender> s_sender;
+};
+
+std::shared_ptr<UDPTCPCrossQuerySender> UDPCrossProtocolQuery::s_sender = std::make_shared<UDPTCPCrossQuerySender>();
+
+std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion);
+std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion)
+{
+ dnsQuestion.ids.origID = dnsQuestion.getHeader()->id;
+ return std::make_unique<UDPCrossProtocolQuery>(std::move(dnsQuestion.getMutableData()), std::move(dnsQuestion.ids), nullptr);
+}
+
+ProcessQueryResult processQuery(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+{
+ const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
+
+ try {
+ /* we need an accurate ("real") value for the response and
+ to store into the IDS, but not for insertion into the
+ rings for example */
+ timespec now{};
+ gettime(&now);
+
+ if (!applyRulesToQuery(holders, dnsQuestion, now)) {
+ return ProcessQueryResult::Drop;
+ }
+
+ if (dnsQuestion.isAsynchronous()) {
+ return ProcessQueryResult::Asynchronous;
+ }
+
+ return processQueryAfterRules(dnsQuestion, holders, selectedBackend);
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got an error while parsing a %s query from %s, id %d: %s", (dnsQuestion.overTCP() ? "TCP" : "UDP"), dnsQuestion.ids.origRemote.toStringWithPort(), queryId, e.what());
+ }
+ return ProcessQueryResult::Drop;
+}
+
+bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend)
+{
+ bool doh = dnsQuestion.ids.du != nullptr;
+
+ bool failed = false;
+ if (downstream->d_config.useProxyProtocol) {
+ try {
+ addProxyProtocol(dnsQuestion, &dnsQuestion.ids.d_proxyProtocolPayloadSize);
+ }
+ catch (const std::exception& e) {
+ vinfolog("Adding proxy protocol payload to %s query from %s failed: %s", (dnsQuestion.ids.du ? "DoH" : ""), dnsQuestion.ids.origDest.toStringWithPort(), e.what());
+ return false;
+ }
+ }
+
+ if (doh && !dnsQuestion.ids.d_packet) {
+ dnsQuestion.ids.d_packet = std::make_unique<PacketBuffer>(query);
+ }
+
+ try {
+ int descriptor = downstream->pickSocketForSending();
+ if (actuallySend) {
+ dnsQuestion.ids.backendFD = descriptor;
+ }
+ dnsQuestion.ids.origID = queryID;
+ dnsQuestion.ids.forwardedOverUDP = true;
+
+ vinfolog("Got query for %s|%s from %s%s, relayed to %s%s", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), (doh ? " (https)" : ""), downstream->getNameWithAddr(), actuallySend ? "" : " (xsk)");
+
+ /* make a copy since we cannot touch dnsQuestion.ids after the move */
+ auto proxyProtocolPayloadSize = dnsQuestion.ids.d_proxyProtocolPayloadSize;
+ auto idOffset = downstream->saveState(std::move(dnsQuestion.ids));
+ /* set the correct ID */
+ memcpy(&query.at(proxyProtocolPayloadSize), &idOffset, sizeof(idOffset));
+
+ if (!actuallySend) {
+ return true;
+ }
+
+ /* you can't touch ids or du after this line, unless the call returned a non-negative value,
+ because it might already have been freed */
+ ssize_t ret = udpClientSendRequestToBackend(downstream, descriptor, query);
+
+ if (ret < 0) {
+ failed = true;
+ }
+
+ if (failed) {
+ /* clear up the state. In the very unlikely event it was reused
+ in the meantime, so be it. */
+ auto cleared = downstream->getState(idOffset);
+ if (cleared) {
+ dnsQuestion.ids.du = std::move(cleared->du);
+ }
+ ++dnsdist::metrics::g_stats.downstreamSendErrors;
+ ++downstream->sendErrors;
+ return false;
+ }
+ }
+ catch (const std::exception& e) {
+ throw;
+ }
+
+ return true;
+}
+
+static void processUDPQuery(ClientState& clientState, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, PacketBuffer& query, std::vector<mmsghdr>* responsesVect, unsigned int* queuedResponses, struct iovec* respIOV, cmsgbuf_aligned* respCBuf)
+{
+ assert(responsesVect == nullptr || (queuedResponses != nullptr && respIOV != nullptr && respCBuf != nullptr));
+ uint16_t queryId = 0;
+ InternalQueryState ids;
+ ids.cs = &clientState;
+ ids.origRemote = remote;
+ ids.hopRemote = remote;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ try {
+ bool expectProxyProtocol = false;
+ if (!isUDPQueryAcceptable(clientState, holders, msgh, remote, dest, expectProxyProtocol)) {
+ return;
+ }
+ /* dest might have been updated, if we managed to harvest the destination address */
+ if (dest.sin4.sin_family != 0) {
+ ids.origDest = dest;
+ ids.hopLocal = dest;
+ }
+ else {
+ /* if we have not been able to harvest the destination address,
+ we do NOT want to update dest or hopLocal, to let the kernel
+ pick the less terrible option, but we want to update origDest
+ which is used by rules and actions to at least the correct
+ address family */
+ ids.origDest = clientState.local;
+ ids.hopLocal.sin4.sin_family = 0;
+ }
+
+ std::vector<ProxyProtocolValue> proxyProtocolValues;
+ if (expectProxyProtocol && !handleProxyProtocol(remote, false, *holders.acl, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
+ return;
+ }
+
+ ids.queryRealTime.start();
+
+ auto dnsCryptResponse = checkDNSCryptQuery(clientState, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
+ if (dnsCryptResponse) {
+ sendUDPResponse(clientState.udpFD, query, 0, dest, remote);
+ return;
+ }
+
+ {
+ /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
+ const dnsheader_aligned dnsHeader(query.data());
+ queryId = ntohs(dnsHeader->id);
+
+ if (!checkQueryHeaders(*dnsHeader, clientState)) {
+ return;
+ }
+
+ if (dnsHeader->qdcount == 0) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
+ header.rcode = RCode::NotImp;
+ header.qr = true;
+ return true;
+ });
+
+ sendUDPResponse(clientState.udpFD, query, 0, dest, remote);
+ return;
+ }
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ if (ids.dnsCryptQuery) {
+ ids.protocol = dnsdist::Protocol::DNSCryptUDP;
+ }
+ DNSQuestion dnsQuestion(ids, query);
+ const uint16_t* flags = getFlagsFromDNSHeader(dnsQuestion.getHeader().get());
+ ids.origFlags = *flags;
+
+ if (!proxyProtocolValues.empty()) {
+ dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
+ }
+
+ std::shared_ptr<DownstreamState> backend{nullptr};
+ auto result = processQuery(dnsQuestion, holders, backend);
+
+ if (result == ProcessQueryResult::Drop || result == ProcessQueryResult::Asynchronous) {
+ return;
+ }
+
+ // the buffer might have been invalidated by now (resized)
+ const auto dnsHeader = dnsQuestion.getHeader();
+ if (result == ProcessQueryResult::SendAnswer) {
+#ifndef DISABLE_RECVMMSG
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+ if (dnsQuestion.ids.delayMsec == 0 && responsesVect != nullptr) {
+ queueResponse(clientState, query, dest, remote, (*responsesVect)[*queuedResponses], respIOV, respCBuf);
+ (*queuedResponses)++;
+ handleResponseSent(dnsQuestion.ids.qname, dnsQuestion.ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
+ return;
+ }
+#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
+#endif /* DISABLE_RECVMMSG */
+ /* we use dest, always, because we don't want to use the listening address to send a response since it could be 0.0.0.0 */
+ sendUDPResponse(clientState.udpFD, query, dnsQuestion.ids.delayMsec, dest, remote);
+
+ handleResponseSent(dnsQuestion.ids.qname, dnsQuestion.ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
+ return;
+ }
+
+ if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
+ return;
+ }
+
+ if (backend->isTCPOnly()) {
+ std::string proxyProtocolPayload;
+ /* we need to do this _before_ creating the cross protocol query because
+ after that the buffer will have been moved */
+ if (backend->d_config.useProxyProtocol) {
+ proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+ }
+
+ ids.origID = dnsHeader->id;
+ auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), backend);
+ cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+ backend->passCrossProtocolQuery(std::move(cpq));
+ return;
+ }
+
+ assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query);
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", ids.origRemote.toStringWithPort(), queryId, e.what());
+ }
+}
+
+#ifdef HAVE_XSK
+namespace dnsdist::xsk
+{
+bool XskProcessQuery(ClientState& clientState, LocalHolders& holders, XskPacket& packet)
+{
+ uint16_t queryId = 0;
+ const auto& remote = packet.getFromAddr();
+ const auto& dest = packet.getToAddr();
+ InternalQueryState ids;
+ ids.cs = &clientState;
+ ids.origRemote = remote;
+ ids.hopRemote = remote;
+ ids.origDest = dest;
+ ids.hopLocal = dest;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.xskPacketHeader = packet.cloneHeaderToPacketBuffer();
+
+ try {
+ bool expectProxyProtocol = false;
+ if (!XskIsQueryAcceptable(packet, clientState, holders, expectProxyProtocol)) {
+ return false;
+ }
+
+ auto query = packet.clonePacketBuffer();
+ std::vector<ProxyProtocolValue> proxyProtocolValues;
+ if (expectProxyProtocol && !handleProxyProtocol(remote, false, *holders.acl, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
+ return false;
+ }
+
+ ids.queryRealTime.start();
+
+ auto dnsCryptResponse = checkDNSCryptQuery(clientState, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
+ if (dnsCryptResponse) {
+ packet.setPayload(query);
+ return true;
+ }
+
+ {
+ /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
+ dnsheader_aligned dnsHeader(query.data());
+ queryId = ntohs(dnsHeader->id);
+
+ if (!checkQueryHeaders(*dnsHeader, clientState)) {
+ return false;
+ }
+
+ if (dnsHeader->qdcount == 0) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
+ header.rcode = RCode::NotImp;
+ header.qr = true;
+ return true;
+ });
+ packet.setPayload(query);
+ return true;
+ }
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ if (ids.origDest.sin4.sin_family == 0) {
+ ids.origDest = clientState.local;
+ }
+ if (ids.dnsCryptQuery) {
+ ids.protocol = dnsdist::Protocol::DNSCryptUDP;
+ }
+ DNSQuestion dnsQuestion(ids, query);
+ if (!proxyProtocolValues.empty()) {
+ dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
+ }
+ std::shared_ptr<DownstreamState> backend{nullptr};
+ auto result = processQuery(dnsQuestion, holders, backend);
+
+ if (result == ProcessQueryResult::Drop) {
+ return false;
+ }
+
+ if (result == ProcessQueryResult::SendAnswer) {
+ packet.setPayload(query);
+ if (dnsQuestion.ids.delayMsec > 0) {
+ packet.addDelay(dnsQuestion.ids.delayMsec);
+ }
+ const auto dnsHeader = dnsQuestion.getHeader();
+ handleResponseSent(ids.qname, ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
+ return true;
+ }
+
+ if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
+ return false;
+ }
+
+ // the buffer might have been invalidated by now (resized)
+ const auto dnsHeader = dnsQuestion.getHeader();
+ if (backend->isTCPOnly()) {
+ std::string proxyProtocolPayload;
+ /* we need to do this _before_ creating the cross protocol query because
+ after that the buffer will have been moved */
+ if (backend->d_config.useProxyProtocol) {
+ proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+ }
+
+ ids.origID = dnsHeader->id;
+ auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), backend);
+ cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+ backend->passCrossProtocolQuery(std::move(cpq));
+ return false;
+ }
+
+ if (backend->d_xskInfos.empty()) {
+ assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query, true);
+ return false;
+ }
+
+ assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query, false);
+ auto sourceAddr = backend->pickSourceAddressForSending();
+ packet.setAddr(sourceAddr, backend->d_config.sourceMACAddr, backend->d_config.remote, backend->d_config.destMACAddr);
+ packet.setPayload(query);
+ packet.rewrite();
+ return true;
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
+ }
+ return false;
+}
+
+}
+#endif /* HAVE_XSK */
+
+#ifndef DISABLE_RECVMMSG
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+static void MultipleMessagesUDPClientThread(ClientState* clientState, LocalHolders& holders)
+{
+ struct MMReceiver
+ {
+ PacketBuffer packet;
+ ComboAddress remote;
+ ComboAddress dest;
+ iovec iov{};
+ /* used by HarvestDestinationAddress */
+ cmsgbuf_aligned cbuf{};
+ };
+ const size_t vectSize = g_udpVectorSize;
+
+ if (vectSize > std::numeric_limits<uint16_t>::max()) {
+ throw std::runtime_error("The value of setUDPMultipleMessagesVectorSize is too high, the maximum value is " + std::to_string(std::numeric_limits<uint16_t>::max()));
+ }
+
+ auto recvData = std::vector<MMReceiver>(vectSize);
+ auto msgVec = std::vector<mmsghdr>(vectSize);
+ auto outMsgVec = std::vector<mmsghdr>(vectSize);
+
+ /* the actual buffer is larger because:
+ - we may have to add EDNS and/or ECS
+ - we use it for self-generated responses (from rule or cache)
+ but we only accept incoming payloads up to that size
+ */
+ const size_t initialBufferSize = getInitialUDPPacketBufferSize(clientState->d_enableProxyProtocol);
+ const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*clientState);
+
+ /* initialize the structures needed to receive our messages */
+ for (size_t idx = 0; idx < vectSize; idx++) {
+ recvData[idx].remote.sin4.sin_family = clientState->local.sin4.sin_family;
+ recvData[idx].packet.resize(initialBufferSize);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ fillMSGHdr(&msgVec[idx].msg_hdr, &recvData[idx].iov, &recvData[idx].cbuf, sizeof(recvData[idx].cbuf), reinterpret_cast<char*>(recvData[idx].packet.data()), maxIncomingPacketSize, &recvData[idx].remote);
+ }
+
+ /* go now */
+ for (;;) {
+
+ /* reset the IO vector, since it's also used to send the vector of responses
+ to avoid having to copy the data around */
+ for (size_t idx = 0; idx < vectSize; idx++) {
+ recvData[idx].packet.resize(initialBufferSize);
+ recvData[idx].iov.iov_base = &recvData[idx].packet.at(0);
+ recvData[idx].iov.iov_len = recvData[idx].packet.size();
+ }
+
+ /* block until we have at least one message ready, but return
+ as many as possible to save the syscall costs */
+ int msgsGot = recvmmsg(clientState->udpFD, msgVec.data(), vectSize, MSG_WAITFORONE | MSG_TRUNC, nullptr);
+
+ if (msgsGot <= 0) {
+ vinfolog("Getting UDP messages via recvmmsg() failed with: %s", stringerror());
+ continue;
+ }
+
+ unsigned int msgsToSend = 0;
+
+ /* process the received messages */
+ for (int msgIdx = 0; msgIdx < msgsGot; msgIdx++) {
+ const struct msghdr* msgh = &msgVec[msgIdx].msg_hdr;
+ unsigned int got = msgVec[msgIdx].msg_len;
+ const ComboAddress& remote = recvData[msgIdx].remote;
+
+ if (static_cast<size_t>(got) < sizeof(struct dnsheader)) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState->nonCompliantQueries;
+ continue;
+ }
+
+ recvData[msgIdx].packet.resize(got);
+ processUDPQuery(*clientState, holders, msgh, remote, recvData[msgIdx].dest, recvData[msgIdx].packet, &outMsgVec, &msgsToSend, &recvData[msgIdx].iov, &recvData[msgIdx].cbuf);
+ }
+
+ /* immediate (not delayed or sent to a backend) responses (mostly from a rule, dynamic block
+ or the cache) can be sent in batch too */
+
+ if (msgsToSend > 0 && msgsToSend <= static_cast<unsigned int>(msgsGot)) {
+ int sent = sendmmsg(clientState->udpFD, outMsgVec.data(), msgsToSend, 0);
+
+ if (sent < 0 || static_cast<unsigned int>(sent) != msgsToSend) {
+ vinfolog("Error sending responses with sendmmsg() (%d on %u): %s", sent, msgsToSend, stringerror());
+ }
+ }
+ }
+}
+#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
+#endif /* DISABLE_RECVMMSG */
+
+// listens to incoming queries, sends out to downstream servers, noting the intended return path
+static void udpClientThread(std::vector<ClientState*> states)
+{
+ try {
+ setThreadName("dnsdist/udpClie");
+ LocalHolders holders;
+#ifndef DISABLE_RECVMMSG
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+ if (g_udpVectorSize > 1) {
+ MultipleMessagesUDPClientThread(states.at(0), holders);
+ }
+ else
+#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
+#endif /* DISABLE_RECVMMSG */
+ {
+ /* the actual buffer is larger because:
+ - we may have to add EDNS and/or ECS
+ - we use it for self-generated responses (from rule or cache)
+ but we only accept incoming payloads up to that size
+ */
+ struct UDPStateParam
+ {
+ ClientState* cs{nullptr};
+ size_t maxIncomingPacketSize{0};
+ int socket{-1};
+ };
+ const size_t initialBufferSize = getInitialUDPPacketBufferSize(true);
+ PacketBuffer packet(initialBufferSize);
+
+ msghdr msgh{};
+ iovec iov{};
+ ComboAddress remote;
+ ComboAddress dest;
+
+ auto handleOnePacket = [&packet, &iov, &holders, &msgh, &remote, &dest, initialBufferSize](const UDPStateParam& param) {
+ packet.resize(initialBufferSize);
+ iov.iov_base = &packet.at(0);
+ iov.iov_len = packet.size();
+
+ ssize_t got = recvmsg(param.socket, &msgh, 0);
+
+ if (got < 0 || static_cast<size_t>(got) < sizeof(struct dnsheader)) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++param.cs->nonCompliantQueries;
+ return;
+ }
+
+ packet.resize(static_cast<size_t>(got));
+
+ processUDPQuery(*param.cs, holders, &msgh, remote, dest, packet, nullptr, nullptr, nullptr, nullptr);
+ };
+
+ std::vector<UDPStateParam> params;
+ for (auto& state : states) {
+ const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*state);
+ params.emplace_back(UDPStateParam{state, maxIncomingPacketSize, state->udpFD});
+ }
+
+ if (params.size() == 1) {
+ const auto& param = params.at(0);
+ remote.sin4.sin_family = param.cs->local.sin4.sin_family;
+ /* used by HarvestDestinationAddress */
+ cmsgbuf_aligned cbuf;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param.maxIncomingPacketSize, &remote);
+ while (true) {
+ try {
+ handleOnePacket(param);
+ }
+ catch (const std::bad_alloc& e) {
+ /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
+ in which case we DO NOT want to log (as it would trigger another memory allocation attempt
+ that might throw as well) but wait a bit (one millisecond) and then try to recover */
+ usleep(1000);
+ }
+ }
+ }
+ else {
+ auto callback = [&remote, &msgh, &iov, &packet, &handleOnePacket, initialBufferSize](int socket, FDMultiplexer::funcparam_t& funcparam) {
+ const auto* param = boost::any_cast<const UDPStateParam*>(funcparam);
+ try {
+ remote.sin4.sin_family = param->cs->local.sin4.sin_family;
+ packet.resize(initialBufferSize);
+ /* used by HarvestDestinationAddress */
+ cmsgbuf_aligned cbuf;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param->maxIncomingPacketSize, &remote);
+ handleOnePacket(*param);
+ }
+ catch (const std::bad_alloc& e) {
+ /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
+ in which case we DO NOT want to log (as it would trigger another memory allocation attempt
+ that might throw as well) but wait a bit (one millisecond) and then try to recover */
+ usleep(1000);
+ }
+ };
+ auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
+ for (const auto& param : params) {
+ mplexer->addReadFD(param.socket, callback, ¶m);
+ }
+
+ timeval now{};
+ while (true) {
+ mplexer->run(&now, -1);
+ }
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ errlog("UDP client thread died because of exception: %s", e.what());
+ }
+ catch (const PDNSException& e) {
+ errlog("UDP client thread died because of PowerDNS exception: %s", e.reason);
+ }
+ catch (...) {
+ errlog("UDP client thread died because of an exception: %s", "unknown");
+ }
+}
+
+boost::optional<uint64_t> g_maxTCPClientThreads{boost::none};
+pdns::stat16_t g_cacheCleaningDelay{60};
+pdns::stat16_t g_cacheCleaningPercentage{100};
+
+static void maintThread()
+{
+ setThreadName("dnsdist/main");
+ constexpr int interval = 1;
+ size_t counter = 0;
+ int32_t secondsToWaitLog = 0;
+
+ for (;;) {
+ std::this_thread::sleep_for(std::chrono::seconds(interval));
+
+ {
+ auto lua = g_lua.lock();
+ try {
+ auto maintenanceCallback = lua->readVariable<boost::optional<std::function<void()>>>("maintenance");
+ if (maintenanceCallback) {
+ (*maintenanceCallback)();
+ }
+ dnsdist::lua::hooks::runMaintenanceHooks(*lua);
+ secondsToWaitLog = 0;
+ }
+ catch (const std::exception& e) {
+ if (secondsToWaitLog <= 0) {
+ warnlog("Error during execution of maintenance function(s): %s", e.what());
+ secondsToWaitLog = 61;
+ }
+ secondsToWaitLog -= interval;
+ }
+ }
+
+ counter++;
+ if (counter >= g_cacheCleaningDelay) {
+ /* keep track, for each cache, of whether we should keep
+ expired entries */
+ std::map<std::shared_ptr<DNSDistPacketCache>, bool> caches;
+
+ /* gather all caches actually used by at least one pool, and see
+ if something prevents us from cleaning the expired entries */
+ auto localPools = g_pools.getLocal();
+ for (const auto& entry : *localPools) {
+ const auto& pool = entry.second;
+
+ auto packetCache = pool->packetCache;
+ if (!packetCache) {
+ continue;
+ }
+
+ auto pair = caches.insert({packetCache, false});
+ auto& iter = pair.first;
+ /* if we need to keep stale data for this cache (ie, not clear
+ expired entries when at least one pool using this cache
+ has all its backends down) */
+ if (packetCache->keepStaleData() && !iter->second) {
+ /* so far all pools had at least one backend up */
+ if (pool->countServers(true) == 0) {
+ iter->second = true;
+ }
+ }
+ }
+
+ const time_t now = time(nullptr);
+ for (const auto& pair : caches) {
+ /* shall we keep expired entries ? */
+ if (pair.second) {
+ continue;
+ }
+ const auto& packetCache = pair.first;
+ size_t upTo = (packetCache->getMaxEntries() * (100 - g_cacheCleaningPercentage)) / 100;
+ packetCache->purgeExpired(upTo, now);
+ }
+ counter = 0;
+ }
+ }
+}
+
+#ifndef DISABLE_DYNBLOCKS
+static void dynBlockMaintenanceThread()
+{
+ setThreadName("dnsdist/dynBloc");
+
+ DynBlockMaintenance::run();
+}
+#endif
+
+#ifndef DISABLE_SECPOLL
+static void secPollThread()
+{
+ setThreadName("dnsdist/secpoll");
+
+ for (;;) {
+ try {
+ doSecPoll(g_secPollSuffix);
+ }
+ catch (...) {
+ }
+ // coverity[store_truncates_time_t]
+ std::this_thread::sleep_for(std::chrono::seconds(g_secPollInterval));
+ }
+}
+#endif /* DISABLE_SECPOLL */
+
+static void healthChecksThread()
+{
+ setThreadName("dnsdist/healthC");
+
+ constexpr int intervalUsec = 1000 * 1000;
+ struct timeval lastRound
+ {
+ .tv_sec = 0,
+ .tv_usec = 0
+ };
+ auto states = g_dstates.getLocal(); // this points to the actual shared_ptrs!
+
+ for (;;) {
+ timeval now{};
+ gettimeofday(&now, nullptr);
+ auto elapsedTimeUsec = uSec(now - lastRound);
+ if (elapsedTimeUsec < intervalUsec) {
+ usleep(intervalUsec - elapsedTimeUsec);
+ gettimeofday(&lastRound, nullptr);
+ }
+ else {
+ lastRound = now;
+ }
+
+ std::unique_ptr<FDMultiplexer> mplexer{nullptr};
+ for (const auto& dss : *states) {
+ dss->updateStatisticsInfo();
+
+ dss->handleUDPTimeouts();
+
+ if (!dss->healthCheckRequired()) {
+ continue;
+ }
+
+ if (!mplexer) {
+ mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states->size()));
+ }
+
+ if (!queueHealthCheck(mplexer, dss)) {
+ dss->submitHealthCheckResult(false, false);
+ }
+ }
+
+ if (mplexer) {
+ handleQueuedHealthChecks(*mplexer);
+ }
+ }
+}
+
+static void bindAny(int addressFamily, int sock)
+{
+ __attribute__((unused)) int one = 1;
+
+#ifdef IP_FREEBIND
+ if (setsockopt(sock, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) {
+ warnlog("Warning: IP_FREEBIND setsockopt failed: %s", stringerror());
+ }
+#endif
+
+#ifdef IP_BINDANY
+ if (addressFamily == AF_INET) {
+ if (setsockopt(sock, IPPROTO_IP, IP_BINDANY, &one, sizeof(one)) < 0) {
+ warnlog("Warning: IP_BINDANY setsockopt failed: %s", stringerror());
+ }
+ }
+#endif
+#ifdef IPV6_BINDANY
+ if (addressFamily == AF_INET6) {
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_BINDANY, &one, sizeof(one)) < 0) {
+ warnlog("Warning: IPV6_BINDANY setsockopt failed: %s", stringerror());
+ }
+ }
+#endif
+#ifdef SO_BINDANY
+ if (setsockopt(sock, SOL_SOCKET, SO_BINDANY, &one, sizeof(one)) < 0) {
+ warnlog("Warning: SO_BINDANY setsockopt failed: %s", stringerror());
+ }
+#endif
+}
+
+static void dropGroupPrivs(gid_t gid)
+{
+ if (gid != 0) {
+ if (setgid(gid) == 0) {
+ if (setgroups(0, nullptr) < 0) {
+ warnlog("Warning: Unable to drop supplementary gids: %s", stringerror());
+ }
+ }
+ else {
+ warnlog("Warning: Unable to set group ID to %d: %s", gid, stringerror());
+ }
+ }
+}
+
+static void dropUserPrivs(uid_t uid)
+{
+ if (uid != 0) {
+ if (setuid(uid) < 0) {
+ warnlog("Warning: Unable to set user ID to %d: %s", uid, stringerror());
+ }
+ }
+}
+
+static void checkFileDescriptorsLimits(size_t udpBindsCount, size_t tcpBindsCount)
+{
+ /* stdin, stdout, stderr */
+ rlim_t requiredFDsCount = 3;
+ auto backends = g_dstates.getLocal();
+ /* UDP sockets to backends */
+ size_t backendUDPSocketsCount = 0;
+ for (const auto& backend : *backends) {
+ backendUDPSocketsCount += backend->sockets.size();
+ }
+ requiredFDsCount += backendUDPSocketsCount;
+ /* TCP sockets to backends */
+ if (g_maxTCPClientThreads) {
+ requiredFDsCount += (backends->size() * (*g_maxTCPClientThreads));
+ }
+ /* listening sockets */
+ requiredFDsCount += udpBindsCount;
+ requiredFDsCount += tcpBindsCount;
+ /* number of TCP connections currently served, assuming 1 connection per worker thread which is of course not right */
+ if (g_maxTCPClientThreads) {
+ requiredFDsCount += *g_maxTCPClientThreads;
+ /* max pipes for communicating between TCP acceptors and client threads */
+ requiredFDsCount += (*g_maxTCPClientThreads * 2);
+ }
+ /* max TCP queued connections */
+ requiredFDsCount += g_maxTCPQueuedConnections;
+ /* DelayPipe pipe */
+ requiredFDsCount += 2;
+ /* syslog socket */
+ requiredFDsCount++;
+ /* webserver main socket */
+ requiredFDsCount++;
+ /* console main socket */
+ requiredFDsCount++;
+ /* carbon export */
+ requiredFDsCount++;
+ /* history file */
+ requiredFDsCount++;
+ rlimit resourceLimits{};
+ getrlimit(RLIMIT_NOFILE, &resourceLimits);
+ if (resourceLimits.rlim_cur <= requiredFDsCount) {
+ warnlog("Warning, this configuration can use more than %d file descriptors, web server and console connections not included, and the current limit is %d.", std::to_string(requiredFDsCount), std::to_string(resourceLimits.rlim_cur));
+#ifdef HAVE_SYSTEMD
+ warnlog("You can increase this value by using LimitNOFILE= in the systemd unit file or ulimit.");
+#else
+ warnlog("You can increase this value by using ulimit.");
+#endif
+ }
+}
+
+static bool g_warned_ipv6_recvpktinfo = false;
+
+static void setupLocalSocket(ClientState& clientState, const ComboAddress& addr, int& socket, bool tcp, bool warn)
+{
+ (void)warn;
+ socket = SSocket(addr.sin4.sin_family, !tcp ? SOCK_DGRAM : SOCK_STREAM, 0);
+
+ if (tcp) {
+ SSetsockopt(socket, SOL_SOCKET, SO_REUSEADDR, 1);
+#ifdef TCP_DEFER_ACCEPT
+ SSetsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
+#endif
+ if (clientState.fastOpenQueueSize > 0) {
+#ifdef TCP_FASTOPEN
+ SSetsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, clientState.fastOpenQueueSize);
+#ifdef TCP_FASTOPEN_KEY
+ if (!g_TCPFastOpenKey.empty()) {
+ auto res = setsockopt(socket, IPPROTO_IP, TCP_FASTOPEN_KEY, g_TCPFastOpenKey.data(), g_TCPFastOpenKey.size() * sizeof(g_TCPFastOpenKey[0]));
+ if (res == -1) {
+ throw runtime_error("setsockopt for level IPPROTO_TCP and opname TCP_FASTOPEN_KEY failed: " + stringerror());
+ }
+ }
+#endif /* TCP_FASTOPEN_KEY */
+#else /* TCP_FASTOPEN */
+ if (warn) {
+ warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", addr.toStringWithPort());
+ }
+#endif /* TCP_FASTOPEN */
+ }
+ }
+
+ if (addr.sin4.sin_family == AF_INET6) {
+ SSetsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, 1);
+ }
+
+ bindAny(addr.sin4.sin_family, socket);
+
+ if (!tcp && IsAnyAddress(addr)) {
+ int one = 1;
+ (void)setsockopt(socket, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
+#ifdef IPV6_RECVPKTINFO
+ if (addr.isIPv6() && setsockopt(socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) < 0 && !g_warned_ipv6_recvpktinfo) {
+ warnlog("Warning: IPV6_RECVPKTINFO setsockopt failed: %s", stringerror());
+ g_warned_ipv6_recvpktinfo = true;
+ }
+#endif
+ }
+
+ if (clientState.reuseport) {
+ if (!setReusePort(socket)) {
+ if (warn) {
+ /* no need to warn again if configured but support is not available, we already did for UDP */
+ warnlog("SO_REUSEPORT has been configured on local address '%s' but is not supported", addr.toStringWithPort());
+ }
+ }
+ }
+
+ const bool isQUIC = clientState.doqFrontend != nullptr || clientState.doh3Frontend != nullptr;
+ if (isQUIC) {
+ /* disable fragmentation and force PMTU discovery for QUIC-enabled sockets */
+ try {
+ setSocketForcePMTU(socket, addr.sin4.sin_family);
+ }
+ catch (const std::exception& e) {
+ warnlog("Failed to set IP_MTU_DISCOVER on QUIC server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
+ }
+ }
+ else if (!tcp && !clientState.dnscryptCtx) {
+ /* Only set this on IPv4 UDP sockets.
+ Don't set it for DNSCrypt binds. DNSCrypt pads queries for privacy
+ purposes, so we do receive large, sometimes fragmented datagrams. */
+ try {
+ setSocketIgnorePMTU(socket, addr.sin4.sin_family);
+ }
+ catch (const std::exception& e) {
+ warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
+ }
+ }
+
+ if (!tcp) {
+ if (g_socketUDPSendBuffer > 0) {
+ try {
+ setSocketSendBuffer(socket, g_socketUDPSendBuffer);
+ }
+ catch (const std::exception& e) {
+ warnlog(e.what());
+ }
+ }
+ else {
+ try {
+ auto result = raiseSocketSendBufferToMax(socket);
+ if (result > 0) {
+ infolog("Raised send buffer to %u for local address '%s'", result, addr.toStringWithPort());
+ }
+ }
+ catch (const std::exception& e) {
+ warnlog(e.what());
+ }
+ }
+
+ if (g_socketUDPRecvBuffer > 0) {
+ try {
+ setSocketReceiveBuffer(socket, g_socketUDPRecvBuffer);
+ }
+ catch (const std::exception& e) {
+ warnlog(e.what());
+ }
+ }
+ else {
+ try {
+ auto result = raiseSocketReceiveBufferToMax(socket);
+ if (result > 0) {
+ infolog("Raised receive buffer to %u for local address '%s'", result, addr.toStringWithPort());
+ }
+ }
+ catch (const std::exception& e) {
+ warnlog(e.what());
+ }
+ }
+ }
+
+ const std::string& itf = clientState.interface;
+ if (!itf.empty()) {
+#ifdef SO_BINDTODEVICE
+ int res = setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, itf.c_str(), itf.length());
+ if (res != 0) {
+ warnlog("Error setting up the interface on local address '%s': %s", addr.toStringWithPort(), stringerror());
+ }
+#else
+ if (warn) {
+ warnlog("An interface has been configured on local address '%s' but SO_BINDTODEVICE is not supported", addr.toStringWithPort());
+ }
+#endif
+ }
+
+#ifdef HAVE_EBPF
+ if (g_defaultBPFFilter && !g_defaultBPFFilter->isExternal()) {
+ clientState.attachFilter(g_defaultBPFFilter, socket);
+ vinfolog("Attaching default BPF Filter to %s frontend %s", (!tcp ? std::string("UDP") : std::string("TCP")), addr.toStringWithPort());
+ }
+#endif /* HAVE_EBPF */
+
+ SBind(socket, addr);
+
+ if (tcp) {
+ SListen(socket, clientState.tcpListenQueueSize);
+
+ if (clientState.tlsFrontend != nullptr) {
+ infolog("Listening on %s for TLS", addr.toStringWithPort());
+ }
+ else if (clientState.dohFrontend != nullptr) {
+ infolog("Listening on %s for DoH", addr.toStringWithPort());
+ }
+ else if (clientState.dnscryptCtx != nullptr) {
+ infolog("Listening on %s for DNSCrypt", addr.toStringWithPort());
+ }
+ else {
+ infolog("Listening on %s", addr.toStringWithPort());
+ }
+ }
+ else {
+ if (clientState.doqFrontend != nullptr) {
+ infolog("Listening on %s for DoQ", addr.toStringWithPort());
+ }
+ else if (clientState.doh3Frontend != nullptr) {
+ infolog("Listening on %s for DoH3", addr.toStringWithPort());
+ }
+#ifdef HAVE_XSK
+ else if (clientState.xskInfo != nullptr) {
+ infolog("Listening on %s (XSK-enabled)", addr.toStringWithPort());
+ }
+#endif
+ }
+}
+
+static void setUpLocalBind(std::unique_ptr<ClientState>& cstate)
+{
+ /* skip some warnings if there is an identical UDP context */
+ bool warn = !cstate->tcp || cstate->tlsFrontend != nullptr || cstate->dohFrontend != nullptr;
+ int& descriptor = !cstate->tcp ? cstate->udpFD : cstate->tcpFD;
+ (void)warn;
+
+ setupLocalSocket(*cstate, cstate->local, descriptor, cstate->tcp, warn);
+
+ for (auto& [addr, socket] : cstate->d_additionalAddresses) {
+ setupLocalSocket(*cstate, addr, socket, true, false);
+ }
+
+ if (cstate->tlsFrontend != nullptr) {
+ if (!cstate->tlsFrontend->setupTLS()) {
+ errlog("Error while setting up TLS on local address '%s', exiting", cstate->local.toStringWithPort());
+ _exit(EXIT_FAILURE);
+ }
+ }
+
+ if (cstate->dohFrontend != nullptr) {
+ cstate->dohFrontend->setup();
+ }
+ if (cstate->doqFrontend != nullptr) {
+ cstate->doqFrontend->setup();
+ }
+ if (cstate->doh3Frontend != nullptr) {
+ cstate->doh3Frontend->setup();
+ }
+
+ cstate->ready = true;
+}
+
+struct
+{
+ vector<string> locals;
+ vector<string> remotes;
+ bool checkConfig{false};
+ bool beClient{false};
+ bool beSupervised{false};
+ string command;
+ string config;
+ string uid;
+ string gid;
+} g_cmdLine;
+
+std::atomic<bool> g_configurationDone{false};
+
+static void usage()
+{
+ cout << endl;
+ cout << "Syntax: dnsdist [-C,--config file] [-c,--client [IP[:PORT]]]\n";
+ cout << "[-e,--execute cmd] [-h,--help] [-l,--local addr]\n";
+ cout << "[-v,--verbose] [--check-config] [--version]\n";
+ cout << "\n";
+ cout << "-a,--acl netmask Add this netmask to the ACL\n";
+ cout << "-C,--config file Load configuration from 'file'\n";
+ cout << "-c,--client Operate as a client, connect to dnsdist. This reads\n";
+ cout << " controlSocket from your configuration file, but also\n";
+ cout << " accepts an IP:PORT argument\n";
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+ cout << "-k,--setkey KEY Use KEY for encrypted communication to dnsdist. This\n";
+ cout << " is similar to setting setKey in the configuration file.\n";
+ cout << " NOTE: this will leak this key in your shell's history\n";
+ cout << " and in the systems running process list.\n";
+#endif
+ cout << "--check-config Validate the configuration file and exit. The exit-code\n";
+ cout << " reflects the validation, 0 is OK, 1 means an error.\n";
+ cout << " Any errors are printed as well.\n";
+ cout << "-e,--execute cmd Connect to dnsdist and execute 'cmd'\n";
+ cout << "-g,--gid gid Change the process group ID after binding sockets\n";
+ cout << "-h,--help Display this helpful message\n";
+ cout << "-l,--local address Listen on this local address\n";
+ cout << "--supervised Don't open a console, I'm supervised\n";
+ cout << " (use with e.g. systemd and daemontools)\n";
+ cout << "--disable-syslog Don't log to syslog, only to stdout\n";
+ cout << " (use with e.g. systemd)\n";
+ cout << "--log-timestamps Prepend timestamps to messages logged to stdout.\n";
+ cout << "-u,--uid uid Change the process user ID after binding sockets\n";
+ cout << "-v,--verbose Enable verbose mode\n";
+ cout << "-V,--version Show dnsdist version information and exit\n";
+}
+
+#ifdef COVERAGE
+static void cleanupLuaObjects()
+{
+ /* when our coverage mode is enabled, we need to make sure
+ that the Lua objects are destroyed before the Lua contexts. */
+ dnsdist::rules::g_ruleactions.setState({});
+ for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+ chain.holder.setState({});
+ }
+ g_dstates.setState({});
+ g_policy.setState(ServerPolicy());
+ g_pools.setState({});
+ clearWebHandlers();
+ dnsdist::lua::hooks::clearMaintenanceHooks();
+}
+
+static void sigTermHandler(int)
+{
+ cleanupLuaObjects();
+ pdns::coverage::dumpCoverageData();
+ _exit(EXIT_SUCCESS);
+}
+#else /* COVERAGE */
+
+/* g++ defines __SANITIZE_THREAD__
+ clang++ supports the nice __has_feature(thread_sanitizer),
+ let's merge them */
+#if defined(__has_feature)
+#if __has_feature(thread_sanitizer)
+#define __SANITIZE_THREAD__ 1
+#endif
+#endif
+
+static void sigTermHandler([[maybe_unused]] int sig)
+{
+#if !defined(__SANITIZE_THREAD__)
+ /* TSAN is rightfully unhappy about this:
+ WARNING: ThreadSanitizer: signal-unsafe call inside of a signal
+ This is not a real problem for us, as the worst case is that
+ we crash trying to exit, but let's try to avoid the warnings
+ in our tests.
+ */
+ if (dnsdist::logging::LoggingConfiguration::getSyslog()) {
+ syslog(LOG_INFO, "Exiting on user request");
+ }
+ std::cout << "Exiting on user request" << std::endl;
+#endif /* __SANITIZE_THREAD__ */
+
+ _exit(EXIT_SUCCESS);
+}
+#endif /* COVERAGE */
+
+static void reportFeatures()
+{
+#ifdef LUAJIT_VERSION
+ cout << "dnsdist " << VERSION << " (" << LUA_RELEASE << " [" << LUAJIT_VERSION << "])" << endl;
+#else
+ cout << "dnsdist " << VERSION << " (" << LUA_RELEASE << ")" << endl;
+#endif
+ cout << "Enabled features: ";
+#ifdef HAVE_XSK
+ cout << "AF_XDP ";
+#endif
+#ifdef HAVE_CDB
+ cout << "cdb ";
+#endif
+#ifdef HAVE_DNS_OVER_QUIC
+ cout << "dns-over-quic ";
+#endif
+#ifdef HAVE_DNS_OVER_HTTP3
+ cout << "dns-over-http3 ";
+#endif
+#ifdef HAVE_DNS_OVER_TLS
+ cout << "dns-over-tls(";
+#ifdef HAVE_GNUTLS
+ cout << "gnutls";
+#ifdef HAVE_LIBSSL
+ cout << " ";
+#endif
+#endif /* HAVE_GNUTLS */
+#ifdef HAVE_LIBSSL
+ cout << "openssl";
+#endif
+ cout << ") ";
+#endif /* HAVE_DNS_OVER_TLS */
+#ifdef HAVE_DNS_OVER_HTTPS
+ cout << "dns-over-https(";
+#ifdef HAVE_LIBH2OEVLOOP
+ cout << "h2o";
+#endif /* HAVE_LIBH2OEVLOOP */
+#if defined(HAVE_LIBH2OEVLOOP) && defined(HAVE_NGHTTP2)
+ cout << " ";
+#endif /* defined(HAVE_LIBH2OEVLOOP) && defined(HAVE_NGHTTP2) */
+#ifdef HAVE_NGHTTP2
+ cout << "nghttp2";
+#endif /* HAVE_NGHTTP2 */
+ cout << ") ";
+#endif /* HAVE_DNS_OVER_HTTPS */
+#ifdef HAVE_DNSCRYPT
+ cout << "dnscrypt ";
+#endif
+#ifdef HAVE_EBPF
+ cout << "ebpf ";
+#endif
+#ifdef HAVE_FSTRM
+ cout << "fstrm ";
+#endif
+#ifdef HAVE_IPCIPHER
+ cout << "ipcipher ";
+#endif
+#ifdef HAVE_LIBEDIT
+ cout << "libedit ";
+#endif
+#ifdef HAVE_LIBSODIUM
+ cout << "libsodium ";
+#endif
+#ifdef HAVE_LMDB
+ cout << "lmdb ";
+#endif
+#ifndef DISABLE_PROTOBUF
+ cout << "protobuf ";
+#endif
+#ifdef HAVE_RE2
+ cout << "re2 ";
+#endif
+#ifndef DISABLE_RECVMMSG
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+ cout << "recvmmsg/sendmmsg ";
+#endif
+#endif /* DISABLE_RECVMMSG */
+#ifdef HAVE_NET_SNMP
+ cout << "snmp ";
+#endif
+#ifdef HAVE_SYSTEMD
+ cout << "systemd";
+#endif
+ cout << endl;
+}
+
+static void parseParameters(int argc, char** argv, ComboAddress& clientAddress)
+{
+ const std::array<struct option, 16> longopts{{{"acl", required_argument, nullptr, 'a'},
+ {"check-config", no_argument, nullptr, 1},
+ {"client", no_argument, nullptr, 'c'},
+ {"config", required_argument, nullptr, 'C'},
+ {"disable-syslog", no_argument, nullptr, 2},
+ {"execute", required_argument, nullptr, 'e'},
+ {"gid", required_argument, nullptr, 'g'},
+ {"help", no_argument, nullptr, 'h'},
+ {"local", required_argument, nullptr, 'l'},
+ {"log-timestamps", no_argument, nullptr, 4},
+ {"setkey", required_argument, nullptr, 'k'},
+ {"supervised", no_argument, nullptr, 3},
+ {"uid", required_argument, nullptr, 'u'},
+ {"verbose", no_argument, nullptr, 'v'},
+ {"version", no_argument, nullptr, 'V'},
+ {nullptr, 0, nullptr, 0}}};
+ int longindex = 0;
+ string optstring;
+ while (true) {
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ int gotChar = getopt_long(argc, argv, "a:cC:e:g:hk:l:u:vV", longopts.data(), &longindex);
+ if (gotChar == -1) {
+ break;
+ }
+ switch (gotChar) {
+ case 1:
+ g_cmdLine.checkConfig = true;
+ break;
+ case 2:
+ dnsdist::logging::LoggingConfiguration::setSyslog(false);
+ break;
+ case 3:
+ g_cmdLine.beSupervised = true;
+ break;
+ case 4:
+ dnsdist::logging::LoggingConfiguration::setLogTimestamps(true);
+ break;
+ case 'C':
+ g_cmdLine.config = optarg;
+ break;
+ case 'c':
+ g_cmdLine.beClient = true;
+ break;
+ case 'e':
+ g_cmdLine.command = optarg;
+ break;
+ case 'g':
+ g_cmdLine.gid = optarg;
+ break;
+ case 'h':
+ cout << "dnsdist " << VERSION << endl;
+ usage();
+ cout << "\n";
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ exit(EXIT_SUCCESS);
+ break;
+ case 'a':
+ optstring = optarg;
+ g_ACL.modify([optstring](NetmaskGroup& nmg) { nmg.addMask(optstring); });
+ break;
+ case 'k':
+#if defined HAVE_LIBSODIUM || defined(HAVE_LIBCRYPTO)
+ if (B64Decode(string(optarg), g_consoleKey) < 0) {
+ cerr << "Unable to decode key '" << optarg << "'." << endl;
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ exit(EXIT_FAILURE);
+ }
+#else
+ cerr << "dnsdist has been built without libsodium or libcrypto, -k/--setkey is unsupported." << endl;
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ exit(EXIT_FAILURE);
+#endif
+ break;
+ case 'l':
+ g_cmdLine.locals.push_back(boost::trim_copy(string(optarg)));
+ break;
+ case 'u':
+ g_cmdLine.uid = optarg;
+ break;
+ case 'v':
+ g_verbose = true;
+ break;
+ case 'V':
+ reportFeatures();
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ exit(EXIT_SUCCESS);
+ break;
+ case '?':
+ // getopt_long printed an error message.
+ usage();
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): argv
+ argv += optind;
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): argv
+ for (const auto* ptr = argv; *ptr != nullptr; ++ptr) {
+ if (g_cmdLine.beClient) {
+ clientAddress = ComboAddress(*ptr, 5199);
+ }
+ else {
+ g_cmdLine.remotes.emplace_back(*ptr);
+ }
+ }
+}
+static void setupPools()
+{
+ auto pools = g_pools.getCopy();
+ {
+ bool precompute = false;
+ if (g_policy.getLocal()->getName() == "chashed") {
+ precompute = true;
+ }
+ else {
+ for (const auto& entry : pools) {
+ if (entry.second->policy != nullptr && entry.second->policy->getName() == "chashed") {
+ precompute = true;
+ break;
+ }
+ }
+ }
+ if (precompute) {
+ vinfolog("Pre-computing hashes for consistent hash load-balancing policy");
+ // pre compute hashes
+ auto backends = g_dstates.getLocal();
+ for (const auto& backend : *backends) {
+ if (backend->d_config.d_weight < 100) {
+ vinfolog("Warning, the backend '%s' has a very low weight (%d), which will not yield a good distribution of queries with the 'chashed' policy. Please consider raising it to at least '100'.", backend->getName(), backend->d_config.d_weight);
+ }
+
+ backend->hash();
+ }
+ }
+ }
+}
+
+static void dropPrivileges()
+{
+ uid_t newgid = getegid();
+ gid_t newuid = geteuid();
+
+ if (!g_cmdLine.gid.empty()) {
+ newgid = strToGID(g_cmdLine.gid);
+ }
+
+ if (!g_cmdLine.uid.empty()) {
+ newuid = strToUID(g_cmdLine.uid);
+ }
+
+ bool retainedCapabilities = true;
+ if (!g_capabilitiesToRetain.empty() && (getegid() != newgid || geteuid() != newuid)) {
+ retainedCapabilities = keepCapabilitiesAfterSwitchingIDs();
+ }
+
+ if (getegid() != newgid) {
+ if (running_in_service_mgr()) {
+ errlog("--gid/-g set on command-line, but dnsdist was started as a systemd service. Use the 'Group' setting in the systemd unit file to set the group to run as");
+ _exit(EXIT_FAILURE);
+ }
+ dropGroupPrivs(newgid);
+ }
+
+ if (geteuid() != newuid) {
+ if (running_in_service_mgr()) {
+ errlog("--uid/-u set on command-line, but dnsdist was started as a systemd service. Use the 'User' setting in the systemd unit file to set the user to run as");
+ _exit(EXIT_FAILURE);
+ }
+ dropUserPrivs(newuid);
+ }
+
+ if (retainedCapabilities) {
+ dropCapabilitiesAfterSwitchingIDs();
+ }
+
+ try {
+ /* we might still have capabilities remaining,
+ for example if we have been started as root
+ without --uid or --gid (please don't do that)
+ or as an unprivileged user with ambient
+ capabilities like CAP_NET_BIND_SERVICE.
+ */
+ dropCapabilities(g_capabilitiesToRetain);
+ }
+ catch (const std::exception& e) {
+ warnlog("%s", e.what());
+ }
+}
+
+static void initFrontends()
+{
+ if (!g_cmdLine.locals.empty()) {
+ for (auto it = g_frontends.begin(); it != g_frontends.end();) {
+ /* DoH, DoT and DNSCrypt frontends are separate */
+ if ((*it)->dohFrontend == nullptr && (*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->doqFrontend == nullptr && (*it)->doh3Frontend == nullptr) {
+ it = g_frontends.erase(it);
+ }
+ else {
+ ++it;
+ }
+ }
+
+ for (const auto& loc : g_cmdLine.locals) {
+ /* UDP */
+ g_frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), false, false, 0, "", std::set<int>{}, true));
+ /* TCP */
+ g_frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), true, false, 0, "", std::set<int>{}, true));
+ }
+ }
+
+ if (g_frontends.empty()) {
+ /* UDP */
+ g_frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), false, false, 0, "", std::set<int>{}, true));
+ /* TCP */
+ g_frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), true, false, 0, "", std::set<int>{}, true));
+ }
+}
+
+namespace dnsdist
+{
+static void startFrontends()
+{
+#ifdef HAVE_XSK
+ for (auto& xskContext : dnsdist::xsk::g_xsk) {
+ std::thread xskThread(dnsdist::xsk::XskRouter, std::move(xskContext));
+ xskThread.detach();
+ }
+#endif /* HAVE_XSK */
+
+ std::vector<ClientState*> tcpStates;
+ std::vector<ClientState*> udpStates;
+ for (auto& clientState : g_frontends) {
+#ifdef HAVE_XSK
+ if (clientState->xskInfo) {
+ dnsdist::xsk::addDestinationAddress(clientState->local);
+
+ std::thread xskCT(dnsdist::xsk::XskClientThread, clientState.get());
+ if (!clientState->cpus.empty()) {
+ mapThreadToCPUList(xskCT.native_handle(), clientState->cpus);
+ }
+ xskCT.detach();
+ }
+#endif /* HAVE_XSK */
+
+ if (clientState->dohFrontend != nullptr && clientState->dohFrontend->d_library == "h2o") {
+#ifdef HAVE_DNS_OVER_HTTPS
+#ifdef HAVE_LIBH2OEVLOOP
+ std::thread dotThreadHandle(dohThread, clientState.get());
+ if (!clientState->cpus.empty()) {
+ mapThreadToCPUList(dotThreadHandle.native_handle(), clientState->cpus);
+ }
+ dotThreadHandle.detach();
+#endif /* HAVE_LIBH2OEVLOOP */
+#endif /* HAVE_DNS_OVER_HTTPS */
+ continue;
+ }
+ if (clientState->doqFrontend != nullptr) {
+#ifdef HAVE_DNS_OVER_QUIC
+ std::thread doqThreadHandle(doqThread, clientState.get());
+ if (!clientState->cpus.empty()) {
+ mapThreadToCPUList(doqThreadHandle.native_handle(), clientState->cpus);
+ }
+ doqThreadHandle.detach();
+#endif /* HAVE_DNS_OVER_QUIC */
+ continue;
+ }
+ if (clientState->doh3Frontend != nullptr) {
+#ifdef HAVE_DNS_OVER_HTTP3
+ std::thread doh3ThreadHandle(doh3Thread, clientState.get());
+ if (!clientState->cpus.empty()) {
+ mapThreadToCPUList(doh3ThreadHandle.native_handle(), clientState->cpus);
+ }
+ doh3ThreadHandle.detach();
+#endif /* HAVE_DNS_OVER_HTTP3 */
+ continue;
+ }
+ if (clientState->udpFD >= 0) {
+#ifdef USE_SINGLE_ACCEPTOR_THREAD
+ udpStates.push_back(clientState.get());
+#else /* USE_SINGLE_ACCEPTOR_THREAD */
+ std::thread udpClientThreadHandle(udpClientThread, std::vector<ClientState*>{clientState.get()});
+ if (!clientState->cpus.empty()) {
+ mapThreadToCPUList(udpClientThreadHandle.native_handle(), clientState->cpus);
+ }
+ udpClientThreadHandle.detach();
+#endif /* USE_SINGLE_ACCEPTOR_THREAD */
+ }
+ else if (clientState->tcpFD >= 0) {
+#ifdef USE_SINGLE_ACCEPTOR_THREAD
+ tcpStates.push_back(clientState.get());
+#else /* USE_SINGLE_ACCEPTOR_THREAD */
+ std::thread tcpAcceptorThreadHandle(tcpAcceptorThread, std::vector<ClientState*>{clientState.get()});
+ if (!clientState->cpus.empty()) {
+ mapThreadToCPUList(tcpAcceptorThreadHandle.native_handle(), clientState->cpus);
+ }
+ tcpAcceptorThreadHandle.detach();
+#endif /* USE_SINGLE_ACCEPTOR_THREAD */
+ }
+ }
+#ifdef USE_SINGLE_ACCEPTOR_THREAD
+ if (!udpStates.empty()) {
+ std::thread udpThreadHandle(udpClientThread, udpStates);
+ udpThreadHandle.detach();
+ }
+ if (!tcpStates.empty()) {
+ g_tcpclientthreads = std::make_unique<TCPClientCollection>(1, tcpStates);
+ }
+#endif /* USE_SINGLE_ACCEPTOR_THREAD */
+}
+}
+
+int main(int argc, char** argv)
+{
+ try {
+ size_t udpBindsCount = 0;
+ size_t tcpBindsCount = 0;
+#ifdef HAVE_LIBEDIT
+#ifndef DISABLE_COMPLETION
+ rl_attempted_completion_function = my_completion;
+ rl_completion_append_character = 0;
+#endif /* DISABLE_COMPLETION */
+#endif /* HAVE_LIBEDIT */
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): SIG_IGN macro
+ signal(SIGPIPE, SIG_IGN);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): SIG_IGN macro
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGTERM, sigTermHandler);
+
+ openlog("dnsdist", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+#ifdef HAVE_LIBSODIUM
+ if (sodium_init() == -1) {
+ cerr << "Unable to initialize crypto library" << endl;
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): only on thread at this point
+ exit(EXIT_FAILURE);
+ }
+#endif
+ dnsdist::initRandom();
+ g_hashperturb = dnsdist::getRandomValue(0xffffffff);
+
+#ifdef HAVE_XSK
+ try {
+ dnsdist::xsk::clearDestinationAddresses();
+ }
+ catch (const std::exception& exp) {
+ /* silently handle failures: at this point we don't even know if XSK is enabled,
+ and we might not have the correct map (not the default one). */
+ }
+#endif /* HAVE_XSK */
+
+ ComboAddress clientAddress = ComboAddress();
+ g_cmdLine.config = SYSCONFDIR "/dnsdist.conf";
+
+ parseParameters(argc, argv, clientAddress);
+
+ ServerPolicy leastOutstandingPol{"leastOutstanding", leastOutstanding, false};
+
+ g_policy.setState(leastOutstandingPol);
+ if (g_cmdLine.beClient || !g_cmdLine.command.empty()) {
+ setupLua(*(g_lua.lock()), true, false, g_cmdLine.config);
+ if (clientAddress != ComboAddress()) {
+ g_serverControl = clientAddress;
+ }
+ doClient(g_serverControl, g_cmdLine.command);
+#ifdef COVERAGE
+ exit(EXIT_SUCCESS);
+#else
+ _exit(EXIT_SUCCESS);
+#endif
+ }
+
+ auto acl = g_ACL.getCopy();
+ if (acl.empty()) {
+ for (const auto& addr : {"127.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "169.254.0.0/16", "192.168.0.0/16", "172.16.0.0/12", "::1/128", "fc00::/7", "fe80::/10"}) {
+ acl.addMask(addr);
+ }
+ g_ACL.setState(acl);
+ }
+
+ auto consoleACL = g_consoleACL.getCopy();
+ for (const auto& mask : {"127.0.0.1/8", "::1/128"}) {
+ consoleACL.addMask(mask);
+ }
+ g_consoleACL.setState(consoleACL);
+ registerBuiltInWebHandlers();
+
+ if (g_cmdLine.checkConfig) {
+ setupLua(*(g_lua.lock()), false, true, g_cmdLine.config);
+ // No exception was thrown
+ infolog("Configuration '%s' OK!", g_cmdLine.config);
+#ifdef COVERAGE
+ cleanupLuaObjects();
+ exit(EXIT_SUCCESS);
+#else
+ _exit(EXIT_SUCCESS);
+#endif
+ }
+
+ infolog("dnsdist %s comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2", VERSION);
+
+ dnsdist::g_asyncHolder = std::make_unique<dnsdist::AsynchronousHolder>();
+
+ auto todo = setupLua(*(g_lua.lock()), false, false, g_cmdLine.config);
+
+ setupPools();
+
+ initFrontends();
+
+ g_configurationDone = true;
+
+ g_rings.init();
+
+ for (auto& frontend : g_frontends) {
+ setUpLocalBind(frontend);
+
+ if (!frontend->tcp) {
+ ++udpBindsCount;
+ }
+ else {
+ ++tcpBindsCount;
+ }
+ }
+
+ {
+ std::string acls;
+ auto aclEntries = g_ACL.getLocal()->toStringVector();
+ for (const auto& aclEntry : aclEntries) {
+ if (!acls.empty()) {
+ acls += ", ";
+ }
+ acls += aclEntry;
+ }
+ infolog("ACL allowing queries from: %s", acls);
+ }
+ {
+ std::string acls;
+ auto aclEntries = g_consoleACL.getLocal()->toStringVector();
+ for (const auto& entry : aclEntries) {
+ if (!acls.empty()) {
+ acls += ", ";
+ }
+ acls += entry;
+ }
+ infolog("Console ACL allowing connections from: %s", acls.c_str());
+ }
+
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+ if (g_consoleEnabled && g_consoleKey.empty()) {
+ warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
+ }
+#endif
+
+ dropPrivileges();
+
+ /* this need to be done _after_ dropping privileges */
+#ifndef DISABLE_DELAY_PIPE
+ g_delay = std::make_unique<DelayPipe<DelayedPacket>>();
+#endif /* DISABLE_DELAY_PIPE */
+
+ if (g_snmpAgent != nullptr) {
+ g_snmpAgent->run();
+ }
+
+ if (!g_maxTCPClientThreads) {
+ g_maxTCPClientThreads = static_cast<size_t>(10);
+ }
+ else if (*g_maxTCPClientThreads == 0 && tcpBindsCount > 0) {
+ warnlog("setMaxTCPClientThreads() has been set to 0 while we are accepting TCP connections, raising to 1");
+ g_maxTCPClientThreads = 1;
+ }
+
+ /* we need to create the TCP worker threads before the
+ acceptor ones, otherwise we might crash when processing
+ the first TCP query */
+#ifndef USE_SINGLE_ACCEPTOR_THREAD
+ g_tcpclientthreads = std::make_unique<TCPClientCollection>(*g_maxTCPClientThreads, std::vector<ClientState*>());
+#endif
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+ initDoHWorkers();
+#endif
+
+ for (auto& todoItem : todo) {
+ todoItem();
+ }
+
+ auto localPools = g_pools.getCopy();
+ /* create the default pool no matter what */
+ createPoolIfNotExists(localPools, "");
+ if (!g_cmdLine.remotes.empty()) {
+ for (const auto& address : g_cmdLine.remotes) {
+ DownstreamState::Config config;
+ config.remote = ComboAddress(address, 53);
+ auto ret = std::make_shared<DownstreamState>(std::move(config), nullptr, true);
+ addServerToPool(localPools, "", ret);
+ ret->start();
+ g_dstates.modify([&ret](servers_t& servers) { servers.push_back(std::move(ret)); });
+ }
+ }
+ g_pools.setState(localPools);
+
+ if (g_dstates.getLocal()->empty()) {
+ errlog("No downstream servers defined: all packets will get dropped");
+ // you might define them later, but you need to know
+ }
+
+ checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount);
+
+ {
+ auto states = g_dstates.getCopy(); // it is a copy, but the internal shared_ptrs are the real deal
+ auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states.size()));
+ for (auto& dss : states) {
+
+ if (dss->d_config.availability == DownstreamState::Availability::Auto || dss->d_config.availability == DownstreamState::Availability::Lazy) {
+ if (dss->d_config.availability == DownstreamState::Availability::Auto) {
+ dss->d_nextCheck = dss->d_config.checkInterval;
+ }
+
+ if (!queueHealthCheck(mplexer, dss, true)) {
+ dss->submitHealthCheckResult(true, false);
+ dss->setUpStatus(false);
+ warnlog("Marking downstream %s as 'down'", dss->getNameWithAddr());
+ }
+ }
+ }
+ handleQueuedHealthChecks(*mplexer, true);
+ }
+
+ dnsdist::startFrontends();
+
+ dnsdist::ServiceDiscovery::run();
+
+#ifndef DISABLE_CARBON
+ dnsdist::Carbon::run();
+#endif /* DISABLE_CARBON */
+
+ thread stattid(maintThread);
+ stattid.detach();
+
+ thread healththread(healthChecksThread);
+
+#ifndef DISABLE_DYNBLOCKS
+ thread dynBlockMaintThread(dynBlockMaintenanceThread);
+ dynBlockMaintThread.detach();
+#endif /* DISABLE_DYNBLOCKS */
+
+#ifndef DISABLE_SECPOLL
+ if (!g_secPollSuffix.empty()) {
+ thread secpollthread(secPollThread);
+ secpollthread.detach();
+ }
+#endif /* DISABLE_SECPOLL */
+
+ if (g_cmdLine.beSupervised) {
+#ifdef HAVE_SYSTEMD
+ sd_notify(0, "READY=1");
+#endif
+ healththread.join();
+ }
+ else {
+ healththread.detach();
+ doConsole();
+ }
+#ifdef COVERAGE
+ cleanupLuaObjects();
+ exit(EXIT_SUCCESS);
+#else
+ _exit(EXIT_SUCCESS);
+#endif
+ }
+ catch (const LuaContext::ExecutionErrorException& e) {
+ try {
+ errlog("Fatal Lua error: %s", e.what());
+ std::rethrow_if_nested(e);
+ }
+ catch (const std::exception& ne) {
+ errlog("Details: %s", ne.what());
+ }
+ catch (const PDNSException& ae) {
+ errlog("Fatal pdns error: %s", ae.reason);
+ }
+#ifdef COVERAGE
+ cleanupLuaObjects();
+ exit(EXIT_FAILURE);
+#else
+ _exit(EXIT_FAILURE);
+#endif
+ }
+ catch (const std::exception& e) {
+ errlog("Fatal error: %s", e.what());
+#ifdef COVERAGE
+ cleanupLuaObjects();
+ exit(EXIT_FAILURE);
+#else
+ _exit(EXIT_FAILURE);
+#endif
+ }
+ catch (const PDNSException& ae) {
+ errlog("Fatal pdns error: %s", ae.reason);
+#ifdef COVERAGE
+ cleanupLuaObjects();
+ exit(EXIT_FAILURE);
+#else
+ _exit(EXIT_FAILURE);
+#endif
+ }
+}
-../dnsdistconf.lua
\ No newline at end of file
+dnsdistconf.lua
\ No newline at end of file
+++ /dev/null
-../dnsdist.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "config.h"
+#include "ext/luawrapper/include/LuaContext.hpp"
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <time.h>
+#include <unistd.h>
+#include <unordered_map>
+
+#include <boost/variant.hpp>
+
+#include "circular_buffer.hh"
+#include "dnscrypt.hh"
+#include "dnsdist-cache.hh"
+#include "dnsdist-dynbpf.hh"
+#include "dnsdist-idstate.hh"
+#include "dnsdist-lbpolicies.hh"
+#include "dnsdist-protocols.hh"
+#include "dnsname.hh"
+#include "dnsdist-doh-common.hh"
+#include "doq.hh"
+#include "doh3.hh"
+#include "ednsoptions.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "mplexer.hh"
+#include "noinitvector.hh"
+#include "sholder.hh"
+#include "tcpiohandler.hh"
+#include "uuid-utils.hh"
+#include "proxy-protocol.hh"
+#include "stat_t.hh"
+
+uint64_t uptimeOfProcess(const std::string& str);
+
+extern uint16_t g_ECSSourcePrefixV4;
+extern uint16_t g_ECSSourcePrefixV6;
+extern bool g_ECSOverride;
+
+using QTag = std::unordered_map<string, string>;
+
+class IncomingTCPConnectionState;
+
+struct ClientState;
+
+struct DNSQuestion
+{
+ DNSQuestion(InternalQueryState& ids_, PacketBuffer& data_) :
+ data(data_), ids(ids_), ecsPrefixLength(ids.origRemote.sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), ecsOverride(g_ECSOverride)
+ {
+ }
+ DNSQuestion(const DNSQuestion&) = delete;
+ DNSQuestion& operator=(const DNSQuestion&) = delete;
+ DNSQuestion(DNSQuestion&&) = default;
+ virtual ~DNSQuestion() = default;
+
+ std::string getTrailingData() const;
+ bool setTrailingData(const std::string&);
+ const PacketBuffer& getData() const
+ {
+ return data;
+ }
+ PacketBuffer& getMutableData()
+ {
+ return data;
+ }
+
+ bool editHeader(const std::function<bool(dnsheader&)>& editFunction);
+
+ const dnsheader_aligned getHeader() const
+ {
+ if (data.size() < sizeof(dnsheader)) {
+ throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
+ }
+ dnsheader_aligned dh(data.data());
+ return dh;
+ }
+
+ /* this function is not safe against unaligned access, you should
+ use editHeader() instead, but we need it for the Lua bindings */
+ dnsheader* getMutableHeader() const
+ {
+ if (data.size() < sizeof(dnsheader)) {
+ throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ return reinterpret_cast<dnsheader*>(data.data());
+ }
+
+ bool hasRoomFor(size_t more) const
+ {
+ return data.size() <= getMaximumSize() && (getMaximumSize() - data.size()) >= more;
+ }
+
+ size_t getMaximumSize() const
+ {
+ if (overTCP()) {
+ return std::numeric_limits<uint16_t>::max();
+ }
+ return 4096;
+ }
+
+ dnsdist::Protocol getProtocol() const
+ {
+ return ids.protocol;
+ }
+
+ bool overTCP() const
+ {
+ return !(ids.protocol == dnsdist::Protocol::DoUDP || ids.protocol == dnsdist::Protocol::DNSCryptUDP);
+ }
+
+ void setTag(std::string&& key, std::string&& value)
+ {
+ if (!ids.qTag) {
+ ids.qTag = std::make_unique<QTag>();
+ }
+ ids.qTag->insert_or_assign(std::move(key), std::move(value));
+ }
+
+ void setTag(const std::string& key, const std::string& value)
+ {
+ if (!ids.qTag) {
+ ids.qTag = std::make_unique<QTag>();
+ }
+ ids.qTag->insert_or_assign(key, value);
+ }
+
+ void setTag(const std::string& key, std::string&& value)
+ {
+ if (!ids.qTag) {
+ ids.qTag = std::make_unique<QTag>();
+ }
+ ids.qTag->insert_or_assign(key, std::move(value));
+ }
+
+ const struct timespec& getQueryRealTime() const
+ {
+ return ids.queryRealTime.d_start;
+ }
+
+ bool isAsynchronous() const
+ {
+ return asynchronous;
+ }
+
+ std::shared_ptr<IncomingTCPConnectionState> getIncomingTCPState() const
+ {
+ return d_incomingTCPState;
+ }
+
+ ClientState* getFrontend() const
+ {
+ return ids.cs;
+ }
+
+protected:
+ PacketBuffer& data;
+
+public:
+ InternalQueryState& ids;
+ std::unique_ptr<Netmask> ecs{nullptr};
+ std::string sni; /* Server Name Indication, if any (DoT or DoH) */
+ mutable std::unique_ptr<EDNSOptionViewMap> ednsOptions; /* this needs to be mutable because it is parsed just in time, when DNSQuestion is read-only */
+ std::shared_ptr<IncomingTCPConnectionState> d_incomingTCPState{nullptr};
+ std::unique_ptr<std::vector<ProxyProtocolValue>> proxyProtocolValues{nullptr};
+ uint16_t ecsPrefixLength;
+ uint8_t ednsRCode{0};
+ bool ecsOverride;
+ bool useECS{true};
+ bool addXPF{true};
+ bool asynchronous{false};
+};
+
+struct DownstreamState;
+
+struct DNSResponse : DNSQuestion
+{
+ DNSResponse(InternalQueryState& ids_, PacketBuffer& data_, const std::shared_ptr<DownstreamState>& downstream) :
+ DNSQuestion(ids_, data_), d_downstream(downstream) {}
+ DNSResponse(const DNSResponse&) = delete;
+ DNSResponse& operator=(const DNSResponse&) = delete;
+ DNSResponse(DNSResponse&&) = default;
+
+ const std::shared_ptr<DownstreamState>& d_downstream;
+};
+
+/* so what could you do:
+ drop,
+ fake up nxdomain,
+ provide actual answer,
+ allow & and stop processing,
+ continue processing,
+ modify header: (servfail|refused|notimp), set TC=1,
+ send to pool */
+
+class DNSAction
+{
+public:
+ enum class Action : uint8_t
+ {
+ Drop,
+ Nxdomain,
+ Refused,
+ Spoof,
+ Allow,
+ HeaderModify,
+ Pool,
+ Delay,
+ Truncate,
+ ServFail,
+ None,
+ NoOp,
+ NoRecurse,
+ SpoofRaw,
+ SpoofPacket
+ };
+ static std::string typeToString(const Action& action)
+ {
+ switch (action) {
+ case Action::Drop:
+ return "Drop";
+ case Action::Nxdomain:
+ return "Send NXDomain";
+ case Action::Refused:
+ return "Send Refused";
+ case Action::Spoof:
+ return "Spoof an answer";
+ case Action::SpoofPacket:
+ return "Spoof a raw answer from bytes";
+ case Action::SpoofRaw:
+ return "Spoof an answer from raw bytes";
+ case Action::Allow:
+ return "Allow";
+ case Action::HeaderModify:
+ return "Modify the header";
+ case Action::Pool:
+ return "Route to a pool";
+ case Action::Delay:
+ return "Delay";
+ case Action::Truncate:
+ return "Truncate over UDP";
+ case Action::ServFail:
+ return "Send ServFail";
+ case Action::None:
+ case Action::NoOp:
+ return "Do nothing";
+ case Action::NoRecurse:
+ return "Set rd=0";
+ }
+
+ return "Unknown";
+ }
+
+ virtual Action operator()(DNSQuestion*, string* ruleresult) const = 0;
+ virtual ~DNSAction()
+ {
+ }
+ virtual string toString() const = 0;
+ virtual std::map<string, double> getStats() const
+ {
+ return {{}};
+ }
+ virtual void reload()
+ {
+ }
+};
+
+class DNSResponseAction
+{
+public:
+ enum class Action : uint8_t
+ {
+ Allow,
+ Delay,
+ Drop,
+ HeaderModify,
+ ServFail,
+ Truncate,
+ None
+ };
+ virtual Action operator()(DNSResponse*, string* ruleresult) const = 0;
+ virtual ~DNSResponseAction()
+ {
+ }
+ virtual string toString() const = 0;
+ virtual void reload()
+ {
+ }
+};
+
+struct DynBlock
+{
+ DynBlock() :
+ action(DNSAction::Action::None), warning(false)
+ {
+ until.tv_sec = 0;
+ until.tv_nsec = 0;
+ }
+
+ DynBlock(const std::string& reason_, const struct timespec& until_, const DNSName& domain_, DNSAction::Action action_) :
+ reason(reason_), domain(domain_), until(until_), action(action_), warning(false)
+ {
+ }
+
+ DynBlock(const DynBlock& rhs) :
+ reason(rhs.reason), domain(rhs.domain), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
+ {
+ blocks.store(rhs.blocks);
+ }
+
+ DynBlock(DynBlock&& rhs) :
+ reason(std::move(rhs.reason)), domain(std::move(rhs.domain)), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
+ {
+ blocks.store(rhs.blocks);
+ }
+
+ DynBlock& operator=(const DynBlock& rhs)
+ {
+ reason = rhs.reason;
+ until = rhs.until;
+ domain = rhs.domain;
+ action = rhs.action;
+ blocks.store(rhs.blocks);
+ warning = rhs.warning;
+ bpf = rhs.bpf;
+ return *this;
+ }
+
+ DynBlock& operator=(DynBlock&& rhs)
+ {
+ reason = std::move(rhs.reason);
+ until = rhs.until;
+ domain = std::move(rhs.domain);
+ action = rhs.action;
+ blocks.store(rhs.blocks);
+ warning = rhs.warning;
+ bpf = rhs.bpf;
+ return *this;
+ }
+
+ string reason;
+ DNSName domain;
+ struct timespec until;
+ mutable std::atomic<unsigned int> blocks;
+ DNSAction::Action action{DNSAction::Action::None};
+ bool warning{false};
+ bool bpf{false};
+};
+
+extern GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
+
+extern vector<pair<struct timeval, std::string>> g_confDelta;
+
+using pdns::stat_t;
+
+class BasicQPSLimiter
+{
+public:
+ BasicQPSLimiter()
+ {
+ }
+
+ BasicQPSLimiter(unsigned int burst) :
+ d_tokens(burst)
+ {
+ d_prev.start();
+ }
+
+ virtual ~BasicQPSLimiter()
+ {
+ }
+
+ bool check(unsigned int rate, unsigned int burst) const // this is not quite fair
+ {
+ if (checkOnly(rate, burst)) {
+ addHit();
+ return true;
+ }
+
+ return false;
+ }
+
+ bool checkOnly(unsigned int rate, unsigned int burst) const // this is not quite fair
+ {
+ auto delta = d_prev.udiffAndSet();
+
+ if (delta > 0.0) { // time, frequently, does go backwards..
+ d_tokens += 1.0 * rate * (delta / 1000000.0);
+ }
+
+ if (d_tokens > burst) {
+ d_tokens = burst;
+ }
+
+ bool ret = false;
+ if (d_tokens >= 1.0) { // we need this because burst=1 is weird otherwise
+ ret = true;
+ }
+
+ return ret;
+ }
+
+ virtual void addHit() const
+ {
+ --d_tokens;
+ }
+
+ bool seenSince(const struct timespec& cutOff) const
+ {
+ return cutOff < d_prev.d_start;
+ }
+
+protected:
+ mutable StopWatch d_prev;
+ mutable double d_tokens{0.0};
+};
+
+class QPSLimiter : public BasicQPSLimiter
+{
+public:
+ QPSLimiter() :
+ BasicQPSLimiter()
+ {
+ }
+
+ QPSLimiter(unsigned int rate, unsigned int burst) :
+ BasicQPSLimiter(burst), d_rate(rate), d_burst(burst), d_passthrough(false)
+ {
+ d_prev.start();
+ }
+
+ unsigned int getRate() const
+ {
+ return d_passthrough ? 0 : d_rate;
+ }
+
+ bool check() const // this is not quite fair
+ {
+ if (d_passthrough) {
+ return true;
+ }
+
+ return BasicQPSLimiter::check(d_rate, d_burst);
+ }
+
+ bool checkOnly() const
+ {
+ if (d_passthrough) {
+ return true;
+ }
+
+ return BasicQPSLimiter::checkOnly(d_rate, d_burst);
+ }
+
+ void addHit() const override
+ {
+ if (!d_passthrough) {
+ --d_tokens;
+ }
+ }
+
+private:
+ unsigned int d_rate{0};
+ unsigned int d_burst{0};
+ bool d_passthrough{true};
+};
+
+typedef std::unordered_map<string, unsigned int> QueryCountRecords;
+typedef std::function<std::tuple<bool, string>(const DNSQuestion* dq)> QueryCountFilter;
+struct QueryCount
+{
+ QueryCount()
+ {
+ }
+ ~QueryCount()
+ {
+ }
+ SharedLockGuarded<QueryCountRecords> records;
+ QueryCountFilter filter;
+ bool enabled{false};
+};
+
+extern QueryCount g_qcount;
+
+class XskPacket;
+class XskSocket;
+class XskWorker;
+
+struct ClientState
+{
+ ClientState(const ComboAddress& local_, bool isTCP_, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_, bool enableProxyProtocol) :
+ cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort), d_enableProxyProtocol(enableProxyProtocol)
+ {
+ }
+
+ stat_t queries{0};
+ stat_t nonCompliantQueries{0};
+ mutable stat_t responses{0};
+ mutable stat_t tcpDiedReadingQuery{0};
+ mutable stat_t tcpDiedSendingResponse{0};
+ mutable stat_t tcpGaveUp{0};
+ mutable stat_t tcpClientTimeouts{0};
+ mutable stat_t tcpDownstreamTimeouts{0};
+ /* current number of connections to this frontend */
+ mutable stat_t tcpCurrentConnections{0};
+ /* maximum number of concurrent connections to this frontend reached */
+ mutable stat_t tcpMaxConcurrentConnections{0};
+ stat_t tlsNewSessions{0}; // A new TLS session has been negotiated, no resumption
+ stat_t tlsResumptions{0}; // A TLS session has been resumed, either via session id or via a TLS ticket
+ stat_t tlsUnknownTicketKey{0}; // A TLS ticket has been presented but we don't have the associated key (might have expired)
+ stat_t tlsInactiveTicketKey{0}; // A TLS ticket has been successfully resumed but the key is no longer active, we should issue a new one
+ stat_t tls10queries{0}; // valid DNS queries received via TLSv1.0
+ stat_t tls11queries{0}; // valid DNS queries received via TLSv1.1
+ stat_t tls12queries{0}; // valid DNS queries received via TLSv1.2
+ stat_t tls13queries{0}; // valid DNS queries received via TLSv1.3
+ stat_t tlsUnknownqueries{0}; // valid DNS queries received via unknown TLS version
+ pdns::stat_t_trait<double> tcpAvgQueriesPerConnection{0.0};
+ /* in ms */
+ pdns::stat_t_trait<double> tcpAvgConnectionDuration{0.0};
+ std::set<int> cpus;
+ std::string interface;
+ ComboAddress local;
+ std::vector<std::pair<ComboAddress, int>> d_additionalAddresses;
+ std::shared_ptr<DNSCryptContext> dnscryptCtx{nullptr};
+ std::shared_ptr<TLSFrontend> tlsFrontend{nullptr};
+ std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
+ std::shared_ptr<DOQFrontend> doqFrontend{nullptr};
+ std::shared_ptr<DOH3Frontend> doh3Frontend{nullptr};
+ std::shared_ptr<BPFFilter> d_filter{nullptr};
+ std::shared_ptr<XskWorker> xskInfo{nullptr};
+ size_t d_maxInFlightQueriesPerConn{1};
+ size_t d_tcpConcurrentConnectionsLimit{0};
+ int udpFD{-1};
+ int tcpFD{-1};
+ int tcpListenQueueSize{SOMAXCONN};
+ int fastOpenQueueSize{0};
+ bool muted{false};
+ bool tcp;
+ bool reuseport;
+ bool d_enableProxyProtocol{true}; // the global proxy protocol ACL still applies
+ bool ready{false};
+
+ int getSocket() const
+ {
+ return udpFD != -1 ? udpFD : tcpFD;
+ }
+
+ bool isUDP() const
+ {
+ return udpFD != -1;
+ }
+
+ bool isTCP() const
+ {
+ return udpFD == -1;
+ }
+
+ bool isDoH() const
+ {
+ return dohFrontend != nullptr;
+ }
+
+ bool hasTLS() const
+ {
+ return tlsFrontend != nullptr || (dohFrontend != nullptr && dohFrontend->isHTTPS());
+ }
+
+ const TLSFrontend& getTLSFrontend() const
+ {
+ if (tlsFrontend != nullptr) {
+ return *tlsFrontend;
+ }
+ if (dohFrontend) {
+ return dohFrontend->d_tlsContext;
+ }
+ throw std::runtime_error("Trying to get a TLS frontend from a non-TLS ClientState");
+ }
+
+ dnsdist::Protocol getProtocol() const
+ {
+ if (dnscryptCtx) {
+ if (udpFD != -1) {
+ return dnsdist::Protocol::DNSCryptUDP;
+ }
+ return dnsdist::Protocol::DNSCryptTCP;
+ }
+ if (isDoH()) {
+ return dnsdist::Protocol::DoH;
+ }
+ else if (hasTLS()) {
+ return dnsdist::Protocol::DoT;
+ }
+ else if (doqFrontend != nullptr) {
+ return dnsdist::Protocol::DoQ;
+ }
+ else if (doh3Frontend != nullptr) {
+ return dnsdist::Protocol::DoH3;
+ }
+ else if (udpFD != -1) {
+ return dnsdist::Protocol::DoUDP;
+ }
+ else {
+ return dnsdist::Protocol::DoTCP;
+ }
+ }
+
+ std::string getType() const
+ {
+ std::string result = udpFD != -1 ? "UDP" : "TCP";
+
+ if (doqFrontend) {
+ result += " (DNS over QUIC)";
+ }
+ else if (doh3Frontend) {
+ result += " (DNS over HTTP/3)";
+ }
+ else if (dohFrontend) {
+ if (dohFrontend->isHTTPS()) {
+ result += " (DNS over HTTPS)";
+ }
+ else {
+ result += " (DNS over HTTP)";
+ }
+ }
+ else if (tlsFrontend) {
+ result += " (DNS over TLS)";
+ }
+ else if (dnscryptCtx) {
+ result += " (DNSCrypt)";
+ }
+
+ return result;
+ }
+
+ void detachFilter(int socket)
+ {
+ if (d_filter) {
+ d_filter->removeSocket(socket);
+ d_filter = nullptr;
+ }
+ }
+
+ void attachFilter(shared_ptr<BPFFilter>& bpf, int socket)
+ {
+ detachFilter(socket);
+
+ bpf->addSocket(socket);
+ d_filter = bpf;
+ }
+
+ void detachFilter()
+ {
+ if (d_filter) {
+ detachFilter(getSocket());
+ for (const auto& [addr, socket] : d_additionalAddresses) {
+ (void)addr;
+ if (socket != -1) {
+ detachFilter(socket);
+ }
+ }
+
+ d_filter = nullptr;
+ }
+ }
+
+ void attachFilter(shared_ptr<BPFFilter>& bpf)
+ {
+ detachFilter();
+
+ bpf->addSocket(getSocket());
+ for (const auto& [addr, socket] : d_additionalAddresses) {
+ (void)addr;
+ if (socket != -1) {
+ bpf->addSocket(socket);
+ }
+ }
+ d_filter = bpf;
+ }
+
+ void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
+ {
+ tcpAvgQueriesPerConnection = (99.0 * tcpAvgQueriesPerConnection / 100.0) + (nbQueries / 100.0);
+ tcpAvgConnectionDuration = (99.0 * tcpAvgConnectionDuration / 100.0) + (durationMs / 100.0);
+ }
+};
+
+struct CrossProtocolQuery;
+
+struct DownstreamState : public std::enable_shared_from_this<DownstreamState>
+{
+ DownstreamState(const DownstreamState&) = delete;
+ DownstreamState(DownstreamState&&) = delete;
+ DownstreamState& operator=(const DownstreamState&) = delete;
+ DownstreamState& operator=(DownstreamState&&) = delete;
+
+ typedef std::function<std::tuple<DNSName, uint16_t, uint16_t>(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t;
+ enum class Availability : uint8_t
+ {
+ Up,
+ Down,
+ Auto,
+ Lazy
+ };
+ enum class LazyHealthCheckMode : uint8_t
+ {
+ TimeoutOnly,
+ TimeoutOrServFail
+ };
+
+ struct Config
+ {
+ Config()
+ {
+ }
+ Config(const ComboAddress& remote_) :
+ remote(remote_)
+ {
+ }
+
+ TLSContextParameters d_tlsParams;
+ set<string> pools;
+ std::set<int> d_cpus;
+ checkfunc_t checkFunction;
+ std::optional<boost::uuids::uuid> id;
+ DNSName checkName{"a.root-servers.net."};
+ ComboAddress remote;
+ ComboAddress sourceAddr;
+ std::string sourceItfName;
+ std::string d_tlsSubjectName;
+ std::string d_dohPath;
+ std::string name;
+ std::string nameWithAddr;
+#ifdef HAVE_XSK
+ std::array<uint8_t, 6> sourceMACAddr;
+ std::array<uint8_t, 6> destMACAddr;
+#endif /* HAVE_XSK */
+ size_t d_numberOfSockets{1};
+ size_t d_maxInFlightQueriesPerConn{1};
+ size_t d_tcpConcurrentConnectionsLimit{0};
+ int order{1};
+ int d_weight{1};
+ int tcpConnectTimeout{5};
+ int tcpRecvTimeout{30};
+ int tcpSendTimeout{30};
+ int d_qpsLimit{0};
+ unsigned int checkInterval{1};
+ unsigned int sourceItf{0};
+ QType checkType{QType::A};
+ uint16_t checkClass{QClass::IN};
+ uint16_t d_retries{5};
+ uint16_t xpfRRCode{0};
+ uint16_t checkTimeout{1000}; /* in milliseconds */
+ uint16_t d_lazyHealthCheckSampleSize{100};
+ uint16_t d_lazyHealthCheckMinSampleCount{1};
+ uint16_t d_lazyHealthCheckFailedInterval{30};
+ uint16_t d_lazyHealthCheckMaxBackOff{3600};
+ uint8_t d_lazyHealthCheckThreshold{20};
+ LazyHealthCheckMode d_lazyHealthCheckMode{LazyHealthCheckMode::TimeoutOrServFail};
+ uint8_t maxCheckFailures{1};
+ uint8_t minRiseSuccesses{1};
+ Availability availability{Availability::Auto};
+ bool d_tlsSubjectIsAddr{false};
+ bool mustResolve{false};
+ bool useECS{false};
+ bool useProxyProtocol{false};
+ bool d_proxyProtocolAdvertiseTLS{false};
+ bool setCD{false};
+ bool disableZeroScope{false};
+ bool tcpFastOpen{false};
+ bool ipBindAddrNoPort{true};
+ bool reconnectOnUp{false};
+ bool d_tcpCheck{false};
+ bool d_tcpOnly{false};
+ bool d_addXForwardedHeaders{false}; // for DoH backends
+ bool d_lazyHealthCheckUseExponentialBackOff{false};
+ bool d_upgradeToLazyHealthChecks{false};
+ };
+
+ struct HealthCheckMetrics
+ {
+ stat_t d_failures{0};
+ stat_t d_timeOuts{0};
+ stat_t d_parseErrors{0};
+ stat_t d_networkErrors{0};
+ stat_t d_mismatchErrors{0};
+ stat_t d_invalidResponseErrors{0};
+ };
+
+ DownstreamState(DownstreamState::Config&& config, std::shared_ptr<TLSCtx> tlsCtx, bool connect);
+ DownstreamState(const ComboAddress& remote) :
+ DownstreamState(DownstreamState::Config(remote), nullptr, false)
+ {
+ }
+
+ ~DownstreamState();
+
+ Config d_config;
+ HealthCheckMetrics d_healthCheckMetrics;
+ stat_t sendErrors{0};
+ stat_t outstanding{0};
+ stat_t reuseds{0};
+ stat_t queries{0};
+ stat_t responses{0};
+ stat_t nonCompliantResponses{0};
+ struct
+ {
+ stat_t sendErrors{0};
+ stat_t reuseds{0};
+ stat_t queries{0};
+ } prev;
+ stat_t tcpDiedSendingQuery{0};
+ stat_t tcpDiedReadingResponse{0};
+ stat_t tcpGaveUp{0};
+ stat_t tcpReadTimeouts{0};
+ stat_t tcpWriteTimeouts{0};
+ stat_t tcpConnectTimeouts{0};
+ /* current number of connections to this backend */
+ stat_t tcpCurrentConnections{0};
+ /* maximum number of concurrent connections to this backend reached */
+ stat_t tcpMaxConcurrentConnections{0};
+ /* number of times we had to enforce the maximum concurrent connections limit */
+ stat_t tcpTooManyConcurrentConnections{0};
+ stat_t tcpReusedConnections{0};
+ stat_t tcpNewConnections{0};
+ stat_t tlsResumptions{0};
+ pdns::stat_t_trait<double> tcpAvgQueriesPerConnection{0.0};
+ /* in ms */
+ pdns::stat_t_trait<double> tcpAvgConnectionDuration{0.0};
+ pdns::stat_t_trait<double> queryLoad{0.0};
+ pdns::stat_t_trait<double> dropRate{0.0};
+
+ SharedLockGuarded<std::vector<unsigned int>> hashes;
+ LockGuarded<std::unique_ptr<FDMultiplexer>> mplexer{nullptr};
+
+private:
+ LockGuarded<std::map<uint16_t, IDState>> d_idStatesMap;
+ vector<IDState> idStates;
+
+ struct LazyHealthCheckStats
+ {
+ boost::circular_buffer<bool> d_lastResults;
+ time_t d_nextCheck{0};
+ enum class LazyStatus : uint8_t
+ {
+ Healthy = 0,
+ PotentialFailure,
+ Failed
+ };
+ LazyStatus d_status{LazyStatus::Healthy};
+ };
+ LockGuarded<LazyHealthCheckStats> d_lazyHealthCheckStats;
+
+public:
+ std::shared_ptr<TLSCtx> d_tlsCtx{nullptr};
+ std::vector<int> sockets;
+ StopWatch sw;
+ QPSLimiter qps;
+#ifdef HAVE_XSK
+ std::vector<std::shared_ptr<XskWorker>> d_xskInfos;
+ std::vector<std::shared_ptr<XskSocket>> d_xskSockets;
+#endif
+ std::atomic<uint64_t> idOffset{0};
+ size_t socketsOffset{0};
+ double latencyUsec{0.0};
+ double latencyUsecTCP{0.0};
+ unsigned int d_nextCheck{0};
+ uint16_t currentCheckFailures{0};
+ std::atomic<bool> hashesComputed{false};
+ std::atomic<bool> connected{false};
+ bool upStatus{false};
+
+private:
+ void handleUDPTimeout(IDState& ids);
+ void updateNextLazyHealthCheck(LazyHealthCheckStats& stats, bool checkScheduled, std::optional<time_t> currentTime = std::nullopt);
+ void connectUDPSockets();
+#ifdef HAVE_XSK
+ void addXSKDestination(int fd);
+ void removeXSKDestination(int fd);
+#endif /* HAVE_XSK */
+
+ std::mutex connectLock;
+ std::condition_variable d_connectedWait;
+#ifdef HAVE_XSK
+ SharedLockGuarded<std::vector<ComboAddress>> d_socketSourceAddresses;
+#endif
+ std::atomic_flag threadStarted;
+ uint8_t consecutiveSuccessfulChecks{0};
+ bool d_stopped{false};
+
+public:
+ void updateStatisticsInfo()
+ {
+ auto delta = sw.udiffAndSet() / 1000000.0;
+ queryLoad.store(1.0 * (queries.load() - prev.queries.load()) / delta);
+ dropRate.store(1.0 * (reuseds.load() - prev.reuseds.load()) / delta);
+ prev.queries.store(queries.load());
+ prev.reuseds.store(reuseds.load());
+ }
+ void start();
+
+ bool isUp() const
+ {
+ if (d_config.availability == Availability::Down) {
+ return false;
+ }
+ else if (d_config.availability == Availability::Up) {
+ return true;
+ }
+ return upStatus;
+ }
+
+ void setUp()
+ {
+ d_config.availability = Availability::Up;
+ }
+
+ void setUpStatus(bool newStatus)
+ {
+ upStatus = newStatus;
+ if (!upStatus) {
+ latencyUsec = 0.0;
+ latencyUsecTCP = 0.0;
+ }
+ }
+ void setDown()
+ {
+ d_config.availability = Availability::Down;
+ latencyUsec = 0.0;
+ latencyUsecTCP = 0.0;
+ }
+ void setAuto()
+ {
+ d_config.availability = Availability::Auto;
+ }
+ void setLazyAuto()
+ {
+ d_config.availability = Availability::Lazy;
+ d_lazyHealthCheckStats.lock()->d_lastResults.set_capacity(d_config.d_lazyHealthCheckSampleSize);
+ }
+ bool healthCheckRequired(std::optional<time_t> currentTime = std::nullopt);
+
+ const string& getName() const
+ {
+ return d_config.name;
+ }
+ const string& getNameWithAddr() const
+ {
+ return d_config.nameWithAddr;
+ }
+ void setName(const std::string& newName)
+ {
+ d_config.name = newName;
+ d_config.nameWithAddr = newName.empty() ? d_config.remote.toStringWithPort() : (d_config.name + " (" + d_config.remote.toStringWithPort() + ")");
+ }
+
+ string getStatus() const
+ {
+ string status;
+ if (d_config.availability == DownstreamState::Availability::Up) {
+ status = "UP";
+ }
+ else if (d_config.availability == DownstreamState::Availability::Down) {
+ status = "DOWN";
+ }
+ else {
+ status = (upStatus ? "up" : "down");
+ }
+ return status;
+ }
+
+ bool reconnect(bool initialAttempt = false);
+ void waitUntilConnected();
+ void hash();
+ void setId(const boost::uuids::uuid& newId);
+ void setWeight(int newWeight);
+ void stop();
+ bool isStopped() const
+ {
+ return d_stopped;
+ }
+ const boost::uuids::uuid& getID() const
+ {
+ return *d_config.id;
+ }
+
+ void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
+ {
+ tcpAvgQueriesPerConnection = (99.0 * tcpAvgQueriesPerConnection / 100.0) + (nbQueries / 100.0);
+ tcpAvgConnectionDuration = (99.0 * tcpAvgConnectionDuration / 100.0) + (durationMs / 100.0);
+ }
+
+ void updateTCPLatency(double udiff)
+ {
+ latencyUsecTCP = (127.0 * latencyUsecTCP / 128.0) + udiff / 128.0;
+ }
+
+ void incQueriesCount()
+ {
+ ++queries;
+ qps.addHit();
+ }
+
+ void incCurrentConnectionsCount();
+
+ bool doHealthcheckOverTCP() const
+ {
+ return d_config.d_tcpOnly || d_config.d_tcpCheck || d_tlsCtx != nullptr;
+ }
+
+ bool isTCPOnly() const
+ {
+ return d_config.d_tcpOnly || d_tlsCtx != nullptr;
+ }
+
+ bool isDoH() const
+ {
+ return !d_config.d_dohPath.empty();
+ }
+
+ bool passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq);
+ int pickSocketForSending();
+ void pickSocketsReadyForReceiving(std::vector<int>& ready);
+ void handleUDPTimeouts();
+ void reportTimeoutOrError();
+ void reportResponse(uint8_t rcode);
+ void submitHealthCheckResult(bool initial, bool newResult);
+ time_t getNextLazyHealthCheck();
+ uint16_t saveState(InternalQueryState&&);
+ void restoreState(uint16_t id, InternalQueryState&&);
+ std::optional<InternalQueryState> getState(uint16_t id);
+
+#ifdef HAVE_XSK
+ void registerXsk(std::vector<std::shared_ptr<XskSocket>>& xsks);
+ [[nodiscard]] ComboAddress pickSourceAddressForSending();
+#endif /* HAVE_XSK */
+
+ dnsdist::Protocol getProtocol() const
+ {
+ if (isDoH()) {
+ return dnsdist::Protocol::DoH;
+ }
+ if (d_tlsCtx != nullptr) {
+ return dnsdist::Protocol::DoT;
+ }
+ if (isTCPOnly()) {
+ return dnsdist::Protocol::DoTCP;
+ }
+ return dnsdist::Protocol::DoUDP;
+ }
+
+ double getRelevantLatencyUsec() const
+ {
+ if (isTCPOnly()) {
+ return latencyUsecTCP;
+ }
+ return latencyUsec;
+ }
+
+ static int s_udpTimeout;
+ static bool s_randomizeSockets;
+ static bool s_randomizeIDs;
+};
+using servers_t = vector<std::shared_ptr<DownstreamState>>;
+
+void responderThread(std::shared_ptr<DownstreamState> dss);
+extern LockGuarded<LuaContext> g_lua;
+extern std::string g_outputBuffer; // locking for this is ok, as locked by g_luamutex
+
+class DNSRule
+{
+public:
+ virtual ~DNSRule()
+ {
+ }
+ virtual bool matches(const DNSQuestion* dq) const = 0;
+ virtual string toString() const = 0;
+ mutable stat_t d_matches{0};
+};
+
+struct ServerPool
+{
+ ServerPool() :
+ d_servers(std::make_shared<const ServerPolicy::NumberedServerVector>())
+ {
+ }
+
+ ~ServerPool()
+ {
+ }
+
+ const std::shared_ptr<DNSDistPacketCache> getCache() const { return packetCache; };
+
+ bool getECS() const
+ {
+ return d_useECS;
+ }
+
+ void setECS(bool useECS)
+ {
+ d_useECS = useECS;
+ }
+
+ std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
+ std::shared_ptr<ServerPolicy> policy{nullptr};
+
+ size_t poolLoad();
+ size_t countServers(bool upOnly);
+ const std::shared_ptr<const ServerPolicy::NumberedServerVector> getServers();
+ void addServer(shared_ptr<DownstreamState>& server);
+ void removeServer(shared_ptr<DownstreamState>& server);
+
+private:
+ SharedLockGuarded<std::shared_ptr<const ServerPolicy::NumberedServerVector>> d_servers;
+ bool d_useECS{false};
+};
+
+enum ednsHeaderFlags
+{
+ EDNS_HEADER_FLAG_NONE = 0,
+ EDNS_HEADER_FLAG_DO = 32768
+};
+
+extern GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
+extern DNSAction::Action g_dynBlockAction;
+
+extern GlobalStateHolder<ServerPolicy> g_policy;
+extern GlobalStateHolder<servers_t> g_dstates;
+extern GlobalStateHolder<pools_t> g_pools;
+extern GlobalStateHolder<NetmaskGroup> g_ACL;
+
+extern ComboAddress g_serverControl; // not changed during runtime
+
+extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
+extern std::vector<shared_ptr<DOHFrontend>> g_dohlocals;
+extern std::vector<shared_ptr<DOQFrontend>> g_doqlocals;
+extern std::vector<shared_ptr<DOH3Frontend>> g_doh3locals;
+extern std::vector<std::unique_ptr<ClientState>> g_frontends;
+extern bool g_truncateTC;
+extern bool g_fixupCase;
+extern int g_tcpRecvTimeout;
+extern int g_tcpSendTimeout;
+extern uint16_t g_maxOutstanding;
+extern std::atomic<bool> g_configurationDone;
+extern boost::optional<uint64_t> g_maxTCPClientThreads;
+extern uint64_t g_maxTCPQueuedConnections;
+extern size_t g_maxTCPQueriesPerConn;
+extern size_t g_maxTCPConnectionDuration;
+extern size_t g_tcpInternalPipeBufferSize;
+extern pdns::stat16_t g_cacheCleaningDelay;
+extern pdns::stat16_t g_cacheCleaningPercentage;
+extern uint32_t g_staleCacheEntriesTTL;
+extern bool g_apiReadWrite;
+extern std::string g_apiConfigDirectory;
+extern bool g_servFailOnNoPolicy;
+extern size_t g_udpVectorSize;
+extern bool g_allowEmptyResponse;
+extern uint32_t g_socketUDPSendBuffer;
+extern uint32_t g_socketUDPRecvBuffer;
+
+extern shared_ptr<BPFFilter> g_defaultBPFFilter;
+extern std::vector<std::shared_ptr<DynBPFFilter>> g_dynBPFFilters;
+
+void tcpAcceptorThread(const std::vector<ClientState*>& states);
+
+void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects
+void setLuaSideEffect(); // set to report a side effect, cancelling all _no_ side effect calls
+bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect
+void resetLuaSideEffect(); // reset to indeterminate state
+
+bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote);
+
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState);
+
+extern std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
+bool handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response);
+bool checkDNSCryptQuery(const ClientState& clientState, PacketBuffer& query, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp);
+
+#include "dnsdist-snmp.hh"
+
+extern bool g_snmpEnabled;
+extern bool g_snmpTrapsEnabled;
+extern std::unique_ptr<DNSDistSNMPAgent> g_snmpAgent;
+extern bool g_addEDNSToSelfGeneratedResponses;
+
+extern std::set<std::string> g_capabilitiesToRetain;
+static const uint16_t s_udpIncomingBufferSize{1500}; // don't accept UDP queries larger than this value
+
+enum class ProcessQueryResult : uint8_t
+{
+ Drop,
+ SendAnswer,
+ PassToBackend,
+ Asynchronous
+};
+
+#include "dnsdist-rule-chains.hh"
+
+struct LocalHolders
+{
+ LocalHolders() :
+ acl(g_ACL.getLocal()), policy(g_policy.getLocal()), ruleactions(dnsdist::rules::g_ruleactions.getLocal()), cacheHitRespRuleactions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheHitResponseRules).getLocal()), cacheInsertedRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal()), selfAnsweredRespRuleactions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::SelfAnsweredResponseRules).getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
+ {
+ }
+
+ LocalStateHolder<NetmaskGroup> acl;
+ LocalStateHolder<ServerPolicy> policy;
+ LocalStateHolder<vector<dnsdist::rules::RuleAction>> ruleactions;
+ LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> cacheHitRespRuleactions;
+ LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> cacheInsertedRespRuleActions;
+ LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> selfAnsweredRespRuleactions;
+ LocalStateHolder<servers_t> servers;
+ LocalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> dynNMGBlock;
+ LocalStateHolder<SuffixMatchTree<DynBlock>> dynSMTBlock;
+ LocalStateHolder<pools_t> pools;
+};
+
+ProcessQueryResult processQuery(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
+ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
+bool processResponse(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted);
+bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dnsQuestion, std::string& ruleresult, bool& drop);
+bool processResponseAfterRules(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted);
+bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& localRespRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids);
+
+bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend = true);
+
+ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& backend, const int socketDesc, const PacketBuffer& request, bool healthCheck = false);
+bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote);
+void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend);
+void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend);
-- send the queries for selected domain suffixes to the servers
-- in the 'abuse' pool
--- addAction({"abuse.example.org.", "xxx."}, PoolAction("abuse"))
+-- addAction(SuffixMatchNodeRule({"abuse.example.org.", "xxx."}), PoolAction("abuse"))
-- drop queries for this exact qname
-- addAction(QNameRule("drop-me.example.org."), DropAction())
-- send the queries from a selected subnet to the
-- abuse pool
--- addAction("192.0.2.0/24", PoolAction("abuse"))
+-- addAction(NetmaskGroupRule("192.0.2.0/24"), PoolAction("abuse"))
-- Refuse incoming AXFR, IXFR, NOTIFY and UPDATE
-- Add trusted sources (slaves, masters) explicitely in front of this rule
> bd = getBind(0)
> bd:attachFilter(bpf)
-:program:`dnsdist` also supports adding dynamic, expiring blocks to a BPF filter::
+:program:`dnsdist` also supports adding dynamic, expiring blocks to a BPF filter:
+.. code-block:: lua
+
+ bpf = newBPFFilter({ipv4MaxItems=1024, ipv6MaxItems=1024, qnamesMaxItems=1024})
+ setDefaultBPFFilter(bpf)
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(20, 10, "Exceeded query rate", 60)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+This will dynamically block all hosts that exceeded 20 queries/s as measured over the past 10 seconds, and the dynamic block will last for 60 seconds.
+
+Since 1.6.0, the default BPF filter set via :func:`setDefaultBPFFilter` will automatically get used when a "drop" dynamic block is inserted via a :ref:`DynBlockRulesGroup`, which provides a better way to combine dynamic blocks with eBPF filtering.
+Before that, it was possible to use the :func:`addBPFFilterDynBlocks` method instead:
+
+.. code-block:: lua
+
+ -- this is a legacy method, please see above for DNSdist >= 1.6.0
bpf = newBPFFilter({ipv4MaxItems=1024, ipv6MaxItems=1024, qnamesMaxItems=1024})
setDefaultBPFFilter(bpf)
dbpf = newDynBPFFilter(bpf)
dbpf:purgeExpired()
end
-This will dynamically block all hosts that exceeded 20 queries/s as measured over the past 10 seconds, and the dynamic block will last for 60 seconds.
-
The dynamic eBPF blocks and the number of queries they blocked can be seen in the web interface and retrieved from the API. Note however that eBPF dynamic objects need to be registered before they appear in the web interface or the API, using the :func:`registerDynBPFFilter` function::
registerDynBPFFilter(dbpf)
They can be unregistered at a later point using the :func:`unregisterDynBPFFilter` function.
-
-Since 1.6.0, the default BPF filter set via :func:`setDefaultBPFFilter` will automatically get used when a "drop" dynamic block is inserted via a :ref:`DynBlockRulesGroup`, which provides a better way to combine dynamic blocks with eBPF filtering.
+Since 1.8.2, the metrics for the BPF filter registered via :func:`setDefaultBPFFilter` are exported as well.
Requirements
------------
multiple-instance
out-of-order
ocsp-stapling
+ tls-certificates-management
tls-sessions-management
internal-design
asynchronous-processing
+ xsk
If the incoming request already contains a XPF record, it will not be overwritten. Instead a new one will be added to the query and the existing one will be preserved.
That might be an issue by allowing clients to spoof their source address by adding a forged XPF record to their query. That can be prevented by using a rule to drop incoming queries containing a XPF record (in that example the 65280 option code has been assigned to XPF):
+.. code-block:: lua
+
addAction(RecordsTypeCountRule(DNSSection.Additional, 65280, 1, 65535), DropAction())
Proxy Protocol
In order to use it in dnsdist, the ``useProxyProtocol`` parameter can be used when creating a :func:`new server <newServer>`.
This parameter indicates whether a Proxy Protocol version 2 (binary) header should be prepended to the query before forwarding it to the backend, over UDP or TCP.
-Such a Proxy Protocol header can also be passed from the client to dnsdist, using :func:`setProxyProtocolACL` to specify which clients to accept it from.
+Such a Proxy Protocol header can also be passed from the client to dnsdist, using :func:`setProxyProtocolACL` to specify which clients to accept it from. Note that a proxy protocol payload will be required from these clients, regular DNS queries will no longer be accepted if they are not preceded by a proxy protocol payload.
+
If :func:`setProxyProtocolApplyACLToProxiedClients` is set (default is false), the general ACL will be applied to the source IP address as seen by dnsdist first, but also to the source IP address provided in the Proxy Protocol header.
Custom values can be added to the header via :meth:`DNSQuestion:addProxyProtocolValue`, :meth:`DNSQuestion:setProxyProtocolValues`, :func:`SetAdditionalProxyProtocolValueAction` and :func:`SetProxyProtocolValuesAction`.
--- /dev/null
+TLS Certificates Management
+===========================
+
+TLS certificates and keys are used in several places of :program:`dnsdist`, dealing with incoming connections over :doc:`../guides/dns-over-tls`, :doc:`../guides/dns-over-https`, :doc:`../guides/dns-over-http3` and :doc:`../guides/dns-over-quic`.
+
+The related functions (:func:`addTLSLocal`, :func:`addDOHLocal`, :func:`addDOH3Local` and :func:`addDOQLocal`) accept:
+
+- a path to a X.509 certificate file in ``PEM`` format, or a list of paths to such files, or a :class:`TLSCertificate` object
+- a path to the private key file corresponding to the certificate, or a list of paths to such files whose order should match the certificate files ones. This parameter is ignored if the first one contains :class:`TLSCertificate` objects, as keys are then retrieved from the objects.
+
+For example, to load two certificates, one ``RSA`` and one ``ECDSA`` one:
+
+.. code-block:: lua
+
+ addTLSLocal("192.0.2.1:853", { "/path/to/rsa/pem", "/path/to/ecdsa/pem" }, { "/path/to/rsa/key", "/path/to/ecdsa/key" })
+
+Password-protected PKCS12 files
+-------------------------------
+
+.. note::
+
+ ``PKCS12`` support requires the use of the ``openssl`` TLS provider.
+
+:program:`dnsdist` can use password-protected ``PKCS12`` certificates and keys. The certificate and key are loaded from a password-protected file using :func:`newTLSCertificate`
+which returns a :class:`TLSCertificate` object, which can then be passed to :func:`addTLSLocal`, :func:`addDOHLocal`, :func:`addDOH3Local` and :func:`addDOQLocal`.
+
+.. code-block:: lua
+
+ myCertObject = newTLSCertificate("path/to/domain.p12", {password="passphrase"}) -- use a password protected PKCS12 file
+
+Reloading certificates
+----------------------
+
+There are two ways to instruct :program:`dnsdist` to reload the certificate and key files from disk. The easiest one is to use :func:`reloadAllCertificates` which reload all :doc:`../guides/dnscrypt` and TLS certificates, along with their associated keys.
+The second allows a finer-grained, per-bind, approach:
+
+.. code-block:: lua
+
+ -- reload certificates and keys for DoT binds:
+ for idx = 0, getTLSFrontendCount() - 1 do
+ frontend = getTLSFrontend(idx)
+ frontend:reloadCertificates()
+ end
+
+ -- reload certificates and keys for DoH binds:
+ for idx = 0, getDOHFrontendCount() - 1 do
+ frontend = getDOHFrontend(idx)
+ frontend:reloadCertificates()
+ end
+
+ -- reload certificates and keys for DoQ binds:
+ for idx = 0, getDOQFrontendCount() - 1 do
+ frontend = getDOQFrontend(idx)
+ frontend:reloadCertificates()
+ end
+
+ -- reload certificates and keys for DoH3 binds:
+ for idx = 0, getDOH3FrontendCount() - 1 do
+ frontend = getDOH3Frontend(idx)
+ frontend:reloadCertificates()
+ end
+
+TLS sessions
+------------
+
+See :doc:`tls-sessions-management`.
+
+OCSP stapling
+-------------
+
+See :doc:`ocsp-stapling`.
Since server-side sessions cannot be shared between several instances, and pretty much all clients support tickets anyway, we do recommend disabling the sessions by passing ``numberOfStoredSessions=0`` to the :func:`addDOHLocal` (for DNS over HTTPS) and :func:`addTLSLocal` (for DNS over TLS) functions.
-By default, dnsdist will generate a new, random STEK at startup and rotate it every 12 hours. It will keep 5 keys in memory, with only the last one marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation. The rotation time and the number of keys to keep in memory can be configured via the ``numberOfTicketsKeys`` and ``ticketsKeysRotationDelay`` parameters of the :func:`addDOHLocal` (for DNS over HTTPS) and :func:`addTLSLocal` (for DNS over TLS) functions.
+By default, dnsdist will generate a new, random STEK at startup for each :func:`addTLSLocal` and :func:`addDOHLocal` directive, and rotate these STEKs every 12 hours. For each frontend it will keep 5 keys in memory, with only the last one marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation. The rotation time and the number of keys to keep in memory can be configured via the ``numberOfTicketsKeys`` and ``ticketsKeysRotationDelay`` parameters of the :func:`addDOHLocal` (for DNS over HTTPS) and :func:`addTLSLocal` (for DNS over TLS) functions.
+When the automatic rotation mechanism kicks in a new, random key will be added to the list of keys. With the OpenSSL provider, the new key becomes active, so new tickets will be encrypted with this key, and the existing keys become passive and only be used to decrypt existing tickets. With the GnuTLS provider only one key is currently supported so the existing keys are immediately discarded.
+This automatic rotation can be disabled by setting ``ticketsKeysRotationDelay`` to 0.
It is also possible to manually request a STEK rotation using the :func:`getDOHFrontend` (DoH) and :func:`getTLSContext` (DoT) functions to retrieve the bind object, and calling its ``rotateTicketsKey`` method (:meth:`DOHFrontend:rotateTicketsKey`, :meth:`TLSContext:rotateTicketsKey`).
-The default settings should be fine for most deployments, but generating a random key for every dnsdist instance will not allow resuming the session from a different instance in a cluster. In that case it is possible to generate the STEK outside of dnsdist, write it to a file, distribute it to all instances using something like rsync over SSH, and load that file from dnsdist. Please remember that the STEK contains very sensitive data, and should be well-protected from access by unauthorized users. It means that special care should be taken to setting the right permissions on that file.
+The default settings should be fine for most deployments, but generating a random key for every dnsdist instance will not allow resuming the session from a different instance in a cluster. It is also not very useful to have a different key for every :func:`addTLSLocal` and :func:`addDOHLocal` directive if you are using the same certificate and key, and it would be much better to use the same STEK to improve the session resumption ratio.
+
+In that case it is possible to generate the STEK outside of dnsdist, write it to a file, distribute it to all instances using something like rsync over SSH, and load that file from dnsdist. Please remember that the STEK contains very sensitive data, and should be well-protected from access by unauthorized users. It means that special care should be taken to setting the right permissions on that file. Automatic rotation should then be disabled by setting ``ticketsKeysRotationDelay`` to 0.
For the OpenSSL provider (DoT, DoH), generating a random STEK in a file is a simple as getting 80 cryptographically secure random bytes and writing them to a file::
If the file contains several keys, so for example 240 random bytes, dnsdist will load several STEKs, using the last one for encrypting new tickets and all of them to decrypt existing tickets.
In order to rotate the keys at runtime, it is possible to instruct dnsdist to reload the content of the certificates, keys, and STEKs from the same file used at configuration time, for all DoH and DoH binds, by issuing the :func:`reloadAllCertificates` command.
-It can also be done one bind at a time using the :func:`getDOHFrontend` (DoH) and :func:`getTLSContext` (DoT) functions to retrieve the bind object, and calling its ``loadTicketsKeys`` method (:meth:`DOHFrontend.loadTicketsKeys`, :meth:`TLSContext:loadTicketsKeys`).
+It can also be done one bind at a time using the :func:`getDOHFrontend` (DoH) and :func:`getTLSContext` (DoT) functions to retrieve the bind object, and calling its ``loadTicketsKeys`` method (:meth:`DOHFrontend:loadTicketsKeys`, :meth:`TLSContext:loadTicketsKeys`).
+
+One possible way of handling manual rotation of the key would be to first:
+
+- generate ``N`` keys in ``N`` (``1..N``) separate files (for example executing ``dd if=/dev/urandom of=/secure-tmp-fs/N.key bs=80 count=1`` ``N`` times)
+- concatenate the ``N`` files into a single file (``/secure-tmp-fs/STEKs.key``) that you pass to dnsdist's ``ticketKeyFile`` parameter
+
+Then, when the STEK should be rotated:
+
+- generate one new key file (``N+1``)
+- delete the first key file (``1``)
+- concatenate the ``2..N+1`` files into one (``/secure-tmp-fs/STEKs.key``)
+- issue :func:`reloadAllCertificates` via the dnsdist console, or call ``loadTicketsKeys('/secure-tmp-fs/STEKs.key')`` for all frontends
+
+This way dnsdist can still decrypt incoming tickets that were encoded via the previous key (the active one is always the one at the end of the file, and we start by removing the one at the beginning of the file).
Content of the STEK file
------------------------
When dealing with a large traffic load, it might happen that the internal pipe used to pass queries between the threads handling the incoming connections and the one getting a response from the backend become full too quickly, degrading performance and causing timeouts. This can be prevented by increasing the size of the internal pipe buffer, via the `internalPipeBufferSize` option of :func:`addDOHLocal`. Setting a value of `1048576` is known to yield good results on Linux.
+`AF_XDP` / `XSK`
+----------------
+
+On recent versions of Linux (`>= 4.18`), DNSDist supports receiving UDP datagrams directly from the kernel, bypassing the usual network stack, via `AF_XDP`/`XSK`. This yields much better performance but comes with some limitations. Please see :doc:`xsk` for more information.
+
+UDP buffer sizes
+----------------
+
+The operating system usually maintains buffers of incoming and outgoing datagrams for UDP sockets, to deal with short spikes where packets are received or emitted faster than the network layer can process them. On medium to large setups, it is usually useful to increase these buffers to deal with large spikes. This can be done via the :func:`setUDPSocketBufferSizes`.
+
Outgoing DoH
------------
* supported ciphers depend on the exact kernel version used. ``TLS_AES_128_GCM_SHA256`` might be a good option for testing purpose since it was supported pretty early
* as of OpenSSL 3.0.7, kTLS can only be used for sending TLS 1.3 packets, not receiving them. Both sending and receiving packets should be working for TLS 1.2.
+TLS performance
+---------------
+
+For DNS over HTTPS and DNS over TLS, in addition to the advice above we suggest reading the :doc:`tls-sessions-management` page to learn how to improve TLS session resumption ratio, which has a huge impact on CPU usage and latency.
Rules and Lua
-------------
+---------------------------------+-----------------------------+
| DoH (w/ releaseBuffers) | 15 kB |
+---------------------------------+-----------------------------+
+
+Firewall connection tracking
+----------------------------
+
+When dealing with a lot of queries per second, dnsdist puts a severe stress on any stateful (connection tracking) firewall, so much so that the firewall may fail.
+
+Specifically, many Linux distributions run with a connection tracking firewall configured. For high load operation (thousands of queries/second), it is advised to either turn off ``iptables`` and ``nftables`` completely, or use the ``NOTRACK`` feature to make sure client DNS traffic bypasses the connection tracking.
+
+Network interface receive queues
+--------------------------------
+
+Most high-speed (>= 10 Gbps) network interfaces support multiple queues to offer better performance, using hashing to dispatch incoming packets into a specific queue.
+
+Unfortunately the default hashing algorithm is very often considering the source and destination addresses only, which might be an issue when dnsdist is placed behind a frontend, for example.
+
+On Linux it is possible to inspect the current network flow hashing policy via ``ethtool``::
+
+ $ sudo ethtool -n enp1s0 rx-flow-hash udp4
+ UDP over IPV4 flows use these fields for computing Hash flow key:
+ IP SA
+ IP DA
+
+In this example only the source (``IP SA``) and destination (``IP DA``) addresses are indeed used, meaning that all packets coming from the same source address to the same destination address will end up in the same receive queue, which is not optimal. To take the source and destination ports into account as well::
+
+ $ sudo ethtool -N enp1s0 rx-flow-hash udp4 sdfn
+ $
--- /dev/null
+``AF_XDP`` / ``XSK``
+====================
+
+Since 1.9.0, :program:`dnsdist` can use `AF_XDP <https://www.kernel.org/doc/html/v4.18/networking/af_xdp.html>`_ for high performance UDP packet processing recent Linux kernels (4.18+). It requires :program:`dnsdist` to have the ``CAP_NET_ADMIN`` and ``CAP_SYS_ADMIN`` capabilities at startup, and to have been compiled with the ``--with-xsk`` configure option.
+
+.. note::
+ To retain the required capabilities it is necessary to call :func:`addCapabilitiesToRetain` during startup, as :program:`dnsdist` drops capabilities after startup.
+
+.. note::
+ ``AppArmor`` users might need to update their policy to allow :program:`dnsdist` to keep the capabilities. Adding ``capability sys_admin,`` (for ``CAP_SYS_ADMIN``) and ``capability net_admin,`` (for ``CAP_NET_ADMIN``) lines to the policy file is usually enough.
+
+.. warning::
+ DNSdist's ``AF_XDP`` implementation comes with several limitations:
+
+ - Asymmetrical network setups where the DNS query and its response do not go through the same network device are not supported
+ - Ethernet packets larger than 2048 bytes are not supported
+ - IP and UDP-level checksums are not verified on incoming DNS messages
+ - IP options in incoming packets are not supported
+
+The way ``AF_XDP`` works is that :program:`dnsdist` allocates a number of frames in a memory area called a ``UMEM``, which is accessible both by the program, in userspace, and by the kernel. Using in-memory ring buffers, the receive (``RX``), transmit (``TX``), completion (``cq``) and fill (``fq``) rings, the kernel can very efficiently pass raw incoming packets to :program:`dnsdist`, which can in return pass raw outgoing packets to the kernel.
+In addition to these, an ``eBPF`` ``XDP`` program needs to be loaded to decide which packets to distribute via the ``AF_XDP`` socket (and to which, as there are usually more than one). This program uses a ``BPF`` map of type ``XSKMAP`` (located at ``/sys/fs/bpf/dnsdist/xskmap`` by default) that is populated by :program:`dnsdist` at startup to locate the ``AF_XDP`` socket to use. :program:`dnsdist` also sets up two additional ``BPF`` maps (located at ``/sys/fs/bpf/dnsdist/xsk-destinations-v4`` and ``/sys/fs/bpf/dnsdist/xsk-destinations-v6``) to let the ``XDP`` program know which IP destinations are to be routed to the ``AF_XDP`` sockets and which are to be passed to the regular network stack (health-checks queries and responses, for example). A ready-to-use `XDP program <https://github.com/PowerDNS/pdns/blob/master/contrib/xdp.py>`_ can be found in the ``contrib`` directory of the PowerDNS Git repository::
+
+ $ python xdp.py --xsk --interface eth0
+
+Then :program:`dnsdist` needs to be configured to use ``AF_XDP``, first by creating a :class:`XskSocket` object that are tied to a specific queue of a specific network interface:
+
+.. code-block:: lua
+
+ xsk = newXsk({ifName="enp1s0", NIC_queue_id=0, frameNums=65536, xskMapPath="/sys/fs/bpf/dnsdist/xskmap"})
+
+This ties the new object to the first receive queue on ``enp1s0``, allocating 65536 frames and populating the map located at ``/sys/fs/bpf/dnsdist/xskmap``.
+
+Then we can tell :program:`dnsdist` to listen for ``AF_XDP`` packets to ``192.0.2.1:53``, in addition to packets coming via the regular network stack:
+
+.. code-block:: lua
+
+ addLocal("192.0.2.1:53", {xskSocket=xsk})
+
+In practice most high-speed (>= 10 Gbps) network interfaces support multiple queues to offer better performance, so we need to allocate one :class:`XskSocket` per queue. We can retrieve the number of queues for a given interface via::
+
+ $ sudo ethtool -l enp1s0
+ Channel parameters for enp1s0:
+ Pre-set maximums:
+ RX: n/a
+ TX: n/a
+ Other: 1
+ Combined: 8
+ Current hardware settings:
+ RX: n/a
+ TX: n/a
+ Other: 1
+ Combined: 8
+
+The ``Combined`` lines tell us that the interface supports 8 queues, so we can do something like this:
+
+.. code-block:: lua
+
+ for i=1,8 do
+ xsk = newXsk({ifName="enp1s0", NIC_queue_id=i-1, frameNums=65536, xskMapPath="/sys/fs/bpf/dnsdist/xskmap"})
+ addLocal("192.0.2.1:53", {xskSocket=xsk, reusePort=true})
+ end
+
+This will start one router thread per :class:`XskSocket` object, plus one worker thread per :func:`addLocal` using that :class:`XskSocket` object.
+
+We can instructs :program:`dnsdist` to use ``AF_XDP`` to send and receive UDP packets to a backend in addition to packets from clients:
+
+.. code-block:: lua
+
+ local sockets = {}
+ for i=1,8 do
+ xsk = newXsk({ifName="enp1s0", NIC_queue_id=i-1, frameNums=65536, xskMapPath="/sys/fs/bpf/dnsdist/xskmap"})
+ table.insert(sockets, xsk)
+ addLocal("192.0.2.1:53", {xskSocket=xsk, reusePort=true})
+ end
+
+ newServer("192.0.2.2:53", {xskSocket=sockets})
+
+This will start one router thread per :class:`XskSocket` object, plus one worker thread per :func:`addLocal`/:func:`newServer` using that :class:`XskSocket` object.
+
+We are not passing the MAC address of the backend (or the gateway to reach it) directly, so :program:`dnsdist` will try to fetch it from the system MAC address cache. This may not work, in which case we might need to pass explicitly:
+
+.. code-block:: lua
+
+ newServer("192.0.2.2:53", {xskSocket=sockets, MACAddr='00:11:22:33:44:55'})
+
+
+Performance
+-----------
+
+Using `kxdpgun <https://www.knot-dns.cz/docs/latest/html/man_kxdpgun.html>`_, we can compare the performance of :program:`dnsdist` using the regular network stack and ``AF_XDP``.
+
+This test was realized using two Intel E3-1270 with 4 cores (8 threads) running at 3.8 Ghz, using Intel 82599 10 Gbps network cards. On both the injector running ``kxdpgun`` and the box running :program:`dnsdist` there was no firewall, the governor was set to ``performance``, the UDP buffers were raised to ``16777216`` and the receive queue hash policy set to use the IP addresses and ports (see :doc:`tuning`).
+
+:program:`dnsdist` was configured to immediately respond to incoming queries with ``REFUSED``:
+
+.. code-block:: lua
+
+ addAction(AllRule(), RCodeAction(DNSRCode.REFUSED))
+
+On the injector box we executed::
+
+ $ sudo kxdpgun -Q 2500000 -p 53 -i random_1M 192.0.2.1 -t 60
+ using interface enp1s0, XDP threads 8, UDP, native mode
+ [...]
+
+We first ran without ``AF_XDP``:
+
+.. code-block:: lua
+
+ for i=1,8 do
+ addLocal("192.0.2.1:53", {reusePort=true})
+ end
+
+then with:
+
+.. code-block:: lua
+
+ for i=1,8 do
+ xsk = newXsk({ifName="enp1s0", NIC_queue_id=i-1, frameNums=65536, xskMapPath="/sys/fs/bpf/dnsdist/xskmap"})
+ addLocal("192.0.2.1:53", {xskSocket=xsk, reusePort=true})
+ end
+
+.. figure:: ../imgs/af_xdp_refused_qps.png
+ :align: center
+ :alt: AF_XDP QPS
+
+.. figure:: ../imgs/af_xdp_refused_cpu.png
+ :align: center
+ :alt: AF_XDP CPU
+
+The first run handled roughly 1 million QPS, the second run 2.5 millions, with the CPU usage being much lower in the ``AF_XDP`` case.
Changelog
=========
+.. changelog::
+ :version: 1.9.1
+ :released: 14th of March 2024
+
+ This release does not contain any dnsdist code changes compared to 1.9.0.
+ The only thing that changed is the version of Quiche, because of a `security update <https://github.com/cloudflare/quiche/releases/tag/0.20.1>`_.
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13912
+
+ update Quiche to 0.20.1. Fixes `CVE-2024-1410 <https://www.cve.org/CVERecord?id=CVE-2024-1410>`_ and `CVE-2024-1765 <https://www.cve.org/CVERecord?id=CVE-2024-1765>`_.
+
+.. changelog::
+ :version: 1.9.0
+ :released: 16th of February 2024
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+ .. change::
+ :tags: Improvements, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13755
+
+ Better handling of short, non-initial QUIC headers
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13757
+
+ Fix a warning reported by Coverity
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13768
+
+ Add a Lua maintenance hook
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13771
+ :tickets: 13766
+
+ Do not allocate 16-byte aligned objects through lua(jit)
+
+ .. change::
+ :tags: Bug Fixes, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13774
+
+ Fix a missing explicit atomic load of the Quiche configuration
+
+ .. change::
+ :tags: Improvements, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13779
+
+ Fix performance inefficiencies reported by Coverity
+
+.. changelog::
+ :version: 1.9.0-rc1
+ :released: 30th of January 2024
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTP3
+ :pullreq: 13647
+
+ Set the DNS over HTTP/3 default port to 443
+
+ .. change::
+ :tags: Bug Fixes, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13638
+ :tickets: 13631
+
+ Handle congested DoQ streams
+
+ .. change::
+ :tags: Bug Fixes, Metrics
+ :pullreq: 13630
+
+ Fix the 'TCP Died Reading Query" metric, as reported by Coverity
+
+ .. change::
+ :tags: Improvements, Performance, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13666
+
+ Optimize the DoQ packet handling path
+
+ .. change::
+ :tags: Improvements, Performance
+ :pullreq: 13664
+
+ Increase UDP receive and send buffers to the maximum allowed
+
+ .. change::
+ :tags: Bug Fixes, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13670
+
+ Make sure we enforce the ACL over DoQ and DoH3
+
+ .. change::
+ :tags: Improvements, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13674
+
+ Enable DoQ and DoH3 in dockerfile-dnsdist (Denis Machard)
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTP3
+ :pullreq: 13678
+
+ Grant unidirectional HTTP/3 streams for DoH3
+
+ .. change::
+ :tags: Improvements, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13676
+
+ Enable PMTU discovery and disable fragmentation on QUIC binds
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13667
+
+ Clean up the Lua objects before exiting
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTP3
+ :pullreq: 13689
+ :tickets: 13687
+
+ Buffer HTTP/3 headers until the query has been dispatched
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTP3
+ :pullreq: 13713
+ :tickets: 13690
+
+ Add content-type header information in DoH3 responses
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13711
+
+ Cleanup of code doing SNMP OID handling
+
+ .. change::
+ :tags: Bug Fixes, Protobuf, DNSTAP
+ :pullreq: 13716
+
+ Properly set the incoming protocol when logging via Protobuf or dnstap
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13727
+
+ Fix missed optimizations reported by Coverity
+
+ .. change::
+ :tags: Improvements, DNS over QUIC, DNS over HTTP3
+ :pullreq: 13650
+
+ Fall back to libcrypto for authenticated encryption
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13735
+
+ Move the console socket instead of copying it
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13723
+
+ DNSName: Correct len and offset types
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13724
+
+ DNSName: Optimize parsing of uncompressed labels
+
+ .. change::
+ :tags: New Features
+ :pullreq: 11652
+
+ Add AF_XDP support for UDP (Y7n05h)
+
+.. changelog::
+ :version: 1.8.3
+ :released: 15th of December 2023
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
+ .. change::
+ :tags: Bug Fixes, Metrics
+ :pullreq: 13523
+ :tickets: 13519
+
+ Refactor the exponential back-off timer code
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13598
+
+ Detect and dismiss truncated UDP responses from a backend
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13599
+
+ Fix the removal of the last rule by name or UUID
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13601
+
+ Add a `DynBlockRulesGroup:removeRange()` binding
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13602
+ :tickets: 13307
+
+ Fix several cosmetic issues in eBPF dynamic blocks, update documentation
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13605
+
+ Add a `DNSHeader:getTC()` Lua binding
+
+ .. change::
+ :tags: Bug Fixes, Webserver
+ :pullreq: 13607
+ :tickets: 13050
+
+ Fix code producing JSON
+
+.. changelog::
+ :version: 1.9.0-alpha4
+ :released: 14th of December 2023
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13023
+
+ Remove legacy terms from the codebase (Kees Monshouwer)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13191
+
+ Wrap `DIR*` objects in unique pointers to prevent memory leaks
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13342
+
+ Add a DynBlockRulesGroup:removeRange() binding
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTPS
+ :pullreq: 13381
+
+ Fix the case where nghttp2 is available but DoH is disabled
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13435
+
+ Fix a few Coverity warnings
+
+ .. change::
+ :tags: Improvements, DNS over QUIC
+ :pullreq: 13437
+
+ Require Quiche >= 0.15.0
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13445
+
+ Fix Coverity CID 1523748: Performance inefficiencies in dolog.hh
+
+ .. change::
+ :tags: Improvements, DNS over QUIC
+ :pullreq: 13472
+
+ Add missing DoQ latency metrics
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13473
+
+ Add support for setting Extended DNS Error statuses
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13485
+ :tickets: 13191
+
+ Add `pdns::visit_directory()`, wrapping opendir/readdir/closedir
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13488
+
+ Fix the removal of the last rule by name or UUID
+
+ .. change::
+ :tags: New Features, Webserver
+ :pullreq: 13489
+
+ Add a 'rings' endpoint to the REST API
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13492
+
+ Add a cache-miss ratio dynamic block rule
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13500
+
+ Improve `NetmaskGroupRule`/`SuffixMatchNodeRule`, deprecate `makeRule`
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13503
+
+ Add `NetmaskGroup:addNMG()` to merge Netmask groups
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13505
+
+ Add `getAddressInfo()` for asynchronous DNS resolution
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13506
+
+ Add an option to set the SSL proxy protocol TLV
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13509
+
+ Add Proxy Protocol v2 support to `TeeAction`
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13515
+
+ Allow setting the action from `setSuffixMatchRule{,FFI}()`'s visitor
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13517
+
+ Allow enabling incoming PROXY protocol on a per-bind basis
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13520
+
+ Refactor the exponential back-off timer code
+
+ .. change::
+ :tags: Bug Fixes, DNS over QUIC
+ :pullreq: 13524
+
+ Fix building with DoQ but without DoH or DoT
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13536
+
+ Detect and dismiss truncated UDP responses from a backend
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13537
+
+ Make the max size of entries in the packet cache configurable
+
+ .. change::
+ :tags: New Features, DNS over HTTP3, DNS over HTTPS
+ :pullreq: 13556
+
+ Add support for incoming DNS over HTTP/3
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13560
+
+ Spoof a raw response for ANY queries
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13564
+
+ Add `PayloadSizeRule` and `TCResponseAction`
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13565
+
+ Add Lua FFI bindings: hashing arbitrary data and knowing if the query was received over IPv6
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13592
+
+ Add `QNameSuffixRule`
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS
+ :pullreq: 13594
+
+ Send a HTTP 400 response to HTTP/1.1 clients
+
+.. changelog::
+ :version: 1.9.0-alpha3
+ :released: 20th of October 2023
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+ .. change::
+ :tags: New Features, Protobuf
+ :pullreq: 13185
+
+ Log Extended DNS Errors (EDE) to protobuf
+
+ .. change::
+ :tags: Bugs Fixes
+ :pullreq: 13274
+
+ Enable back h2o support in our packages
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13275
+ :tickets: 13201
+
+ Add Lua binding to downstream address (Denis Machard)
+
+ .. change::
+ :tags: New Features, DNS over QUIC
+ :pullreq: 13280
+
+ Add support for incoming DNS over QUIC
+
+ .. change::
+ :tags: Bugs Fixes, DNS over HTTPS
+ :pullreq: 13298
+
+ Fix timeouts on incoming DoH connections with nghttp2
+
+ .. change::
+ :tags: Bug Fixes, Metrics
+ :pullreq: 13302
+
+ Fix a typo in 'Client timeouts' (phonedph1)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13305
+
+ Set proper levels when logging messages
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13310
+
+ Fix several cosmetic issues in eBPF dynamic blocks, update documentation
+
+ .. change::
+ :tags: Improvements, Webserver
+ :pullreq: 13335
+
+ Display the rule name, if any, in the web interface
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13340
+
+ Netmask: Normalize subnet masks coming from a string
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13372
+ :tickets: 13280
+
+ Prevent DNS header alignment issues
+
+.. changelog::
+ :version: 1.9.0-alpha2
+ :released: Never
+
+ This version was never released due to a last-minute issue in RPM packaging.
+
+.. changelog::
+ :version: 1.8.2
+ :released: 11th of October 2023
+
+ This release fixes the HTTP2 rapid reset attack for the packages we provide.
+ If you are compiling DNSdist yourself or using the packages provided by your distribution,
+ please check that the h2o library has been patched to mitigate this vulnerability.
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
+ .. change::
+ :tags: Bug Fixes, Security
+ :pullreq: #13349
+
+ Switch to our fork of h2o to mitigate the HTTP2 rapid reset attack
+
+.. changelog::
+ :version: 1.7.5
+ :released: 11th of October 2023
+
+ This release fixes the HTTP2 rapid reset attack for the packages we provide.
+ If you are compiling DNSdist yourself or using the packages provided by your distribution,
+ please check that the h2o library has been patched to mitigate this vulnerability.
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
+ .. change::
+ :tags: Bug Fixes, Security
+ :pullreq: #13351
+
+ Switch to our fork of h2o to mitigate the HTTP2 rapid reset attack
+
+.. changelog::
+ :version: 1.9.0-alpha1
+ :released: 18th of September 2023
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+ .. change::
+ :tags: Improvements, DNS over HTTPS
+ :pullreq: 12678
+
+ Add support for incoming DoH via nghttp2
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13145
+
+ Fix building our fuzzing targets from a dist tarball
+
+ .. change::
+ :tags: Removals
+ :pullreq: 13168
+
+ Change the default for building with net-snmp from `auto` to `no`
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13135
+
+ Add a DNSHeader:getTC() Lua binding
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13013
+ :tickets: 13007
+
+ Add Lua bindings to access selector and action
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13088
+
+ Stop passing -u dnsdist -g dnsdist on systemd's ExecStart
+
+ .. change::
+ :tags: Improvements, Metrics
+ :pullreq: 13009
+
+ Add metrics for health-check failures
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12931
+
+ Use arc4random only for random values
+
+ .. change::
+ :tags: New Features
+ :pullreq: 12689
+
+ Add an option to write `grepq`'s output to a file
+
+.. changelog::
+ :version: 1.8.1
+ :released: 8th of September 2023
+
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12820
+
+ Print the received, invalid health-check response ID
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12821
+
+ Account for the health-check run time between two runs
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12822
+
+ Properly set the size of the UDP health-check response
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12823
+
+ Add the query ID to health-check log messages, fix nits
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12824
+
+ Stop setting SO_REUSEADDR on outgoing UDP client sockets
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTPS
+ :pullreq: 12977
+
+ Fix a crash when X-Forwarded-For overrides the initial source IP
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13116
+
+ Properly handle short reads on backend upgrade discovery
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13117
+
+ Undo an accidentally change of disableZeroScope to disableZeroScoping (Winfried Angele)
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13118
+ :tickets: 13027
+
+ Fix the group of the dnsdist.conf file when installed via RPM
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13119
+ :tickets: 12926
+
+ Work around Red Hat 8 messing up OpenSSL's headers and refusing to fix it
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13120
+
+ Fix a typo for libedit in the dnsdist features list
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13121
+
+ Stop using the now deprecated ERR_load_CRYPTO_strings() to detect OpenSSL
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13122
+
+ Automatically load Lua FFI inspection functions
+
+ .. change::
+ :tags: New Features
+ :pullreq: 13123
+
+ Allow declaring custom metrics at runtime
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13124
+
+ Fix webserver config template for our docker container (Houtworm)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13125
+
+ Increment the "dyn blocked" counter for eBPF blocks as well
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13127
+
+ YaHTTP: Prevent integer overflow on very large chunks
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13128
+
+ Fix the console description of PoolAction and QPSPoolAction (phonedph1)
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13129
+ :tickets: 12711
+
+ Properly handle reconnection failure for backend UDP sockets
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTPS, DNS over TLS
+ :pullreq: 13130
+
+ Fix a memory leak when processing TLS tickets w/ OpenSSL 3.x
+
+ .. change::
+ :tags: Bug Fixes, DNS over HTTPS
+ :pullreq: 13131
+ :tickets: 12762
+
+ Fix cache hit and miss metrics with DoH queries
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13132
+
+ SpoofAction: copy the QClass from the request (Christof Chen)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13133
+
+ Make DNSQType.TSIG available (Jacob Bunk)
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13150
+
+ Properly record self-answered UDP responses with recvmmsg
+
+ .. change::
+ :tags: Bug Fixes, DNS over TLS
+ :pullreq: 13178
+
+ Fix a race when creating the first TLS connections
+
.. changelog::
:version: 1.7.4
:released: 14th of April 2023
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 12183
:version: 1.8.0
:released: 30th of March 2023
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 12687
:version: 1.8.0-rc3
:released: 16th of March 2023
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 12641
:version: 1.8.0-rc2
:released: 9th of March 2023
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
.. change::
:tags: Improvements, Protobuf
:pullreq: 12615
:version: 1.8.0-rc1
:released: 23rd of February 2023
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 12569
:tags: New Features
:pullreq: 11051
- Add support to spoof a full self-generated response from lua
+ Add support to spoof a full self-generated response from lua
.. change::
:tags: New Features
:version: 1.7.3
:released: 2nd of November 2022
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
dnsdist 1.7.3 contains no functional changes or bugfixes.
This release strictly serves to bring dnsdist packages to our EL9 and Ubuntu Jammy repositories, and upgrades the dnsdist Docker image from Debian buster to Debian bullseye, as buster is officially EOL.
:version: 1.7.2
:released: 14th of June 2022
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
.. change::
:tags: Improvements
:pullreq: 11579
:version: 1.7.1
:released: 25th of April 2022
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
.. change::
:tags: Improvements
:pullreq: 11195
:version: 1.7.0
:released: 17th of January 2022
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 11156
:version: 1.7.0-rc1
:released: 22nd of December 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
.. change::
:tags: Improvements, DNS over TLS, Performance
:pullreq: 11037
:version: 1.7.0-beta1
:released: 16th of November 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
.. change::
:tags: Improvements
:pullreq: 10646
:version: 1.7.0-alpha2
:released: 19th of October 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
.. change::
:tags: Improvements
:pullreq: 10760
:version: 1.7.0-alpha1
:released: 23rd of September 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
.. change::
:tags: Improvements
:pullreq: 10157
:version: 1.6.1
:released: 15th of September 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 10438
:version: 1.6.0
:released: 11th of May 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
.. changelog::
:version: 1.5.2
:released: 10th of May 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 9583
:version: 1.6.0-rc2
:released: 4th of May 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
.. change::
:tags: Improvements, Metrics
:pullreq: 10323
:version: 1.6.0-rc1
:released: 20th of April 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 10171
:version: 1.6.0-alpha3
:released: 29th of March 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
.. change::
:tags: Improvements
:pullreq: 10156
:version: 1.6.0-alpha2
:released: 4th of March 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
.. change::
:tags: Improvements
:pullreq: 9361
:version: 1.6.0-alpha1
:released: 2nd of February 2021
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
.. change::
:tags: Improvements
:pullreq: 9273
:tags: Bug Fixes
:pullreq: 9925
- Appease clang++ 12 ASAN on MacOS
+ Appease clang++ 12 ASAN on macOS
.. change::
:tags: Improvements
:version: 1.5.1
:released: 1st of October 2020
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
.. change::
:tags: Improvements
:pullreq: 9540
:version: 1.5.0
:released: 30th of July 2020
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
.. change::
:tags: Improvements
:pullreq: 9231
:version: 1.5.0-rc4
:released: 7th of July 2020
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 9278
:version: 1.5.0-rc3
:released: 18th of June 2020
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
.. change::
:tags: Improvements
:pullreq: 9100
:version: 1.5.0-rc2
:released: 13th of May 2020
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 9031
:version: 1.5.0-rc1
:released: 16th of April 2020
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 8955
:version: 1.5.0-alpha1
:released: 20th of March 2020
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
.. change::
:tags: Improvements
:pullreq: 7820
:version: 1.4.0
:released: 20th of November 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 8524
:version: 1.4.0-rc5
:released: 30th of October 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
.. change::
:tags: Improvements, DNS over HTTPS, Metrics
:pullreq: 8465
:version: 1.4.0-rc4
:released: 25th of October 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
.. change::
:tags: New Features, DNS over HTTPS, DNS over TLS
:pullreq: 8442
:version: 1.4.0-rc3
:released: 30th of September 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
+
.. change::
:tags: Improvements
:pullreq: 8083
:version: 1.4.0-rc2
:released: 2nd of September 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
+
.. change::
:tags: New Features
:pullreq: 8139
:version: 1.4.0-rc1
:released: 12th of August 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
.. change::
:tags: Improvements
:pullreq: 7860
:version: 1.4.0-beta1
:released: 6th of June 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
.. change::
:tags: Bug Fixes, DoH
:pullreq: 7814
:version: 1.4.0-alpha2
:released: 26th of April 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
.. change::
:tags: Improvements
:pullreq: 7410
:version: 1.4.0-alpha1
:released: 12th of April 2019
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
.. change::
:tags: New Features
:pullreq: 7209
:version: 1.3.3
:released: 8th of November 2018
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.3.x.
+
.. change::
:tags: New Features
:pullreq: 6737, 6939
:version: 1.3.2
:released: 10th of July 2018
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.3.x.
+
.. change::
:tags: Bug Fixes
:pullreq: 6785
:version: 1.3.1
:released: 10th of July 2018
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.3.x.
+
.. change::
:tags: Improvements
:pullreq: 6358
:version: 1.3.0
:released: 30th of March 2018
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.3.x.
+
.. change::
:tags: Improvements, New Features
:pullreq: 5576, 5860
:version: 1.2.1
:released: 16th of February 2018
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.2.x.
+
.. change::
:tags: New Features
:pullreq: 5880
:version: 1.2.0
:released: 21st of August 2017
+ Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.2.x.
+
.. change::
:tags: Improvements
:pullreq: 4852
# General information about the project.
project = 'dnsdist'
-copyright = '2015-' + str(datetime.date.today().year) + ', PowerDNS.COM BV and its contributors'
+copyright = 'PowerDNS.COM BV and its contributors'
author = 'PowerDNS.COM BV and its contributors'
# The version info for the project you're documenting, acts as replacement for
changelog_render_changeset = "https://github.com/PowerDNS/pdns/commit/%s"
changelog_sections = ['New Features', 'Improvements', 'Bug Fixes', 'Removals']
-changelog_inner_tag_sort = ['Security', 'DNS over HTTPS', 'DNS over TLS', 'DNSCrypt', 'DNSTAP', 'Protobuf', 'Performance', 'Webserver', 'Metrics']
+changelog_inner_tag_sort = ['Security', 'DNS over QUIC', 'DNS over HTTP3', 'DNS over HTTPS', 'DNS over TLS', 'DNSCrypt', 'DNSTAP', 'Protobuf', 'Performance', 'Webserver', 'Metrics']
changelog_hide_tags_in_entry = True
End of life statements
======================
+We aim to have a major release every six months. The latest major release train receives correctness, stability and security updates by the way of minor releases. We support older releases with critical updates for one year after the following major release.
+
+Older releases are marked end of life and receive no updates at all. Pre-releases do not receive immediate security updates.
+
+The currently supported release train of PowerDNS DNSdist is 1.9.
+
+PowerDNS DNSdist 1.8 will only receive critical updates and will be End of Life one year after PowerDNS DNSdist 1.9 was released.
+
+PowerDNS DNSdist 1.7 will only receive critical updates and will be End of Life one year after PowerDNS DNSdist 1.8 was released.
+
+Older versions are End of Life.
+
+.. note::
+ Users with a commercial agreement with PowerDNS.COM BV or Open-Xchange can receive extended support for releases which are End Of Life. If you are such a user, these EOL statements do not apply to you. Please refer to the support commitment for details. Note that for the Open Source support channels we only support the latest minor release of a release train. That means that we ask you to reproduce potential issues on the latest minor release first.
+
.. list-table:: PowerDNS dnsdist Release Life Cycle
:header-rows: 1
- Release date
- Security-Only updates
- End of Life
- * - 1.8
- - March 30 2023
+ * - 1.9
+ - February 16 2024
-
-
+ * - 1.8
+ - March 30 2023
+ - February 16 2024
+ - February 16 2025
* - 1.7
- January 17 2022
- March 30 2023
- -
+ - March 30 2024
* - 1.6
- May 11 2021
- March 30 2023
- -
+ - EOL (February 16 2024)
* - 1.5
- July 30 2020
- January 17 2022
controlSocket('192.0.2.53:5199')
-Enabling the console without encryption enabled is not recommended. Note that encryption requires building dnsdist with libsodium support enabled.
+Enabling the console without encryption enabled is not recommended. Note that encryption requires building dnsdist with either libsodium or libcrypto support enabled.
-Once you have a libsodium-enabled dnsdist, the first step to enable encryption is to generate a key with :func:`makeKey`::
+Once you have a console-enabled dnsdist, the first step to enable encryption is to generate a key with :func:`makeKey`::
$ ./dnsdist -l 127.0.0.1:5300
[..]
--- /dev/null
+DNS-over-HTTP/3 (DoH3)
+======================
+
+.. note::
+ This guide is about DNS over HTTP/3. For DNS over HTTP/1 and DNS over HTTP/2, please see :doc:`dns-over-https`
+
+:program:`dnsdist` supports DNS-over-HTTP/3 (DoH3) for incoming queries since 1.9.0.
+To see if the installation supports this, run ``dnsdist --version``.
+If the output shows ``dns-over-http3`` incoming DNS-over-HTTP/3 is supported.
+
+Incoming
+--------
+
+Adding a listen port for DNS-over-HTTP/3 can be done with the :func:`addDOH3Local` function, e.g.::
+
+ addDOH3Local('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key')
+
+This will make :program:`dnsdist` listen on [2001:db8:1:f00::1]:443 on UDP, and will use the provided certificate and key to serve incoming DoH3 connections.
+
+The fourth parameter, if present, indicates various options. For instance, you can change the congestion control algorithm used. An example is::
+
+ addDOH3Local('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', {congestionControlAlgo="bbr"})
+
+A particular attention should be taken to the permissions of the certificate and key files. Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root, which is no longer true for dnsdist as of 1.5.0. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal.
+
+More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`.
+
+Advertising DNS over HTTP/3 support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If DNS over HTTP/2 is also enabled in the configuration via :func:`addDOHLocal` (see :doc:`dns-over-https` for more information), it might be useful to advertise DNS over HTTP/3 support via the ``Alt-Svc`` header::
+
+ addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/dns", {customResponseHeaders={["alt-svc"]="h3=\":443\""}})
+
+This will advertise that HTTP/3 is available on the same IP, port UDP/443.
DNS-over-HTTPS (DoH)
====================
+.. note::
+ This guide is about DNS over HTTP/1 and DNS over HTTP/2. For DNS over HTTP/3, please see :doc:`dns-over-http3`
+
:program:`dnsdist` supports DNS-over-HTTPS (DoH, standardized in RFC 8484) for incoming queries since 1.4.0, and for outgoing queries since 1.7.0.
To see if the installation supports this, run ``dnsdist --version``.
-If the output shows ``dns-over-https(DOH)``, incoming DNS-over-HTTPS is supported. If ``outgoing-dns-over-https(nghttp2)`` shows up then outgoing DNS-over-HTTPS is supported.
+If the output shows ``dns-over-https(DOH)`` (``dns-over-https(h2o nghttp2)``, ``dns-over-https(h2o)`` or ``dns-over-https(nghttp2)`` since 1.9.0) , incoming DNS-over-HTTPS is supported. If ``outgoing-dns-over-https(nghttp2)`` shows up then outgoing DNS-over-HTTPS is supported.
Incoming
--------
The fifth parameter, if present, indicates various options. For instance, you use it to indicate custom HTTP headers. An example is::
- addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/dns", {customResponseHeaders={["x-foo"]="bar"}}
+ addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/dns", {customResponseHeaders={["x-foo"]="bar"}})
A more complicated (and more realistic) example is when you want to indicate metainformation about the server, such as the stated policy (privacy statement and so on). We use the link types of RFC 8631::
More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`.
+Advertising DNS over HTTP/3 support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If DNS over HTTP/3 is also enabled in the configuration via :func:`addDOH3Local` (see :doc:`dns-over-http3` for more information), it might be useful to advertise this support via the ``Alt-Svc`` header::
+
+ addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/dns", {customResponseHeaders={["alt-svc"]="h3=\":443\""}})
+
+This will advertise that HTTP/3 is available on the same IP, port UDP/443.
+
Custom responses
^^^^^^^^^^^^^^^^
addDOHLocal("127.0.0.1:8053")
addDOHLocal("127.0.0.1:8053", nil, nil, "/", { reusePort=true })
+HTTP/1 support
+^^^^^^^^^^^^^^
+
+dnsdist initially relied on the ``h2o`` library to support incoming DNS over HTTPS. Since 1.9.0, ``h2o`` has been deprecated and ``nghttp2`` is the
+preferred library for incoming DoH support, because ``h2o`` has unfortunately really never been maintained in a way that is suitable for use as a library
+(see https://github.com/h2o/h2o/issues/3230). While we took great care to make the migration as painless as possible, ``h2o`` supported HTTP/1 while ``nghttp2``
+does not. This is not an issue for actual DNS over HTTPS clients that support HTTP/2, but might be one in setups running dnsdist behind a reverse-proxy that
+does not support HTTP/2, like nginx. We do not plan on implementing HTTP/1, and recommend using HTTP/2 between the reverse-proxy and dnsdist for performance reasons.
+For nginx in particular, a possible work-around is to use the `grpc_pass <http://nginx.org/r/grpc_pass>`_ directive as suggested in their `bug tracker <https://trac.nginx.org/nginx/ticket/1875>`_.
+
Internal design
^^^^^^^^^^^^^^^
Support for securing the exchanges between dnsdist and the backend will be implemented in 1.7.0, and will lead to all queries, regardless of whether they were initially received by dnsdist over UDP, TCP, DoT or DoH, being forwarded over a secure DNS over HTTPS channel.
That support can be enabled via the ``dohPath`` parameter of the :func:`newServer` command. Additional parameters control the TLS provider used (``tls``), the validation of the certificate presented by the backend (``caStore``, ``validateCertificates``), the actual TLS ciphers used (``ciphers``, ``ciphersTLS13``) and the SNI value sent (``subjectName``).
+.. code-block:: lua
+
newServer({address="[2001:DB8::1]:443", tls="openssl", subjectName="doh.powerdns.com", dohPath="/dns-query", validateCertificates=true})
--- /dev/null
+DNS-over-QUIC (DoQ)
+====================
+
+:program:`dnsdist` supports DNS-over-QUIC (DoQ, standardized in RFC 9250) for incoming queries since 1.9.0.
+To see if the installation supports this, run ``dnsdist --version``.
+If the output shows ``dns-over-quic`` incoming DNS-over-QUIC is supported.
+
+Incoming
+--------
+
+Adding a listen port for DNS-over-QUIC can be done with the :func:`addDOQLocal` function, e.g.::
+
+ addDOQLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key')
+
+This will make :program:`dnsdist` listen on [2001:db8:1:f00::1]:853 on UDP, and will use the provided certificate and key to serve incoming DoQ connections.
+
+The fourth parameter, if present, indicates various options. For instance, you can change the congestion control algorithm used. An example is::
+
+ addDOQLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', {congestionControlAlgo="bbr"})
+
+A particular attention should be taken to the permissions of the certificate and key files. Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root, which is no longer true for dnsdist as of 1.5.0. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal.
+
+More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`.
Support for securing the exchanges between dnsdist and the backend will be implemented in 1.7.0, and will lead to all queries, regardless of whether they were initially received by dnsdist over UDP, TCP, DoT or DoH, being forwarded over a secure DNS over TLS channel.
That support can be enabled via the ``tls`` parameter of the :func:`newServer` command. Additional parameters control the validation of the certificate presented by the backend (``caStore``, ``validateCertificates``), the actual TLS ciphers used (``ciphers``, ``ciphersTLS13``) and the SNI value sent (``subjectName``).
+.. code-block:: lua
newServer({address="[2001:DB8::1]:853", tls="openssl", subjectName="dot.powerdns.com", validateCertificates=true})
It will get called every second, and from this function you can set rules to block traffic based on statistics.
More exactly, the thread handling the :func:`maintenance` function will sleep for one second between each invocation, so if the function takes several seconds to complete it will not be invoked exactly every second.
-As an example::
+As an example:
+
+.. code-block:: lua
+
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(20, 10, "Exceeded query rate", 60)
function maintenance()
- addDynBlocks(exceedQRate(20, 10), "Exceeded query rate", 60)
+ dbr:apply()
end
This will dynamically block all hosts that exceeded 20 queries/s as measured over the past 10 seconds, and the dynamic block will last for 60 seconds.
+:ref:`DynBlockRulesGroup` is a very efficient way of processing dynamic blocks that was introduced in 1.3.0. Before that, it was possible to use :meth:`addDynBlocks` instead:
+
+.. code-block:: lua
+
+ -- this is a legacy method, please see above for DNSdist >= 1.3.0
+ function maintenance()
+ addDynBlocks(exceedQRate(20, 10), "Exceeded query rate", 60)
+ end
+
Dynamic blocks in force are displayed with :func:`showDynBlocks` and can be cleared with :func:`clearDynBlocks`.
They return a table whose key is a :class:`ComboAddress` object, representing the client's source address, and whose value is an integer representing the number of queries matching the corresponding condition (for example the qtype for :func:`exceedQTypeRate`, rcode for :func:`exceedServFails`).
DynBlockRulesGroup
------------------
-Starting with dnsdist 1.3.0, a new :ref:`dynBlockRulesGroup` function can be used to return a `DynBlockRulesGroup` instance,
+Starting with dnsdist 1.3.0, a new :func:`dynBlockRulesGroup` function can be used to return a :class:`DynBlockRulesGroup` instance,
designed to make the processing of multiple rate-limiting rules faster by walking the query and response buffers only once
for each invocation, instead of once per existing `exceed*()` invocation.
-For example, instead of having something like:
-
-.. code-block:: lua
-
- function maintenance()
- addDynBlocks(exceedQRate(30, 10), "Exceeded query rate", 60)
- addDynBlocks(exceedNXDOMAINs(20, 10), "Exceeded NXD rate", 60)
- addDynBlocks(exceedServFails(20, 10), "Exceeded ServFail rate", 60)
- addDynBlocks(exceedQTypeRate(DNSQType.ANY, 5, 10), "Exceeded ANY rate", 60)
- addDynBlocks(exceedRespByterate(1000000, 10), "Exceeded resp BW rate", 60)
- end
-
The new syntax would be:
.. code-block:: lua
dbr:apply()
end
+Before 1.3.0 the legacy syntax was:
+
+.. code-block:: lua
+
+ function maintenance()
+ -- this example is using legacy methods, please see above for DNSdist >= 1.3.0
+ addDynBlocks(exceedQRate(30, 10), "Exceeded query rate", 60)
+ addDynBlocks(exceedNXDOMAINs(20, 10), "Exceeded NXD rate", 60)
+ addDynBlocks(exceedServFails(20, 10), "Exceeded ServFail rate", 60)
+ addDynBlocks(exceedQTypeRate(DNSQType.ANY, 5, 10), "Exceeded ANY rate", 60)
+ addDynBlocks(exceedRespByterate(1000000, 10), "Exceeded resp BW rate", 60)
+ end
+
+
The old syntax would walk the query buffer 2 times and the response one 3 times, while the new syntax does it only once for each.
It also reuse the same internal table to keep track of the source IPs, reducing the CPU usage.
serverselection
carbon
dns-over-https
+ dns-over-http3
+ dns-over-quic
dns-over-tls
dnscrypt
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'
-You can override those headers, or add custom headers by using the last parameter to :func:`webserver`.
+You can override those headers, or add custom headers by using the last parameter to :func:`setWebserverConfig`.
For example, to remove the X-Frame-Options header and add a X-Custom one:
.. code-block:: lua
dnsdist API
-----------
-To access the API, the `apikey` must be set in the :func:`webserver` function.
+To access the API, the `apikey` must be set in the :func:`setWebserverConfig` function.
Use the API, this key will need to be sent to dnsdist in the ``X-API-Key`` request header.
An HTTP 401 response is returned when a wrong or no API key is received.
A 404 response is generated is the requested endpoint does not exist.
# TYPE dnsdist_frontend_tcpdiedsendingresponse counter
# HELP dnsdist_frontend_tcpgaveup Amount of TCP connections terminated after too many attempts to get a connection to the backend
# TYPE dnsdist_frontend_tcpgaveup counter
- # HELP dnsdist_frontend_tcpclientimeouts Amount of TCP connections terminated by a timeout while reading from the client
- # TYPE dnsdist_frontend_tcpclientimeouts counter
+ # HELP dnsdist_frontend_tcpclienttimeouts Amount of TCP connections terminated by a timeout while reading from the client
+ # TYPE dnsdist_frontend_tcpclienttimeouts counter
# HELP dnsdist_frontend_tcpdownstreamtimeouts Amount of TCP connections terminated by a timeout while reading from the backend
# TYPE dnsdist_frontend_tcpdownstreamtimeouts counter
# HELP dnsdist_frontend_tcpcurrentconnections Amount of current incoming TCP connections from clients
dnsdist_frontend_tcpdiedreadingquery{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
dnsdist_frontend_tcpdiedsendingresponse{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
dnsdist_frontend_tcpgaveup{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
- dnsdist_frontend_tcpclientimeouts{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+ dnsdist_frontend_tcpclienttimeouts{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
dnsdist_frontend_tcpdownstreamtimeouts{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
dnsdist_frontend_tcpcurrentconnections{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
dnsdist_frontend_tcpmaxconcurrentconnections{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
dnsdist_frontend_tcpdiedreadingquery{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
dnsdist_frontend_tcpdiedsendingresponse{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
dnsdist_frontend_tcpgaveup{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
- dnsdist_frontend_tcpclientimeouts{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+ dnsdist_frontend_tcpclienttimeouts{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
dnsdist_frontend_tcpdownstreamtimeouts{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
dnsdist_frontend_tcpcurrentconnections{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
dnsdist_frontend_tcpmaxconcurrentconnections{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
dnsdist_frontend_tcpdiedreadingquery{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
dnsdist_frontend_tcpdiedsendingresponse{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
dnsdist_frontend_tcpgaveup{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
- dnsdist_frontend_tcpclientimeouts{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
+ dnsdist_frontend_tcpclienttimeouts{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
dnsdist_frontend_tcpdownstreamtimeouts{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
dnsdist_frontend_tcpcurrentconnections{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
dnsdist_frontend_tcpmaxconcurrentconnections{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
:>json list: A list of metrics related to that pool
:>json list servers: A list of :json:object:`Server` objects present in that pool
+.. http:get:: /api/v1/servers/localhost/rings?maxQueries=NUM&maxResponses=NUM
+
+ .. versionadded:: 1.9.0
+
+ Get the most recent queries and responses from the in-memory ring buffers. Returns up to ``maxQueries``
+ query entries if set, up to ``maxResponses`` responses if set, and the whole content of the ring buffers otherwise.
+
+ :>json list queries: The list of the most recent queries, as :json:object:`RingEntry` objects
+ :>json list responses: The list of the most recent responses, as :json:object:`RingEntry` objects
+
JSON Objects
~~~~~~~~~~~~
:property integer tlsResumptions: The number of times a TLS session has been resumed
:property integer weight: The weight assigned to this server
:property float dropRate: The amount of packets dropped (timing out) per second by this server
+ :property integer healthCheckFailures: Number of health check attempts that failed (total)
+ :property integer healthCheckFailureParsing: Number of health check attempts that failed because the payload could not be parsed
+ :property integer healthCheckFailureTimeout: Number of health check attempts that failed because the response was not received in time
+ :property integer healthCheckFailureNetwork: Number of health check attempts that failed because of a network error
+ :property integer healthCheckFailureMismatch: Number of health check attempts that failed because the ID, qname, qtype or qclass did not match
+ :property integer healthCheckFailureInvalid: Number of health check attempts that failed because the DNS response was not valid
.. json:object:: StatisticItem
:property string name: The name of this statistic. See :doc:`../statistics`
:property string type: "StatisticItem"
:property integer value: The value for this item
+
+.. json:object:: RingEntry
+
+ This represents an entry in the in-memory ring buffers.
+
+ :property float age: How long ago was the query or response received, in seconds
+ :property integer id: The DNS ID
+ :property string name: The requested domain name
+ :property string requestor: The client IP and port
+ :property integer size: The size of the query or response
+ :property integer qtype: The requested DNS type
+ :property string protocol: The DNS protocol the query or response was received over
+ :property boolean rd: The RD flag
+ :property string mac: The MAC address of the device sending the query
+ :property float latency: The time it took for the response to be sent back to the client, in microseconds
+ :property int rcode: The response code
+ :property boolean tc: The TC flag
+ :property boolean aa: The AA flag
+ :property integer answers: The number of records in the answer section of the response
+ :property string backend: The IP and port of the backend that returned the response, or "Cache" if it was a cache-hit
* `Editline (libedit) <http://thrysoee.dk/editline/>`_
* `libfstrm <https://github.com/farsightsec/fstrm>`_ (optional, dnstap support)
* `GnuTLS <https://www.gnutls.org/>`_ (optional, DoT and outgoing DoH support)
-* `libh2o <https://github.com/h2o/h2o>`_ (optional, incoming DoH support)
+* `libbpf <https://github.com/libbpf/libbpf>`_ and `libxdp <https://github.com/xdp-project/xdp-tools>`_ (optional, `XSK`/`AF_XDP` support)
* `libcap <https://sites.google.com/site/fullycapable/>`_ (optional, capabilities support)
+* `libh2o <https://github.com/h2o/h2o>`_ (optional, incoming DoH support, deprecated in 1.9.0 in favor of ``nghttp2``)
* `libsodium <https://download.libsodium.org/doc/>`_ (optional, DNSCrypt and console encryption support)
* `LMDB <http://www.lmdb.tech/doc/>`_ (optional, LMDB support)
* `net-snmp <http://www.net-snmp.org/>`_ (optional, SNMP support)
* `nghttp2 <https://nghttp2.org/>`_ (optional, outgoing DoH support)
* `OpenSSL <https://www.openssl.org/>`_ (optional, DoT and DoH support)
* `protobuf <https://developers.google.com/protocol-buffers/>`_ (optional, not needed as of 1.6.0)
+* `quiche <https://github.com/cloudflare/quiche>`_ (optional, incoming DoQ support)
* `re2 <https://github.com/google/re2>`_ (optional)
-* `TinyCDB <https://www.corpit.ru/mjt/tinycdb.html>` (optional, CDB support)
+* `TinyCDB <https://www.corpit.ru/mjt/tinycdb.html>`_ (optional, CDB support)
Should :program:`dnsdist` be run on a system with systemd, it is highly recommended to have
the systemd header files (``libsystemd-dev`` on Debian and ``systemd-devel`` on CentOS)
that is used on the server (set with **setKey()**). Note that this
will leak the key into your shell's history and into the systems
running process list. Only available when dnsdist is compiled with
- libsodium support.
+ libsodium or libcrypto support.
-e, --execute <command> Connect to dnsdist and execute *command*.
-h, --help Display a helpful message and exit.
-l, --local <address> Bind to *address*, Supply as many addresses (using multiple
--- /dev/null
+Rule Actions
+============
+
+:doc:`selectors` need to be combined with an action for them to actually do something with the matched packets.
+Some actions allow further processing of rules, this is noted in their description. Most of these start with 'Set' with a few exceptions, mostly for logging actions. These exceptions are:
+
+- :func:`ClearRecordTypesResponseAction`
+- :func:`KeyValueStoreLookupAction`
+- :func:`DnstapLogAction`
+- :func:`DnstapLogResponseAction`
+- :func:`LimitTTLResponseAction`
+- :func:`LogAction`
+- :func:`NoneAction`
+- :func:`RemoteLogAction`
+- :func:`RemoteLogResponseAction`
+- :func:`SNMPTrapAction`
+- :func:`SNMPTrapResponseAction`
+- :func:`TeeAction`
+
+The following actions exist.
+
+.. function:: AllowAction()
+
+ Let these packets go through.
+
+.. function:: AllowResponseAction()
+
+ Let these packets go through.
+
+.. function:: ClearRecordTypesResponseAction(types)
+
+ .. versionadded:: 1.8.0
+
+ Removes given type(s) records from the response. Beware you can accidentally turn the answer into a NODATA response
+ without a SOA record in the additional section in which case you may want to use :func:`NegativeAndSOAAction` to generate an answer,
+ see example below.
+ Subsequent rules are processed after this action.
+
+ .. code-block:: Lua
+
+ -- removes any HTTPS record in the response
+ addResponseAction(
+ QNameRule('www.example.com.'),
+ ClearRecordTypesResponseAction(DNSQType.HTTPS)
+ )
+ -- reply directly with NODATA and a SOA record as we know the answer will be empty
+ addAction(
+ AndRule{QNameRule('www.example.com.'), QTypeRule(DNSQType.HTTPS)},
+ NegativeAndSOAAction(false, 'example.com.', 3600, 'ns.example.com.', 'postmaster.example.com.', 1, 1800, 900, 604800, 86400)
+ )
+
+ :param int types: a single type or a list of types to remove
+
+.. function:: ContinueAction(action)
+
+ .. versionadded:: 1.4.0
+
+ Execute the specified action and override its return with None, making it possible to continue the processing.
+ Subsequent rules are processed after this action.
+
+ :param int action: Any other action
+
+.. function:: DelayAction(milliseconds)
+
+ Delay the response by the specified amount of milliseconds (UDP-only). Note that the sending of the query to the backend, if needed,
+ is not delayed. Only the sending of the response to the client will be delayed.
+ Subsequent rules are processed after this action.
+
+ :param int milliseconds: The amount of milliseconds to delay the response
+
+.. function:: DelayResponseAction(milliseconds)
+
+ Delay the response by the specified amount of milliseconds (UDP-only).
+ The only difference between this action and :func:`DelayAction` is that they can only be applied on, respectively, responses and queries.
+ Subsequent rules are processed after this action.
+
+ :param int milliseconds: The amount of milliseconds to delay the response
+
+.. function:: DisableECSAction()
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetDisableECSAction` instead.
+
+ Disable the sending of ECS to the backend.
+ Subsequent rules are processed after this action.
+
+.. function:: DisableValidationAction()
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetDisableValidationAction` instead.
+
+ Set the CD bit in the query and let it go through.
+ Subsequent rules are processed after this action.
+
+.. function:: DnstapLogAction(identity, logger[, alterFunction])
+
+ Send the current query to a remote logger as a :doc:`dnstap <dnstap>` message.
+ ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
+ Subsequent rules are processed after this action.
+
+ :param string identity: Server identity to store in the dnstap message
+ :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
+ :param alterFunction: A Lua function to alter the message before sending
+
+.. function:: DnstapLogResponseAction(identity, logger[, alterFunction])
+
+ Send the current response to a remote logger as a :doc:`dnstap <dnstap>` message.
+ ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
+ Subsequent rules are processed after this action.
+
+ :param string identity: Server identity to store in the dnstap message
+ :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
+ :param alterFunction: A Lua function to alter the message before sending
+
+.. function:: DropAction()
+
+ Drop the packet.
+
+.. function:: DropResponseAction()
+
+ Drop the packet.
+
+.. function:: ECSOverrideAction(override)
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetECSOverrideAction` instead.
+
+ Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
+ Subsequent rules are processed after this action.
+
+ :param bool override: Whether or not to override ECS value
+
+.. function:: ECSPrefixLengthAction(v4, v6)
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetECSPrefixLengthAction` instead.
+
+ Set the ECS prefix length.
+ Subsequent rules are processed after this action.
+
+ :param int v4: The IPv4 netmask length
+ :param int v6: The IPv6 netmask length
+
+.. function:: ERCodeAction(rcode [, options])
+
+ .. versionadded:: 1.4.0
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
+ Reply immediately by turning the query into a response with the specified EDNS extended ``rcode``.
+ ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+
+ :param int rcode: The extended RCODE to respond with.
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: HTTPStatusAction(status, body, contentType="" [, options])
+
+ .. versionadded:: 1.4.0
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
+ Return an HTTP response with a status code of ''status''. For HTTP redirects, ''body'' should be the redirect URL.
+
+ :param int status: The HTTP status code to return.
+ :param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx).
+ :param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ''application/dns-message''.
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)
+
+ .. versionadded:: 1.4.0
+
+ Does a lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey',
+ and storing the result if any into the tag named 'destinationTag'.
+ The store can be a CDB (:func:`newCDBKVStore`) or a LMDB database (:func:`newLMDBKVStore`).
+ The key can be based on the qname (:func:`KeyValueLookupKeyQName` and :func:`KeyValueLookupKeySuffix`),
+ source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
+ Subsequent rules are processed after this action.
+ Note that the tag is always created, even if there was no match, but in that case the content is empty.
+
+ :param KeyValueStore kvs: The key value store to query
+ :param KeyValueLookupKey lookupKey: The key to use for the lookup
+ :param string destinationTag: The name of the tag to store the result into
+
+.. function:: KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag)
+
+ .. versionadded:: 1.7.0
+
+ Does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey',
+ and storing the result if any into the tag named 'destinationTag'.
+ This assumes that there is a key in network byte order for the last element of the range (for example 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff for 2001:db8::/32) which contains the first element of the range (2001:0db8:0000:0000:0000:0000:0000:0000) (optionally followed by any data) as value, also in network byte order, and that there is no overlapping ranges in the database.
+ This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB.
+
+ Subsequent rules are processed after this action.
+
+ :param KeyValueStore kvs: The key value store to query
+ :param KeyValueLookupKey lookupKey: The key to use for the lookup
+ :param string destinationTag: The name of the tag to store the result into
+
+.. function:: LimitTTLResponseAction(min[, max [, types]])
+
+ .. versionadded:: 1.8.0
+
+ Cap the TTLs of the response to the given boundaries.
+
+ :param int min: The minimum allowed value
+ :param int max: The maximum allowed value
+ :param list of int: The record types to cap the TTL for. Default is empty which means all records will be capped.
+
+.. function:: LogAction([filename[, binary[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
+
+ .. versionchanged:: 1.4.0
+ Added the optional parameters ``verboseOnly`` and ``includeTimestamp``, made ``filename`` optional.
+
+ .. versionchanged:: 1.7.0
+ Added the ``reload`` method.
+
+ Log a line for each query, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
+
+ If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
+
+ When logging to a file, the ``binary`` optional parameter specifies whether we log in binary form (default) or in textual form. Before 1.4.0 the binary log format only included the qname and qtype. Since 1.4.0 it includes an optional timestamp, the query ID, qname, qtype, remote address and port.
+
+ The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
+ The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
+
+ Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
+
+ Subsequent rules are processed after this action.
+
+ :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
+ :param bool binary: Do binary logging. Default true
+ :param bool append: Append to the log. Default false
+ :param bool buffered: Use buffered I/O. Default true
+ :param bool verboseOnly: Whether to log only in verbose mode when logging to stdout. Default is true
+ :param bool includeTimestamp: Whether to include a timestamp for every entry. Default is false
+
+.. function:: LogResponseAction([filename[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
+
+ .. versionadded:: 1.5.0
+
+ .. versionchanged:: 1.7.0
+ Added the ``reload`` method.
+
+ Log a line for each response, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
+
+ If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
+
+ The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
+ The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
+
+ Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
+
+ Subsequent rules are processed after this action.
+
+ :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
+ :param bool append: Append to the log. Default false
+ :param bool buffered: Use buffered I/O. Default true
+ :param bool verboseOnly: Whether to log only in verbose mode when logging to stdout. Default is true
+ :param bool includeTimestamp: Whether to include a timestamp for every entry. Default is false
+
+.. function:: LuaAction(function)
+
+ Invoke a Lua function that accepts a :class:`DNSQuestion`.
+
+ The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
+
+ :param string function: the name of a Lua function
+
+.. function:: LuaFFIAction(function)
+
+ .. versionadded:: 1.5.0
+
+ Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+ The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
+
+ :param string function: the name of a Lua function
+
+.. function:: LuaFFIPerThreadAction(function)
+
+ .. versionadded:: 1.7.0
+
+ Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+ The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
+
+ The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
+ as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
+ are not available.
+
+ :param string function: a Lua string returning a Lua function
+
+.. function:: LuaFFIPerThreadResponseAction(function)
+
+ .. versionadded:: 1.7.0
+
+ Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+ The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
+
+ The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
+ as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
+ are not available.
+
+ :param string function: a Lua string returning a Lua function
+
+.. function:: LuaFFIResponseAction(function)
+
+ .. versionadded:: 1.5.0
+
+ Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+ The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
+
+ :param string function: the name of a Lua function
+
+.. function:: LuaResponseAction(function)
+
+ Invoke a Lua function that accepts a :class:`DNSResponse`.
+
+ The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
+
+ :param string function: the name of a Lua function
+
+.. function:: MacAddrAction(option)
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetMacAddrAction` instead.
+
+ Add the source MAC address to the query as EDNS0 option ``option``.
+ This action is currently only supported on Linux.
+ Subsequent rules are processed after this action.
+
+ :param int option: The EDNS0 option number
+
+.. function:: NegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
+
+ .. versionadded:: 1.6.0
+
+ .. versionchanged:: 1.8.0
+ Added the ``soaInAuthoritySection`` option.
+
+ Turn a question into a response, either a NXDOMAIN or a NODATA one based on ''nxd'', setting the QR bit to 1 and adding a SOA record in the additional section.
+ Note that this function was called :func:`SetNegativeAndSOAAction` before 1.6.0.
+
+ :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
+ :param string zone: The owner name for the SOA record
+ :param int ttl: The TTL of the SOA record
+ :param string mname: The mname of the SOA record
+ :param string rname: The rname of the SOA record
+ :param int serial: The value of the serial field in the SOA record
+ :param int refresh: The value of the refresh field in the SOA record
+ :param int retry: The value of the retry field in the SOA record
+ :param int expire: The value of the expire field in the SOA record
+ :param int minimum: The value of the minimum field in the SOA record
+ :param table options: A table with key: value pairs with options
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+ * ``soaInAuthoritySection``: bool - Place the SOA record in the authority section for a complete NXDOMAIN/NODATA response that works as a cacheable negative response, rather than the RPZ-style response with a purely informational SOA in the additional section. Default is false (SOA in additional section).
+
+.. function:: NoneAction()
+
+ Does nothing.
+ Subsequent rules are processed after this action.
+
+.. function:: NoRecurseAction()
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetNoRecurseAction` instead.
+
+ Strip RD bit from the question, let it go through.
+ Subsequent rules are processed after this action.
+
+.. function:: PoolAction(poolname [, stop])
+
+ .. versionchanged:: 1.8.0
+ Added the ``stop`` optional parameter.
+
+ Send the packet into the specified pool. If ``stop`` is set to false, subsequent rules will be processed after this action.
+
+ :param string poolname: The name of the pool
+ :param bool stop: Whether to stop processing rules after this action. Default is true, meaning the remaining rules will not be processed.
+
+.. function:: QPSAction(maxqps)
+
+ Drop a packet if it does exceed the ``maxqps`` queries per second limits.
+ Letting the subsequent rules apply otherwise.
+
+ :param int maxqps: The QPS limit
+
+.. function:: QPSPoolAction(maxqps, poolname [, stop])
+
+ .. versionchanged:: 1.8.0
+ Added the ``stop`` optional parameter.
+
+ Send the packet into the specified pool only if it does not exceed the ``maxqps`` queries per second limits. If ``stop`` is set to false, subsequent rules will be processed after this action.
+ Letting the subsequent rules apply otherwise.
+
+ :param int maxqps: The QPS limit for that pool
+ :param string poolname: The name of the pool
+ :param bool stop: Whether to stop processing rules after this action. Default is true, meaning the remaining rules will not be processed.
+
+.. function:: RCodeAction(rcode [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
+ Reply immediately by turning the query into a response with the specified ``rcode``.
+ ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+
+ :param int rcode: The RCODE to respond with.
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: RemoteLogAction(remoteLogger[, alterFunction [, options [, metas]]])
+
+ .. versionchanged:: 1.4.0
+ ``ipEncryptKey`` optional key added to the options table.
+
+ .. versionchanged:: 1.8.0
+ ``metas`` optional parameter added.
+ ``exportTags`` optional key added to the options table.
+
+ Send the content of this query to a remote logger via Protocol Buffer.
+ ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
+ Since 1.8.0 it is possible to add configurable meta-data fields to the Protocol Buffer message via the ``metas`` parameter, which takes a list of ``name``=``key`` pairs. For each entry in the list, a new value named ``name``
+ will be added to the message with the value corresponding to the ``key``. Available keys are:
+
+ * ``doh-header:<HEADER>``: the content of the corresponding ``<HEADER>`` HTTP header for DoH queries, empty otherwise
+ * ``doh-host``: the ``Host`` header for DoH queries, empty otherwise
+ * ``doh-path``: the HTTP path for DoH queries, empty otherwise
+ * ``doh-query-string``: the HTTP query string for DoH queries, empty otherwise
+ * ``doh-scheme``: the HTTP scheme for DoH queries, empty otherwise
+ * ``pool``: the currently selected pool of servers
+ * ``proxy-protocol-value:<TYPE>``: the content of the proxy protocol value of type ``<TYPE>``, if any
+ * ``proxy-protocol-values``: the content of all proxy protocol values as a "<type1>:<value1>", ..., "<typeN>:<valueN>" strings
+ * ``b64-content``: the base64-encoded DNS payload of the current query
+ * ``sni``: the Server Name Indication value for queries received over DoT or DoH. Empty otherwise.
+ * ``tag:<TAG>``: the content of the corresponding ``<TAG>`` if any
+ * ``tags``: the list of all tags, and their values, as a "<key1>:<value1>", ..., "<keyN>:<valueN>" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:".
+
+ Subsequent rules are processed after this action.
+
+ :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
+ :param string alterFunction: Name of a function to modify the contents of the logs before sending
+ :param table options: A table with key: value pairs.
+ :param table metas: A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message.
+
+ Options:
+
+ * ``serverID=""``: str - Set the Server Identity field.
+ * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
+ * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
+
+.. function:: RemoteLogResponseAction(remoteLogger[, alterFunction[, includeCNAME [, options [, metas]]]])
+
+ .. versionchanged:: 1.4.0
+ ``ipEncryptKey`` optional key added to the options table.
+
+ .. versionchanged:: 1.8.0
+ ``metas`` optional parameter added.
+ ``exportTags`` optional key added to the options table.
+
+ .. versionchanged:: 1.9.0
+ ``exportExtendedErrorsToMeta`` optional key added to the options table.
+
+ Send the content of this response to a remote logger via Protocol Buffer.
+ ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
+ ``includeCNAME`` indicates whether CNAME records inside the response should be parsed and exported.
+ The default is to only exports A and AAAA records.
+ Since 1.8.0 it is possible to add configurable meta-data fields to the Protocol Buffer message via the ``metas`` parameter, which takes a list of ``name``=``key`` pairs. See :func:`RemoteLogAction` for the list of available keys.
+ Subsequent rules are processed after this action.
+
+ :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
+ :param string alterFunction: Name of a function to modify the contents of the logs before sending
+ :param bool includeCNAME: Whether or not to parse and export CNAMEs. Default false
+ :param table options: A table with key: value pairs.
+ :param table metas: A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message.
+
+ Options:
+
+ * ``serverID=""``: str - Set the Server Identity field.
+ * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
+ * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
+ * ``exportExtendedErrorsToMeta=""``: str - Export Extended DNS Errors present in the DNS response, if any, into the ``meta`` Protocol Buffer field using the specified ``key``. The EDE info code will be exported as an integer value, and the EDE extra text, if present, as a string value.
+
+.. function:: SetAdditionalProxyProtocolValueAction(type, value)
+
+ .. versionadded:: 1.6.0
+
+ Add a Proxy-Protocol Type-Length value to be sent to the server along with this query. It does not replace any
+ existing value with the same type but adds a new value.
+ Be careful that Proxy Protocol values are sent once at the beginning of the TCP connection for TCP and DoT queries.
+ That means that values received on an incoming TCP connection will be inherited by subsequent queries received over
+ the same incoming TCP connection, if any, but values set to a query will not be inherited by subsequent queries.
+ Subsequent rules are processed after this action.
+
+ :param int type: The type of the value to send, ranging from 0 to 255 (both included)
+ :param str value: The binary-safe value
+
+.. function:: SetDisableECSAction()
+
+ .. versionadded:: 1.6.0
+
+ Disable the sending of ECS to the backend.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`DisableECSAction` before 1.6.0.
+
+.. function:: SetDisableValidationAction()
+
+ .. versionadded:: 1.6.0
+
+ Set the CD bit in the query and let it go through.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`DisableValidationAction` before 1.6.0.
+
+.. function:: SetECSAction(v4 [, v6])
+
+ Set the ECS prefix and prefix length sent to backends to an arbitrary value.
+ If both IPv4 and IPv6 masks are supplied the IPv4 one will be used for IPv4 clients
+ and the IPv6 one for IPv6 clients. Otherwise the first mask is used for both, and
+ can actually be an IPv6 mask.
+ Subsequent rules are processed after this action.
+
+ :param string v4: The IPv4 netmask, for example "192.0.2.1/32"
+ :param string v6: The IPv6 netmask, if any
+
+.. function:: SetECSOverrideAction(override)
+
+ .. versionadded:: 1.6.0
+
+ Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`ECSOverrideAction` before 1.6.0.
+
+ :param bool override: Whether or not to override ECS value
+
+.. function:: SetECSPrefixLengthAction(v4, v6)
+
+ .. versionadded:: 1.6.0
+
+ Set the ECS prefix length.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`ECSPrefixLengthAction` before 1.6.0.
+
+ :param int v4: The IPv4 netmask length
+ :param int v6: The IPv6 netmask length
+
+.. function:: SetEDNSOptionAction(option)
+
+ .. versionadded:: 1.7.0
+
+ Add arbitrary EDNS option and data to the query. Any existing EDNS content with the same option code will be overwritten.
+ Subsequent rules are processed after this action.
+
+ :param int option: The EDNS option number
+ :param string data: The EDNS0 option raw content
+
+.. function:: SetExtendedDNSErrorAction(infoCode [, extraText])
+
+ .. versionadded:: 1.9.0
+
+ Set an Extended DNS Error status that will be added to the response corresponding to the current query.
+ Subsequent rules are processed after this action.
+
+ :param int infoCode: The EDNS Extended DNS Error code
+ :param string extraText: The optional EDNS Extended DNS Error extra text
+
+.. function:: SetExtendedDNSErrorResponseAction(infoCode [, extraText])
+
+ .. versionadded:: 1.9.0
+
+ Set an Extended DNS Error status that will be added to this response.
+ Subsequent rules are processed after this action.
+
+ :param int infoCode: The EDNS Extended DNS Error code
+ :param string extraText: The optional EDNS Extended DNS Error extra text
+
+.. function:: SetMacAddrAction(option)
+
+ .. versionadded:: 1.6.0
+
+ Add the source MAC address to the query as EDNS0 option ``option``.
+ This action is currently only supported on Linux.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`MacAddrAction` before 1.6.0.
+
+ :param int option: The EDNS0 option number
+
+.. function:: SetMaxReturnedTTLAction(max)
+
+ .. versionadded:: 1.8.0
+
+ Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values.
+
+ :param int max: The maximum allowed value
+
+.. function:: SetMaxReturnedTTLResponseAction(max)
+
+ .. versionadded:: 1.8.0
+
+ Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values.
+
+ :param int max: The maximum allowed value
+
+.. function:: SetMaxTTLResponseAction(max)
+
+ .. versionadded:: 1.8.0
+
+ Cap the TTLs of the response to the given maximum.
+
+ :param int max: The maximum allowed value
+
+.. function:: SetMinTTLResponseAction(min)
+
+ .. versionadded:: 1.8.0
+
+ Cap the TTLs of the response to the given minimum.
+
+ :param int min: The minimum allowed value
+
+.. function:: SetNoRecurseAction()
+
+ .. versionadded:: 1.6.0
+
+ Strip RD bit from the question, let it go through.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`NoRecurseAction` before 1.6.0.
+
+.. function:: SetNegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
+
+ .. versionadded:: 1.5.0
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`NegativeAndSOAAction` instead.
+
+ Turn a question into a response, either a NXDOMAIN or a NODATA one based on ''nxd'', setting the QR bit to 1 and adding a SOA record in the additional section.
+
+ :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
+ :param string zone: The owner name for the SOA record
+ :param int ttl: The TTL of the SOA record
+ :param string mname: The mname of the SOA record
+ :param string rname: The rname of the SOA record
+ :param int serial: The value of the serial field in the SOA record
+ :param int refresh: The value of the refresh field in the SOA record
+ :param int retry: The value of the retry field in the SOA record
+ :param int expire: The value of the expire field in the SOA record
+ :param int minimum: The value of the minimum field in the SOA record
+ :param table options: A table with key: value pairs with options
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: SetProxyProtocolValuesAction(values)
+
+ .. versionadded:: 1.5.0
+
+ Set the Proxy-Protocol Type-Length values to be sent to the server along with this query to ``values``.
+ Subsequent rules are processed after this action.
+
+ :param table values: A table of types and values to send, for example: ``{ [0] = foo", [42] = "bar" }``
+
+.. function:: SetReducedTTLResponseAction(percentage)
+
+ .. versionadded:: 1.8.0
+
+ Reduce the TTL of records in a response to a percentage of the original TTL. For example,
+ passing 50 means that the original TTL will be cut in half.
+ Subsequent rules are processed after this action.
+
+ :param int percentage: The percentage to use
+
+.. function:: SetSkipCacheAction()
+
+ .. versionadded:: 1.6.0
+
+ Don't lookup the cache for this query, don't store the answer.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`SkipCacheAction` before 1.6.0.
+
+.. function:: SetSkipCacheResponseAction()
+
+ .. versionadded:: 1.6.0
+
+ Don't store this answer into the cache.
+ Subsequent rules are processed after this action.
+
+.. function:: SetTagAction(name, value)
+
+ .. versionadded:: 1.6.0
+
+ .. versionchanged:: 1.7.0
+ Prior to 1.7.0 :func:`SetTagAction` would not overwrite an existing tag value if already set.
+
+ Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
+ This function will overwrite any existing tag value.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`TagAction` before 1.6.0.
+
+ :param string name: The name of the tag to set
+ :param string value: The value of the tag
+
+.. function:: SetTagResponseAction(name, value)
+
+ .. versionadded:: 1.6.0
+
+ .. versionchanged:: 1.7.0
+ Prior to 1.7.0 :func:`SetTagResponseAction` would not overwrite an existing tag value if already set.
+
+ Associate a tag named ``name`` with a value of ``value`` to this response.
+ This function will overwrite any existing tag value.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`TagResponseAction` before 1.6.0.
+
+ :param string name: The name of the tag to set
+ :param string value: The value of the tag
+
+.. function:: SetTempFailureCacheTTLAction(ttl)
+
+ .. versionadded:: 1.6.0
+
+ Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
+ Subsequent rules are processed after this action.
+ Note that this function was called :func:`TempFailureCacheTTLAction` before 1.6.0.
+
+ :param int ttl: Cache TTL for temporary failure replies
+
+.. function:: SkipCacheAction()
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetSkipAction` instead.
+
+ Don't lookup the cache for this query, don't store the answer.
+ Subsequent rules are processed after this action.
+
+.. function:: SNMPTrapAction([message])
+
+ Send an SNMP trap, adding the optional ``message`` string as the query description.
+ Subsequent rules are processed after this action.
+
+ :param string message: The message to include
+
+.. function:: SNMPTrapResponseAction([message])
+
+ Send an SNMP trap, adding the optional ``message`` string as the query description.
+ Subsequent rules are processed after this action.
+
+ :param string message: The message to include
+
+.. function:: SpoofAction(ip [, options])
+ SpoofAction(ips [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
+ .. versionchanged:: 1.6.0
+ Up to 1.6.0, the syntax for this function was ``SpoofAction(ips[, ip[, options]])``.
+
+ Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
+ If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
+
+ :param string ip: An IPv4 and/or IPv6 address to spoof
+ :param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+ * ``ttl``: int - The TTL of the record.
+
+.. function:: SpoofCNAMEAction(cname [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
+ Forge a response with the specified CNAME value.
+
+ :param string cname: The name to respond with
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+ * ``ttl``: int - The TTL of the record.
+
+.. function:: SpoofRawAction(rawAnswer [, options])
+ SpoofRawAction(rawAnswers [, options])
+
+ .. versionadded:: 1.5.0
+
+ .. versionchanged:: 1.6.0
+ Up to 1.6.0, it was only possible to spoof one answer.
+
+ .. versionchanged:: 1.9.0
+ Added the optional parameter ``typeForAny``.
+
+ Forge a response with the specified raw bytes as record data.
+
+ .. code-block:: Lua
+
+ -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with both a "aaa" "bbbb" and "ccc" TXT record:
+ addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction({"\003aaa\004bbbb", "\003ccc"}))
+ -- select queries for the 'raw-srv.powerdns.com.' name and SRV type, and answer with a '0 0 65535 srv.powerdns.com.' SRV record, setting the AA bit to 1 and the TTL to 3600s
+ addAction(AndRule({QNameRule('raw-srv.powerdns.com.'), QTypeRule(DNSQType.SRV)}), SpoofRawAction("\000\000\000\000\255\255\003srv\008powerdns\003com\000", { aa=true, ttl=3600 }))
+ -- select reverse queries for '127.0.0.1' and answer with 'localhost'
+ addAction(AndRule({QNameRule('1.0.0.127.in-addr.arpa.'), QTypeRule(DNSQType.PTR)}), SpoofRawAction("\009localhost\000"))
+ -- rfc8482: Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY via HINFO of value "rfc8482"
+ addAction(QTypeRule(DNSQType.ANY), SpoofRawAction("\007rfc\056\052\056\050\000", { typeForAny=DNSQType.HINFO }))
+
+ :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``.
+
+ ``sdig dumpluaraw`` and ``pdnsutil raw-lua-from-content`` from PowerDNS can generate raw answers for you:
+
+ .. code-block:: Shell
+
+ $ pdnsutil raw-lua-from-content SRV '0 0 65535 srv.powerdns.com.'
+ "\000\000\000\000\255\255\003srv\008powerdns\003com\000"
+ $ sdig 127.0.0.1 53 open-xchange.com MX recurse dumpluaraw
+ Reply to question for qname='open-xchange.com.', qtype=MX
+ Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
+ 0 open-xchange.com. IN MX "\000c\004mx\049\049\012open\045xchange\003com\000"
+ 0 open-xchange.com. IN MX "\000\010\003mx\049\012open\045xchange\003com\000"
+ 0 open-xchange.com. IN MX "\000\020\003mx\050\012open\045xchange\003com\000"
+
+ :param string rawAnswer: The raw record data
+ :param {string} rawAnswers: A table of raw record data to spoof
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+ * ``ttl``: int - The TTL of the record.
+ * ``typeForAny``: int - The record type to use when responding to queries of type ``ANY``, as using ``ANY`` for the type of the response record would not make sense.
+
+.. function:: SpoofSVCAction(svcParams [, options])
+
+ .. versionadded:: 1.7.0
+
+ Forge a response with the specified SVC record data. If the list contains more than one class:`SVCRecordParameters` (generated via :func:`newSVCRecordParameters`) object, they are all returned,
+ and should have different priorities.
+ The hints provided in the SVC parameters, if any, will also be added as A/AAAA records in the additional section, using the target name present in the parameters as owner name if it's not empty (root) and the qname instead.
+
+ :param list of class:`SVCRecordParameters` svcParams: The record data to return
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+ * ``ttl``: int - The TTL of the record.
+
+.. function:: SpoofPacketAction(rawPacket, len)
+
+ .. versionadded:: 1.8.0
+
+ Spoof a raw self-generated answer
+
+ :param string rawPacket: The raw wire-ready DNS answer
+ :param int len: The length of the packet
+
+.. function:: TagAction(name, value)
+
+ .. deprecated:: 1.6.0
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTagAction` instead.
+
+ Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
+ Subsequent rules are processed after this action.
+
+ :param string name: The name of the tag to set
+ :param string value: The value of the tag
+
+.. function:: TagResponseAction(name, value)
+
+ .. deprecated:: 1.6.0
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTagResponseAction` instead.
+
+ Associate a tag named ``name`` with a value of ``value`` to this response.
+ Subsequent rules are processed after this action.
+
+ :param string name: The name of the tag to set
+ :param string value: The value of the tag
+
+.. function:: TCAction()
+
+ .. versionchanged:: 1.7.0
+ This action is now only performed over UDP transports.
+
+ Create answer to query with the TC bit set, and the RA bit set to the value of RD in the query, to force the client to TCP.
+ Before 1.7.0 this action was performed even when the query had been received over TCP, which required the use of :func:`TCPRule` to
+ prevent the TC bit from being set over TCP transports.
+
+.. function:: TCResponseAction()
+
+ .. versionadded:: 1.9.0
+
+ Truncate an existing answer, to force the client to TCP. Only applied to answers that will be sent to the client over TCP.
+ In addition to the TC bit being set, all records are removed from the answer, authority and additional sections.
+
+.. function:: TeeAction(remote[, addECS[, local [, addProxyProtocol]]])
+
+ .. versionchanged:: 1.8.0
+ Added the optional parameter ``local``.
+
+ .. versionchanged:: 1.9.0
+ Added the optional parameter ``addProxyProtocol``.
+
+ Send copy of query to ``remote``, keep stats on responses.
+ If ``addECS`` is set to true, EDNS Client Subnet information will be added to the query.
+ If ``addProxyProtocol`` is set to true, a Proxy Protocol v2 payload will be prepended in front of the query. The payload will contain the protocol the initial query was received over (UDP or TCP), as well as the initial source and destination addresses and ports.
+ If ``local`` has provided a value like "192.0.2.53", :program:`dnsdist` will try binding that address as local address when sending the queries.
+ Subsequent rules are processed after this action.
+
+ :param string remote: An IP:PORT combination to send the copied queries to
+ :param bool addECS: Whether to add ECS information. Default false.
+ :param str local: The local address to use to send queries. The default is to let the kernel pick one.
+ :param bool addProxyProtocol: Whether to prepend a proxy protocol v2 payload in front of the query. Default to false.
+
+.. function:: TempFailureCacheTTLAction(ttl)
+
+ .. deprecated:: 1.6.0
+
+ This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTempFailureCacheTTLAction` instead.
+
+ Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
+ Subsequent rules are processed after this action.
+
+ :param int ttl: Cache TTL for temporary failure replies
.. versionchanged:: 1.6.0
Added ``maxInFlight`` and ``maxConcurrentTCPConnections`` parameters.
+ .. versionchanged:: 1.9.0
+ Added the ``enableProxyProtocol`` parameter, which was always ``true`` before 1.9.0, and the``xskSocket`` one.
+
Add to the list of listen addresses. Note that for IPv6 link-local addresses, it might be necessary to specify the interface to use: ``fe80::1%eth0``. On recent Linux versions specifying the interface via the ``interface`` parameter should work as well.
:param str address: The IP Address with an optional port to listen on.
* ``tcpListenQueueSize=SOMAXCONN``: int - Set the size of the listen queue. Default is ``SOMAXCONN``.
* ``maxInFlight=0``: int - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing.
* ``maxConcurrentTCPConnections=0``: int - Maximum number of concurrent incoming TCP connections. The default is 0 which means unlimited.
+ * ``enableProxyProtocol=true``: str - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address in :func:`setProxyProtocolACL`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the :func:`setProxyProtocolACL` ACL.
+ * ``xskSocket``: :class:`XskSocket` - A socket to enable ``XSK`` / ``AF_XDP`` support for this frontend. See :doc:`../advanced/xsk` for more information.
.. code-block:: lua
``internalPipeBufferSize`` now defaults to 1048576 on Linux.
.. versionchanged:: 1.8.0
- ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`)
+ ``certFile`` now accepts a :class:`TLSCertificate` object or a list of such objects (see :func:`newTLSCertificate`)
``additionalAddresses``, ``ignoreTLSConfigurationErrors`` and ``keepIncomingHeaders`` options added.
- Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate.
+ .. versionchanged:: 1.9.0
+ ``enableProxyProtocol``, ``library``, ``proxyProtocolOutsideTLS`` and ``readAhead`` options added.
+
+ Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
+ More information is available in :doc:`../guides/dns-over-https`.
:param str address: The IP Address with an optional port to listen on.
The default port is 443.
- :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a TLSCertificate object.
- :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains TLSCertificate objects.
+ :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a :class:`TLSCertificate` object.
+ :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains :class:`TLSCertificate` objects.
:param str-or-list urls: The path part of a URL, or a list of paths, to accept queries on. Any query with a path matching exactly one of these will be treated as a DoH query (sub-paths can be accepted by setting the ``exactPathMatching`` to false). The default is /dns-query.
:param table options: A table with key: value pairs with listen options.
* ``idleTimeout=30``: int - Set the idle timeout, in seconds.
* ``ciphers``: str - The TLS ciphers to use, in OpenSSL format. Ciphers for TLS 1.3 must be specified via ``ciphersTLS13``.
* ``ciphersTLS13``: str - The TLS ciphers to use for TLS 1.3, in OpenSSL format.
- * ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "h2o/dnsdist".
+ * ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "h2o/dnsdist" when ``h2o`` is used, "nghttp2-<version>/dnsdist" when ``nghttp2`` is.
* ``customResponseHeaders={}``: table - Set custom HTTP header(s) returned by dnsdist.
* ``ocspResponses``: list - List of files containing OCSP responses, in the same order than the certificates and keys, that will be used to provide OCSP stapling responses.
* ``minTLSVersion``: str - Minimum version of the TLS protocol to support. Possible values are 'tls1.0', 'tls1.1', 'tls1.2' and 'tls1.3'. Default is to require at least TLS 1.0.
* ``numberOfTicketsKeys``: int - The maximum number of tickets keys to keep in memory at the same time. Only one key is marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation. Default to 5.
* ``ticketKeyFile``: str - The path to a file from where TLS tickets keys should be loaded, to support :rfc:`5077`. These keys should be rotated often and never written to persistent storage to preserve forward secrecy. The default is to generate a random key. dnsdist supports several tickets keys to be able to decrypt existing sessions after the rotation. See :doc:`../advanced/tls-sessions-management` for more information.
- * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h).
+ * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h). A value of 0 disables the automatic rotation, which might be useful when ``ticketKeyFile`` is used.
* ``sessionTimeout``: int - Set the TLS session lifetime in seconds, this is used both for TLS ticket lifetime and for sessions kept in memory.
* ``sessionTickets``: bool - Whether session resumption via session tickets is enabled. Default is true, meaning tickets are enabled.
* ``numberOfStoredSessions``: int - The maximum number of sessions kept in memory at the same time. Default is 20480. Setting this value to 0 disables stored session entirely.
* ``keepIncomingHeaders``: bool - Whether to retain the incoming headers in memory, to be able to use :func:`HTTPHeaderRule` or :meth:`DNSQuestion.getHTTPHeaders`. Default is false. Before 1.8.0 the headers were always kept in-memory.
* ``additionalAddresses``: list - List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses.
* ``ignoreTLSConfigurationErrors=false``: bool - Ignore TLS configuration errors (such as invalid certificate path) and just issue a warning instead of aborting the whole process
+ * ``library``: str - Which underlying HTTP2 library should be used, either h2o or nghttp2. Until 1.9.0 only h2o was available, but the use of this library is now deprecated as it is no longer maintained. nghttp2 is the new default since 1.9.0.
+ * ``readAhead``: bool - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls. Default is true.
+ * ``proxyProtocolOutsideTLS``: bool - When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text.
+ * ``enableProxyProtocol=true``: bool - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address in :func:`setProxyProtocolACL`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the :func:`setProxyProtocolACL` ACL.
+
+.. function:: addDOH3Local(address, certFile(s), keyFile(s) [, options])
+
+ .. versionadded:: 1.9.0
+
+ Listen on the specified address and UDP port for incoming DNS over HTTP3 connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
+ More information is available in :doc:`../guides/dns-over-http3`.
+
+ :param str address: The IP Address with an optional port to listen on.
+ The default port is 443.
+ :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a :class:`TLSCertificate` object.
+ :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains :class:`TLSCertificate` objects.
+ :param table options: A table with key: value pairs with listen options.
+
+ Options:
+
+ * ``reusePort=false``: bool - Set the ``SO_REUSEPORT`` socket option.
+ * ``interface=""``: str - Set the network interface to use.
+ * ``cpus={}``: table - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the pthread_setaffinity_np() function.
+ * ``idleTimeout=5``: int - Set the idle timeout, in seconds.
+ * ``internalPipeBufferSize=0``: int - Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used. The default value is 0, except on Linux where it is 1048576 since 1.6.0.
+ * ``maxInFlight=65535``: int - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing.
+ * ``congestionControlAlgo="reno"``: str - The congestion control algorithm to be chosen between ``reno``, ``cubic`` and ``bbr``.
+ * ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.
+
+.. function:: addDOQLocal(address, certFile(s), keyFile(s) [, options])
+
+ .. versionadded:: 1.9.0
+
+ Listen on the specified address and UDP port for incoming DNS over QUIC connections, presenting the specified X.509 certificate.
+ See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
+ More information is available at :doc:`../guides/dns-over-quic`.
+
+ :param str address: The IP Address with an optional port to listen on.
+ The default port is 853.
+ :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a :class:`TLSCertificate` object.
+ :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains :class:`TLSCertificate` objects.
+ :param table options: A table with key: value pairs with listen options.
+
+ Options:
+
+ * ``reusePort=false``: bool - Set the ``SO_REUSEPORT`` socket option.
+ * ``interface=""``: str - Set the network interface to use.
+ * ``cpus={}``: table - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the pthread_setaffinity_np() function.
+ * ``idleTimeout=5``: int - Set the idle timeout, in seconds.
+ * ``internalPipeBufferSize=0``: int - Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used. The default value is 0, except on Linux where it is 1048576 since 1.6.0.
+ * ``maxInFlight=65535``: int - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing.
+ * ``congestionControlAlgo="reno"``: str - The congestion control algorithm to be chosen between ``reno``, ``cubic`` and ``bbr``.
+ * ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.
.. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
.. versionchanged:: 1.8.0
``tlsAsyncMode`` option added.
.. versionchanged:: 1.8.0
- ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`).
+ ``certFile`` now accepts a :class:`TLSCertificate` object or a list of such objects (see :func:`newTLSCertificate`).
``additionalAddresses``, ``ignoreTLSConfigurationErrors`` and ``ktls`` options added.
+ .. versionchanged:: 1.9.0
+ ``enableProxyProtocol``, ``readAhead`` and ``proxyProtocolOutsideTLS`` options added.
- Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate.
+ Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
+ More information is available at :doc:`../guides/dns-over-tls`.
:param str address: The IP Address with an optional port to listen on.
The default port is 853.
- :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a TLSCertificate object.
- :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains TLSCertificate objects.
+ :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a :class:`TLSCertificate` object.
+ :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains :class:`TLSCertificate` objects.
:param table options: A table with key: value pairs with listen options.
Options:
* ``ciphersTLS13``: str - The ciphers to use for TLS 1.3, when the OpenSSL provider is used. When the GnuTLS provider is used, ``ciphers`` applies regardless of the TLS protocol and this setting is not used.
* ``numberOfTicketsKeys``: int - The maximum number of tickets keys to keep in memory at the same time, if the provider supports it (GnuTLS doesn't, OpenSSL does). Only one key is marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation. Default to 5.
* ``ticketKeyFile``: str - The path to a file from where TLS tickets keys should be loaded, to support :rfc:`5077`. These keys should be rotated often and never written to persistent storage to preserve forward secrecy. The default is to generate a random key. The OpenSSL provider supports several tickets keys to be able to decrypt existing sessions after the rotation, while the GnuTLS provider only supports one key. See :doc:`../advanced/tls-sessions-management` for more information.
- * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h).
+ * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h). A value of 0 disables the automatic rotation, which might be useful when ``ticketKeyFile`` is used.
* ``sessionTimeout``: int - Set the TLS session lifetime in seconds, this is used both for TLS ticket lifetime and for sessions kept in memory.
* ``sessionTickets``: bool - Whether session resumption via session tickets is enabled. Default is true, meaning tickets are enabled.
* ``numberOfStoredSessions``: int - The maximum number of sessions kept in memory at the same time. At this time this is only supported by the OpenSSL provider, as stored sessions are not supported with the GnuTLS one. Default is 20480. Setting this value to 0 disables stored session entirely.
* ``additionalAddresses``: list - List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses.
* ``ignoreTLSConfigurationErrors=false``: bool - Ignore TLS configuration errors (such as invalid certificate path) and just issue a warning instead of aborting the whole process
* ``ktls=false``: bool - Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false.
+ * ``readAhead``: bool - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls. Default is true.
+ * ``proxyProtocolOutsideTLS``: bool - When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text.
+ * ``enableProxyProtocol=true``: str - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address in :func:`setProxyProtocolACL`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the :func:`setProxyProtocolACL` ACL.
.. function:: setLocal(address[, options])
The ``password`` parameter is now optional.
The use of optional parameters is now deprecated. Please use :func:`setWebserverConfig` instead.
- .. versionchanged:: 1.7.0
+ .. versionchanged:: 1.8.0
The ``password``, ``apikey``, ``customHeaders`` and ``acl`` parameters is no longer supported.
Please use :func:`setWebserverConfig` instead.
.. versionadded:: 1.6.0
- Set the list of netmasks from which a Proxy Protocol header will be accepted, over UDP, TCP and DNS over TLS. The default is empty. Note that, if :func:`setProxyProtocolApplyACLToProxiedClients` is set (default is false), the general ACL will be applied to the source IP address as seen by dnsdist first, but also to the source IP address provided in the Proxy Protocol header.
+ Set the list of netmasks from which a Proxy Protocol header will be required, over UDP, TCP and DNS over TLS. The default is empty. Note that a proxy protocol payload will be required from these clients, regular DNS queries will no longer be accepted if they are not preceded by a proxy protocol payload. Be also aware that, if :func:`setProxyProtocolApplyACLToProxiedClients` is set (default is false), the general ACL will be applied to the source IP address as seen by dnsdist first, but also to the source IP address provided in the Proxy Protocol header.
:param {str} netmasks: A table of CIDR netmask, e.g. ``{"192.0.2.0/24", "2001:DB8:14::/56"}``. Without a subnetmask, only the specific address is allowed.
-.. function:: setProxyProtocolApplyACL(apply)
+.. function:: setProxyProtocolApplyACLToProxiedClients(apply)
.. versionadded:: 1.6.0
.. versionchanged:: 1.8.0
Added ``autoUpgrade``, ``autoUpgradeDoHKey``, ``autoUpgradeInterval``, ``autoUpgradeKeep``, ``autoUpgradePool``, ``maxConcurrentTCPConnections``, ``subjectAddr``, ``lazyHealthCheckSampleSize``, ``lazyHealthCheckMinSampleCount``, ``lazyHealthCheckThreshold``, ``lazyHealthCheckFailedInterval``, ``lazyHealthCheckMode``, ``lazyHealthCheckUseExponentialBackOff``, ``lazyHealthCheckMaxBackOff``, ``lazyHealthCheckWhenUpgraded``, ``healthCheckMode`` and ``ktls`` to server_table.
+ .. versionchanged:: 1.9.0
+ Added ``MACAddr``, ``proxyProtocolAdvertiseTLS`` and ``xskSockets`` to server_table.
+
+ :param str server_string: A simple IP:PORT string.
+ :param table server_table: A table with at least an ``address`` key
+
Add a new backend server. Call this function with either a string::
newServer(
or a table::
- newServer({
- address="IP:PORT", -- IP and PORT of the backend server (mandatory)
- id=STRING, -- Use a pre-defined UUID instead of a random one
- qps=NUM, -- Limit the number of queries per second to NUM, when using the `firstAvailable` policy
- order=NUM, -- The order of this server, used by the `leastOutstanding` and `firstAvailable` policies
- weight=NUM, -- The weight of this server, used by the `wrandom`, `whashed` and `chashed` policies, default: 1
- -- Supported values are a minimum of 1, and a maximum of 2147483647.
- pool=STRING|{STRING}, -- The pools this server belongs to (unset or empty string means default pool) as a string or table of strings
- retries=NUM, -- The number of TCP connection attempts to the backend, for a given query
- tcpConnectTimeout=NUM, -- The timeout (in seconds) of a TCP connection attempt
- tcpSendTimeout=NUM, -- The timeout (in seconds) of a TCP write attempt
- tcpRecvTimeout=NUM, -- The timeout (in seconds) of a TCP read attempt
- tcpFastOpen=BOOL, -- Whether to enable TCP Fast Open
- ipBindAddrNoPort=BOOL, -- Whether to enable IP_BIND_ADDRESS_NO_PORT if available, default: true
- name=STRING, -- The name associated to this backend, for display purpose
- checkClass=NUM, -- Use NUM as QCLASS in the health-check query, default: DNSClass.IN
- checkName=STRING, -- Use STRING as QNAME in the health-check query, default: "a.root-servers.net."
- checkType=STRING, -- Use STRING as QTYPE in the health-check query, default: "A"
- checkFunction=FUNCTION, -- Use this function to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`)
- checkTimeout=NUM, -- The timeout (in milliseconds) of a health-check query, default: 1000 (1s)
- setCD=BOOL, -- Set the CD (Checking Disabled) flag in the health-check query, default: false
- maxCheckFailures=NUM, -- Allow NUM check failures before declaring the backend down, default: 1
- checkInterval=NUM -- The time in seconds between health checks
- mustResolve=BOOL, -- Set to true when the health check MUST return a RCODE different from NXDomain, ServFail and Refused. Default is false, meaning that every RCODE except ServFail is considered valid
- useClientSubnet=BOOL, -- Add the client's IP address in the EDNS Client Subnet option when forwarding the query to this backend
- source=STRING, -- The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection
- -- The following formats are supported:
- -- "address", e.g. "192.0.2.2"
- -- "interface name", e.g. "eth0"
- -- "address@interface", e.g. "192.0.2.2@eth0"
- addXPF=NUM, -- Add the client's IP address and port to the query, along with the original destination address and port,
- -- using the experimental XPF record from `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ and the specified option code. Default is disabled (0). This is a deprecated feature that will be removed in the near future.
- sockets=NUM, -- Number of UDP sockets (and thus source ports) used toward the backend server, defaults to a single one. Note that for backends which are multithreaded, this setting will have an effect on the number of cores that will be used to process traffic from dnsdist. For example you may want to set 'sockets' to a number somewhat higher than the number of worker threads configured in the backend, particularly if the Linux kernel is being used to distribute traffic to multiple threads listening on the same socket (via `reuseport`).
- disableZeroScope=BOOL, -- Disable the EDNS Client Subnet 'zero scope' feature, which does a cache lookup for an answer valid for all subnets (ECS scope of 0) before adding ECS information to the query and doing the regular lookup. This requires the ``parseECS`` option of the corresponding cache to be set to true
- rise=NUM, -- Require NUM consecutive successful checks before declaring the backend up, default: 1
- useProxyProtocol=BOOL, -- Add a proxy protocol header to the query, passing along the client's IP address and port along with the original destination address and port. Default is disabled.
- reconnectOnUp=BOOL, -- Close and reopen the sockets when a server transits from Down to Up. This helps when an interface is missing when dnsdist is started. Default is disabled.
- maxInFlight=NUM, -- Maximum number of in-flight queries. The default is 0, which disables out-of-order processing. It should only be enabled if the backend does support out-of-order processing. As of 1.6.0, out-of-order processing needs to be enabled on the frontend as well, via :func:`addLocal` and/or :func:`addTLSLocal`. Note that out-of-order is always enabled on DoH frontends.
- tcpOnly=BOOL, -- Always forward queries to that backend over TCP, never over UDP. Always enabled for TLS backends. Default is false.
- checkTCP=BOOL, -- Whether to do healthcheck queries over TCP, instead of UDP. Always enabled for DNS over TLS backend. Default is false.
- tls=STRING, -- Enable DNS over TLS communications for this backend, or DNS over HTTPS if ``dohPath`` is set, using the TLS provider ("openssl" or "gnutls") passed in parameter. Default is an empty string, which means this backend is used for plain UDP and TCP.
- caStore=STRING, -- Specifies the path to the CA certificate file, in PEM format, to use to check the certificate presented by the backend. Default is an empty string, which means to use the system CA store. Note that this directive is only used if ``validateCertificates`` is set.
- ciphers=STRING, -- The TLS ciphers to use. The exact format depends on the provider used. When the OpenSSL provider is used, ciphers for TLS 1.3 must be specified via ``ciphersTLS13``.
- ciphersTLS13=STRING, -- The ciphers to use for TLS 1.3, when the OpenSSL provider is used. When the GnuTLS provider is used, ``ciphers`` applies regardless of the TLS protocol and this setting is not used.
- subjectName=STRING, -- The subject name passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty. If set this value supersedes any ``subjectAddr`` one.
- subjectAddr=STRING, -- The subject IP address passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty.
- validateCertificates=BOOL, -- Whether the certificate presented by the backend should be validated against the CA store (see ``caStore``). Default is true.
- dohPath=STRING, -- Enable DNS over HTTPS communication for this backend, using POST queries to the HTTP host supplied as ``subjectName`` and the HTTP path supplied in this parameter.
- addXForwardedHeaders=BOOL, -- Whether to add X-Forwarded-For, X-Forwarded-Port and X-Forwarded-Proto headers to a DNS over HTTPS backend.
- releaseBuffers=BOOL, -- Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection. Default to true.
- enableRenegotiation=BOOL, -- Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS.
- autoUpgrade=BOOL, -- Whether to use the 'Discovery of Designated Resolvers' mechanism to automatically upgrade a Do53 backend to DoT or DoH, depending on the priorities present in the SVCB record returned by the backend. Default to false.
- autoUpgradeInterval=NUM, -- If ``autoUpgrade`` is set, how often to check if an upgrade is available, in seconds. Default is 3600 seconds.
- autoUpgradeKeep=BOOL, -- If ``autoUpgrade`` is set, whether to keep the existing Do53 backend around after an upgrade. Default is false which means the Do53 backend will be replaced by the upgraded one.
- autoUpgradePool=STRING, -- If ``autoUpgrade`` is set, in which pool to place the newly upgraded backend. Default is empty which means the backend is placed in the default pool.
- autoUpgradeDoHKey=NUM, -- If ``autoUpgrade`` is set, the value to use for the SVC key corresponding to the DoH path. Default is 7.
- maxConcurrentTCPConnections=NUM, -- Maximum number of TCP connections to that backend. When that limit is reached, queries routed to that backend that cannot be forwarded over an existing connection will be dropped. Default is 0 which means no limit.
- healthCheckMode=STRING -- The health-check mode to use: 'auto' which sends health-check queries every ``checkInterval`` seconds, 'up' which considers that the backend is always available, 'down' that it is always not available, and 'lazy' which only sends health-check queries after a configurable amount of regular queries have failed (see ``lazyHealthCheckSampleSize``, ``lazyHealthCheckMinSampleCount``, ``lazyHealthCheckThreshold``, ``lazyHealthCheckFailedInterval`` and ``lazyHealthCheckMode`` for more information). Default is 'auto'. See :ref:`Healthcheck` for a more detailed explanation.
- lazyHealthCheckFailedInterval=NUM, -- The interval, in seconds, between health-check queries in 'lazy' mode. Note that when ``lazyHealthCheckUseExponentialBackOff`` is set to true, the interval doubles between every queries. These queries are only sent when a threshold of failing regular queries has been reached, and until the backend is available again. Default is 30 seconds.
- lazyHealthCheckMinSampleCount=NUM, -- The minimum amount of regular queries that should have been recorded before the ``lazyHealthCheckThreshold`` threshold can be applied. Default is 1 which means only one query is needed.
- lazyHealthCheckMode=STRING, -- The 'lazy' health-check mode: 'TimeoutOnly' means that only timeout and I/O errors of regular queries will be considered for the ``lazyHealthCheckThreshold``, while 'TimeoutOrServFail' will also consider 'Server Failure' answers. Default is 'TimeoutOrServFail'.
- lazyHealthCheckSampleSize=NUM, -- The maximum size of the sample of queries to record and consider for the ``lazyHealthCheckThreshold``. Default is 100, which means the result (failure or success) of the last 100 queries will be considered.
- lazyHealthCheckThreshold=NUM, -- The threshold, as a percentage, of queries that should fail for the 'lazy' health-check to be triggered when ``healthCheckMode`` is set to ``lazy``. The default is 20 which means 20% of the last ``lazyHealthCheckSampleSize`` queries should fail for a health-check to be triggered.
- lazyHealthCheckUseExponentialBackOff=BOOL, -- Whether the 'lazy' health-check should use an exponential back-off instead of a fixed value, between health-check probes. The default is false which means that after a backend has been moved to the 'down' state health-check probes are sent every ``lazyHealthCheckFailedInterval`` seconds. When set to true, the delay between each probe starts at ``lazyHealthCheckFailedInterval`` seconds and double between every probe, capped at ``lazyHealthCheckMaxBackOff`` seconds.
- lazyHealthCheckMaxBackOff=NUM, -- This value, in seconds, caps the time between two health-check queries when ``lazyHealthCheckUseExponentialBackOff`` is set to true. The default is 3600 which means that at most one hour will pass between two health-check queries.
- lazyHealthCheckWhenUpgraded=BOOL, -- Whether the auto-upgraded version of this backend (see ``autoUpgrade``) should use the lazy health-checking mode. Default is false, which means it will use the regular health-checking mode.
- ktls=BOOL, -- Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false. Currently both DoT and DoH backend support this option.
- })
-
- :param str server_string: A simple IP:PORT string.
- :param table server_table: A table with at least a 'name' key
+ newServer({ ... })
+
+ where the elements in the table can be:
+
+ .. csv-table::
+ :delim: space
+ :header: Keyword, Type, Description
+ :widths: auto
+
+ ``address`` ``ip:port`` "``ip`` and ``port`` of the backend server (mandatory)"
+ ``id`` ``string`` "Use a pre-defined UUID instead of a random one"
+
+ ``qps`` ``number`` "Limit the number of queries per second to ``number``, when using the `firstAvailable` policy"
+ ``order`` ``number`` "The order of this server, used by the `leastOutstanding` and `firstAvailable` policies"
+ ``weight`` ``number`` "The weight of this server, used by the `wrandom`, `whashed` and `chashed` policies, default: 1. Supported values are a minimum of 1, and a maximum of 2147483647."
+ ``pool`` ``string|{string}`` "The pools this server belongs to (unset or empty string means default pool) as a string or table of strings"
+ ``retries`` ``number`` "The number of TCP connection attempts to the backend, for a given query"
+ ``tcpConnectTimeout`` ``number`` "The timeout (in seconds) of a TCP connection attempt"
+ ``tcpSendTimeout`` ``number`` "The timeout (in seconds) of a TCP write attempt"
+ ``tcpRecvTimeout`` ``number`` "The timeout (in seconds) of a TCP read attempt"
+ ``tcpFastOpen`` ``bool`` "Whether to enable TCP Fast Open"
+ ``ipBindAddrNoPort`` ``bool`` "Whether to enable IP_BIND_ADDRESS_NO_PORT if available, default: true"
+ ``name`` ``string`` "The name associated to this backend, for display purpose"
+ ``checkClass`` ``number`` "Use ``number`` as QCLASS in the health-check query, default: DNSClass.IN"
+ ``checkName`` ``string`` "Use ``string`` as QNAME in the health-check query, default: ``""a.root-servers.net.""`` "
+ ``checkType`` ``string`` "Use ``string`` as QTYPE in the health-check query, default: ``""A""`` "
+ ``checkFunction`` ``function`` "Use this function to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`)"
+ ``checkTimeout`` ``number`` "The timeout (in milliseconds) of a health-check query, default: 1000 (1s)"
+ ``setCD`` ``bool`` "Set the CD (Checking Disabled) flag in the health-check query, default: false"
+ ``maxCheckFailures`` ``number`` "Allow ``number`` check failures before declaring the backend down, default: 1"
+ ``checkInterval`` ``number`` "The time in seconds between health checks"
+ ``mustResolve`` ``bool`` "Set to true when the health check MUST return a RCODE different from NXDomain, ServFail and Refused. Default is false, meaning that every RCODE except ServFail is considered valid"
+ ``useClientSubnet`` ``bool`` "Add the client's IP address in the EDNS Client Subnet option when forwarding the query to this backend"
+ ``source`` ``string`` "The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection.
+ The following formats are supported:
+
+ - address, e.g. ``""192.0.2.2""``
+ - interface name, e.g. ``""eth0""``
+ - address@interface, e.g. ``""192.0.2.2@eth0""`` "
+ ``addXPF`` ``number`` "Add the client's IP address and port to the query, along with the original destination address and port, using the experimental XPF record from `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ and the specified option code. Default is disabled (0). This is a deprecated feature that will be removed in the near future."
+ ``sockets`` ``number`` "Number of UDP sockets (and thus source ports) used toward the backend server, defaults to a single one. Note that for backends which are multithreaded, this setting will have an effect on the number of cores that will be used to process traffic from dnsdist. For example you may want to set 'sockets' to a number somewhat higher than the number of worker threads configured in the backend, particularly if the Linux kernel is being used to distribute traffic to multiple threads listening on the same socket (via `reuseport`). See also :func:`setRandomizedOutgoingSockets`."
+ ``disableZeroScope`` ``bool`` "Disable the EDNS Client Subnet 'zero scope' feature, which does a cache lookup for an answer valid for all subnets (ECS scope of 0) before adding ECS information to the query and doing the regular lookup. This requires the ``parseECS`` option of the corresponding cache to be set to true"
+ ``rise`` ``number`` "Require ``number`` consecutive successful checks before declaring the backend up, default: 1"
+ ``useProxyProtocol`` ``bool`` "Add a proxy protocol header to the query, passing along the client's IP address and port along with the original destination address and port. Default is disabled."
+ ``reconnectOnUp`` ``bool`` "Close and reopen the sockets when a server transits from Down to Up. This helps when an interface is missing when dnsdist is started. Default is disabled."
+ ``maxInFlight`` ``number`` "Maximum number of in-flight queries. The default is 0, which disables out-of-order processing. It should only be enabled if the backend does support out-of-order processing. As of 1.6.0, out-of-order processing needs to be enabled on the frontend as well, via :func:`addLocal` and/or :func:`addTLSLocal`. Note that out-of-order is always enabled on DoH frontends."
+ ``tcpOnly`` ``bool`` "Always forward queries to that backend over TCP, never over UDP. Always enabled for TLS backends. Default is false."
+ ``checkTCP`` ``bool`` "Whether to do healthcheck queries over TCP, instead of UDP. Always enabled for DNS over TLS backend. Default is false."
+ ``tls`` ``string`` "Enable DNS over TLS communications for this backend, or DNS over HTTPS if ``dohPath`` is set, using the TLS provider (``""openssl""`` or ``""gnutls""``) passed in parameter. Default is an empty string, which means this backend is used for plain UDP and TCP."
+ ``caStore`` ``string`` "Specifies the path to the CA certificate file, in PEM format, to use to check the certificate presented by the backend. Default is an empty string, which means to use the system CA store. Note that this directive is only used if ``validateCertificates`` is set."
+ ``ciphers`` ``string`` "The TLS ciphers to use. The exact format depends on the provider used. When the OpenSSL provider is used, ciphers for TLS 1.3 must be specified via ``ciphersTLS13``."
+ ``ciphersTLS13`` ``string`` "The ciphers to use for TLS 1.3, when the OpenSSL provider is used. When the GnuTLS provider is used, ``ciphers`` applies regardless of the TLS protocol and this setting is not used."
+ ``subjectName`` ``string`` "The subject name passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty. If set this value supersedes any ``subjectAddr`` one."
+ ``subjectAddr`` ``string`` "The subject IP address passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty."
+ ``validateCertificates`` ``bool`` "Whether the certificate presented by the backend should be validated against the CA store (see ``caStore``). Default is true."
+ ``dohPath`` ``string`` "Enable DNS over HTTPS communication for this backend, using POST queries to the HTTP host supplied as ``subjectName`` and the HTTP path supplied in this parameter."
+ ``addXForwardedHeaders`` ``bool`` "Whether to add X-Forwarded-For, X-Forwarded-Port and X-Forwarded-Proto headers to a DNS over HTTPS backend."
+ ``releaseBuffers`` ``bool`` "Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection. Default to true."
+ ``enableRenegotiation`` ``bool`` "Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS."
+ ``autoUpgrade`` ``bool`` "Whether to use the 'Discovery of Designated Resolvers' mechanism to automatically upgrade a Do53 backend to DoT or DoH, depending on the priorities present in the SVCB record returned by the backend. Default to false."
+ ``autoUpgradeInterval`` ``number`` "If ``autoUpgrade`` is set, how often to check if an upgrade is available, in seconds. Default is 3600 seconds."
+ ``autoUpgradeKeep`` ``bool`` "If ``autoUpgrade`` is set, whether to keep the existing Do53 backend around after an upgrade. Default is false which means the Do53 backend will be replaced by the upgraded one."
+ ``autoUpgradePool`` ``string`` "If ``autoUpgrade`` is set, in which pool to place the newly upgraded backend. Default is empty which means the backend is placed in the default pool."
+ ``autoUpgradeDoHKey`` ``number`` "If ``autoUpgrade`` is set, the value to use for the SVC key corresponding to the DoH path. Default is 7."
+ ``maxConcurrentTCPConnections`` ``number`` "Maximum number of TCP connections to that backend. When that limit is reached, queries routed to that backend that cannot be forwarded over an existing connection will be dropped. Default is 0 which means no limit."
+ ``healthCheckMode`` ``string`` "The health-check mode to use: 'auto' which sends health-check queries every ``checkInterval`` seconds, 'up' which considers that the backend is always available, 'down' that it is always not available, and 'lazy' which only sends health-check queries after a configurable amount of regular queries have failed (see ``lazyHealthCheckSampleSize``, ``lazyHealthCheckMinSampleCount``, ``lazyHealthCheckThreshold``, ``lazyHealthCheckFailedInterval`` and ``lazyHealthCheckMode`` for more information). Default is 'auto'. See :ref:`Healthcheck` for a more detailed explanation."
+ ``lazyHealthCheckFailedInterval`` ``number`` "The interval, in seconds, between health-check queries in 'lazy' mode. Note that when ``lazyHealthCheckUseExponentialBackOff`` is set to true, the interval doubles between every queries. These queries are only sent when a threshold of failing regular queries has been reached, and until the backend is available again. Default is 30 seconds."
+ ``lazyHealthCheckMinSampleCount`` ``number`` "The minimum amount of regular queries that should have been recorded before the ``lazyHealthCheckThreshold`` threshold can be applied. Default is 1 which means only one query is needed."
+ ``lazyHealthCheckMode`` ``string`` "The 'lazy' health-check mode: 'TimeoutOnly' means that only timeout and I/O errors of regular queries will be considered for the ``lazyHealthCheckThreshold``, while 'TimeoutOrServFail' will also consider 'Server Failure' answers. Default is 'TimeoutOrServFail'."
+ ``lazyHealthCheckSampleSize`` ``number`` "The maximum size of the sample of queries to record and consider for the ``lazyHealthCheckThreshold``. Default is 100, which means the result (failure or success) of the last 100 queries will be considered."
+ ``lazyHealthCheckThreshold`` ``number`` "The threshold, as a percentage, of queries that should fail for the 'lazy' health-check to be triggered when ``healthCheckMode`` is set to ``lazy``. The default is 20 which means 20% of the last ``lazyHealthCheckSampleSize`` queries should fail for a health-check to be triggered."
+ ``lazyHealthCheckUseExponentialBackOff`` ``bool`` "Whether the 'lazy' health-check should use an exponential back-off instead of a fixed value, between health-check probes. The default is false which means that after a backend has been moved to the 'down' state health-check probes are sent every ``lazyHealthCheckFailedInterval`` seconds. When set to true, the delay between each probe starts at ``lazyHealthCheckFailedInterval`` seconds and double between every probe, capped at ``lazyHealthCheckMaxBackOff`` seconds."
+ ``lazyHealthCheckMaxBackOff`` ``number`` "This value, in seconds, caps the time between two health-check queries when ``lazyHealthCheckUseExponentialBackOff`` is set to true. The default is 3600 which means that at most one hour will pass between two health-check queries."
+ ``lazyHealthCheckWhenUpgraded`` ``bool`` "Whether the auto-upgraded version of this backend (see ``autoUpgrade``) should use the lazy health-checking mode. Default is false, which means it will use the regular health-checking mode."
+ ``ktls`` ``bool`` "Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false. Currently both DoT and DoH backend support this option."
+ ``proxyProtocolAdvertiseTLS`` ``bool`` "Whether to set the SSL Proxy Protocol TLV in the proxy protocol payload sent to the backend if the query was received over an encrypted channel (DNSCrypt, DoQ, DoH or DoT). Requires ``useProxyProtocol=true``. Default is false."
+ ``xskSockets`` ``array`` "An array of :class:`XskSocket` objects to enable ``XSK`` / ``AF_XDP`` support for this backend. See :doc:`../advanced/xsk` for more information."
+ ``MACAddr`` ``str`` "When the ``xskSocket`` option is set, this parameter can be used to specify the destination MAC address to use to reach the backend. If this options is not specified, dnsdist will try to get it from the IP of the backend by looking into the system's MAC address table, but it will fail if the corresponding MAC address is not present."
.. function:: getServer(index) -> Server
.. versionchanged:: 1.7.0
``skipOptions`` parameter added.
+ .. versionchanged:: 1.9.0
+ ``maximumEntrySize`` parameter added.
+
Creates a new :class:`PacketCache` with the settings specified.
:param int maxEntries: The maximum number of entries in this cache
* ``temporaryFailureTTL=60``: int - On a SERVFAIL or REFUSED from the backend, cache for this amount of seconds..
* ``cookieHashing=false``: bool - If true, EDNS Cookie values will be hashed, resulting in separate entries for different cookies in the packet cache. This is required if the backend is sending answers with EDNS Cookies, otherwise a client might receive an answer with the wrong cookie.
* ``skipOptions={}``: Extra list of EDNS option codes to skip when hashing the packet (if ``cookieHashing`` above is false, EDNS cookie option number will be added to this list internally).
+ * ``maximumEntrySize=4096``: int - The maximum size, in bytes, of a DNS packet that can be inserted into the packet cache. Default is 4096 bytes, which was the fixed size before 1.9.0, and is also a hard limit for UDP responses.
.. class:: PacketCache
.. versionadded:: 1.4.0
- Return the DOHFrontend object for the DNS over HTTPS bind of index ``idx``.
+ Return the :class:`DOHFrontend` object for the DNS over HTTPS bind of index ``idx``.
.. function:: getDOHFrontendCount()
.. versionadded:: 1.5.0
- Return the number of DOHFrontend binds.
+ Return the number of :class:`DOHFrontend` binds.
+
+.. function:: getDOH3Frontend(idx)
+
+ .. versionadded:: 1.9.0
+
+ Return the :class:`DOH3Frontend` object for the DNS over HTTP3 bind of index ``idx``.
+
+.. function:: getDOH3FrontendCount()
+
+ .. versionadded:: 1.9.0
+
+ Return the number of :class:`DOH3Frontend` binds.
+
+.. function:: getDOQFrontend(idx)
+
+ .. versionadded:: 1.9.0
+
+ Return the :class:`DOQFrontend` object for the DNS over QUIC bind of index ``idx``.
+
+.. function:: getDOQFrontendCount()
+
+ .. versionadded:: 1.9.0
+
+ Return the number of :class:`DOQFrontend` binds.
.. function:: getListOfAddressesOfNetworkInterface(itf)
:param str selector: Select queries based on this property.
:param {str} selectors: A lua table of selectors. Only queries matching all selectors are shown
- :param int num: Show a maximum of ``num`` recent queries+responses, default is 10.
+ :param int num: Show a maximum of ``num`` recent queries+responses.
:param table options: A table with key: value pairs with options described below.
Options:
* ``outputFile=path``: string - Write the output of the command to the supplied file, instead of the standard output.
+.. function:: setStructuredLogging(enable[, options])
+
+ .. versionadded:: 1.9.0
+
+ Set whether log messages should be in a structured-logging-like format. This is turned off by default.
+ The resulting format looks like this (when timestamps are enabled via ``--log-timestamps`` and with ``levelPrefix="prio"`` and ``timeFormat="ISO8601"``)::
+
+ ts="2023-11-06T12:04:58+0100" prio="Info" msg="Added downstream server 127.0.0.1:53"
+
+ And with ``levelPrefix="level"`` and ``timeFormat="numeric"``)::
+
+ ts="1699268815.133" level="Info" msg="Added downstream server 127.0.0.1:53"
+
+ :param bool enable: Set to true if you want to enable structured logging
+ :param table options: A table with key: value pairs with options described below.
+
+ Options:
+
+ * ``levelPrefix=prefix``: string - Set the prefix for the log level. Default is ``prio``.
+ * ``timeFormat=format``: string - Set the time format. Supported values are ``ISO8601`` and ``numeric``. Default is ``numeric``.
+
.. function:: setVerbose(verbose)
.. versionadded:: 1.8.0
Print the list of all available DNS over HTTPS frontends.
+.. function:: showDOH3Frontends()
+
+ .. versionadded:: 1.9.0
+
+ Print the list of all available DNS over HTTP/3 frontends.
+
.. function:: showDOHResponseCodes()
.. versionadded:: 1.4.0
Print the HTTP response codes statistics for all available DNS over HTTPS frontends.
+.. function:: showDOQFrontends()
+
+ .. versionadded:: 1.9.0
+
+ Print the list of all available DNS over QUIC frontends.
+
.. function:: showResponseLatency()
Show a plot of the response time latency distribution
Dynamic Blocks
--------------
+.. function:: addDynamicBlock(address, message[, action [, seconds [, clientIPMask [, clientIPPortMask]]]])
+
+ .. versionadded:: 1.9.0
+
+ Manually block an IP address or range with ``message`` for (optionally) a number of seconds.
+ The default number of seconds to block for is 10.
+
+ :param address: A :class:`ComboAddress` or string representing an IPv4 or IPv6 address
+ :param string message: The message to show next to the blocks
+ :param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <DNSAction>`. (default to DNSAction.None, meaning the one set with :func:`setDynBlocksAction` is used)
+ :param int seconds: The number of seconds this block to expire
+ :param int clientIPMask: The network mask to apply to the address. Default is 32 for IPv4, 128 for IPv6.
+ :param int clientIPPortMask: The port mask to use to specify a range of ports to match, when the clients are behind a CG-NAT.
+
+ Please see the documentation for :func:`setDynBlocksAction` to confirm which actions are supported by the action paramater.
+
.. function:: addDynBlocks(addresses, message[, seconds=10[, action]])
Block a set of addresses with ``message`` for (optionally) a number of seconds.
The default number of seconds to block for is 10.
+ Since 1.3.0, the use of a :ref:`DynBlockRulesGroup` is a much more efficient way of doing the same thing.
:param addresses: set of Addresses as returned by an exceed function
:param string message: The message to show next to the blocks
Remove all current dynamic blocks.
+.. function:: getDynamicBlocks()
+
+ .. versionadded:: 1.9.0
+
+ Return an associative table of active network-based dynamic blocks. The keys are the network IP or range that are blocked, the value are :class:`DynBlock` objects.
+
+.. function:: getDynamicBlocksSMT()
+
+ .. versionadded:: 1.9.0
+
+ Return an associative table of active domain-based (Suffix Match Tree or SMT) dynamic blocks. The keys are the domains that are blocked, the values are :class:`DynBlock` objects.
+
.. function:: showDynBlocks()
List all dynamic blocks in effect.
:param int sec: The interval between two runs of the cleaning algorithm, in seconds. Default is 60 (1 minute), 0 means disabled.
+.. class:: DynBlock
+
+ .. versionadded:: 1.9.0
+
+ Represent the current state of a dynamic block.
+
+ .. attribute:: DynBlock.action
+
+ The action of this block, as an integer representing a :ref:`DNSAction <DNSAction>`.
+
+ .. attribute:: DynBlock.blocks
+
+ The number of queries blocked.
+
+ .. attribute:: DynBlock.bpf
+
+ Whether this block is using eBPF, as a boolean.
+
+ .. attribute:: DynBlock.domain
+
+ The domain that is blocked, as a string, for Suffix Match Tree blocks.
+
+ .. attribute:: DynBlock.reason
+
+ The reason why this block was inserted, as a string.
+
+ .. attribute:: DynBlock.until
+
+ The time (in seconds since Epoch) at which the block will expire.
+
+ .. attribute:: DynBlock.warning
+
+ Whether this block is only a warning one (true) or is really enforced (false).
+
.. _exceedfuncs:
Getting addresses that exceeded parameters
Represents a group of dynamic block rules.
+ .. method:: DynBlockRulesGroup:setCacheMissRatio(ratio, seconds, reason, blockingTime, minimumNumberOfResponses, minimumGlobalCacheHitRatio, [, action [, warningRate]])
+
+ .. versionadded:: 1.9.0
+
+ Adds a rate-limiting rule for the ratio of cache-misses responses over the total number of responses for a given client.
+ A minimum global cache-hit ratio has to specified to prevent false-positive when the cache is empty.
+
+ :param float ratio: Ratio of cache-miss responses per second over the total number of responses for this client to exceed
+ :param int seconds: Number of seconds the ratio has been exceeded
+ :param string reason: The message to show next to the blocks
+ :param int blockingTime: The number of seconds this block to expire
+ :param int minimumNumberOfResponses: How many total responses is required for this rule to apply
+ :param float minimumGlobalCacheHitRatio: The minimum global cache-hit ratio (over all pools, so ``cache-hits / (cache-hits + cache-misses)``) for that rule to be applied.
+ :param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+ :param float warningRatio: If set to a non-zero value, the ratio above which a warning message will be issued and a no-op block inserted
+
.. method:: DynBlockRulesGroup:setMasks(v4, v6, port)
.. versionadded:: 1.7.0
:param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
:param int warningRate: If set to a non-zero value, the rate above which a warning message will be issued and a no-op block inserted
+ .. method:: DynBlockRulesGroup:setNewBlockInsertedHook(hook)
+
+ .. versionadded:: 1.9.0
+
+ Set a Lua function that will be called everytime a new dynamic block is inserted. The function receives:
+
+ * an integer whose value is 0 if the block is Netmask-based one (Client IP or range) and 1 instead (Domain name suffix)
+ * the key (Client IP/range or domain suffix) as a string
+ * the reason of the block as a string
+ * the action of the block as an integer
+ * the duration of the block in seconds
+ * whether this is a warning block (true) or not (false)
+
.. method:: DynBlockRulesGroup:setRCodeRate(rcode, rate, seconds, reason, blockingTime [, action [, warningRate]])
+ .. note::
+ Cache hits are inserted into the in-memory ring buffers since 1.8.0, so they are now considered when computing the rcode rate.
+
Adds a rate-limiting rule for responses of code ``rcode``, equivalent to:
```
addDynBlocks(exceedServfails(rcode, rate, seconds), reason, blockingTime, action)
.. versionadded:: 1.5.0
+ .. note::
+ Cache hits are inserted into the in-memory ring buffers since 1.8.0, so they are now considered when computing the rcode ratio.
+
Adds a rate-limiting rule for the ratio of responses of code ``rcode`` over the total number of responses for a given client.
:param int rcode: The response code
- :param int ratio: Ratio of responses per second of the given rcode over the total number of responses for this client to exceed
+ :param float ratio: Ratio of responses per second of the given rcode over the total number of responses for this client to exceed
:param int seconds: Number of seconds the ratio has been exceeded
:param string reason: The message to show next to the blocks
:param int blockingTime: The number of seconds this block to expire
:param int minimumNumberOfResponses: How many total responses is required for this rule to apply
:param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
- :param int warningRatio: If set to a non-zero value, the ratio above which a warning message will be issued and a no-op block inserted
+ :param float warningRatio: If set to a non-zero value, the ratio above which a warning message will be issued and a no-op block inserted
.. method:: DynBlockRulesGroup:setQTypeRate(qtype, rate, seconds, reason, blockingTime [, action [, warningRate]])
.. method:: DynBlockRulesGroup:setResponseByteRate(rate, seconds, reason, blockingTime [, action [, warningRate]])
+ .. note::
+ Cache hits are inserted into the in-memory ring buffers since 1.8.0, so they are now considered when computing the bandwidth rate.
+
Adds a bandwidth rate-limiting rule for responses, equivalent to:
```
addDynBlocks(exceedRespByterate(rate, seconds), reason, blockingTime, action)
.. versionchanged:: 1.7.0
This visitor function can now optionally return an additional string which will be set as the ``reason`` for the dynamic block.
- Set a Lua visitor function that will be called for each label of every domain seen in queries and responses. The function receives a `StatNode` object representing the stats of the parent, a second one with the stats of the current label and one with the stats of the current node plus all its children.
+ .. versionchanged:: 1.9.0
+ This visitor function can now optionally return an additional integer which will be set as the ``action`` for the dynamic block.
+
+ Set a Lua visitor function that will be called for each label of every domain seen in queries and responses. The function receives a :class:`StatNode` object representing the stats of the parent, a :class:`StatNodeStats` one with the stats of the current label and a second :class:`StatNodeStats` with the stats of the current node plus all its children.
Note that this function will not be called if a FFI version has been set using :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`
- If the function returns true, the current label will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters. Since 1.7.0, the function can return an additional string, in addition to the boolean, which will be set as the ``reason`` for the dynamic block.
+ If the function returns ``true``, the current suffix will be added to the block list, meaning that the exact name and all its sub-domains will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters. Since 1.7.0, the function can return an additional string, in addition to the boolean, which will be set as the ``reason`` for the dynamic block.
Selected domains can be excluded from this processing using the :meth:`DynBlockRulesGroup:excludeDomains` method.
This replaces the existing :func:`addDynBlockSMT` function.
.. versionadded:: 1.4.0
Set a Lua FFI visitor function that will be called for each label of every domain seen in queries and responses. The function receives a `dnsdist_ffi_stat_node_t` object containing the stats of the parent, a second one with the stats of the current label and one with the stats of the current node plus all its children.
- If the function returns true, the current label will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters.
+ If the function returns ``true``, the current suffix will be added to the block list, meaning that the exact name and all its sub-domains will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters.
Selected domains can be excluded from this processing using the :meth:`DynBlockRulesGroup:excludeDomains` method.
:param int seconds: Number of seconds the rate has been exceeded
:param list netmasks: A :class:`NetmaskGroup` object, or a netmask or list of netmasks as strings, like for example "192.0.2.1/24"
+ .. method:: DynBlockRulesGroup:removeRange(netmasks)
+
+ .. versionadded:: 1.8.3
+
+ Remove a previously included or excluded range. The range should be an exact match of the existing entry to remove.
+
+ :param list netmasks: A :class:`NetmaskGroup` object, or a netmask or list of netmasks as strings, like for example "192.0.2.1/24"
+
.. method:: DynBlockRulesGroup:toString()
Return a string describing the rules and range exclusions of this DynBlockRulesGroup.
.. class:: StatNode
- Represent metrics about a given node, for the visitor functions used with :meth:`DynBlockRulesGroup:setSuffixMatchRule` and :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`. Note that some nodes includes the metrics for their children as well as their own.
+ Represent a given node, for the visitor functions used with :meth:`DynBlockRulesGroup:setSuffixMatchRule` and :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`.
- .. attribute:: StatNode.bytes
+ .. attribute:: StatNode.fullname
- The number of bytes for all responses returned for that node.
+ The complete name of that node, ie 'www.powerdns.com.'.
- .. attribute:: StatNode.drops
+ .. attribute:: StatNode.labelsCount
- The number of drops for that node.
+ The number of labels in that node, for example 3 for 'www.powerdns.com.'.
- .. attribute:: StatNode.fullname
+ .. method:: StatNode:numChildren
+
+ The number of children of that node.
- The complete name of that node, ie 'www.powerdns.com'.
+.. class:: StatNodeStats
- .. attribute:: StatNode.labelsCount
+ Represent the metrics for a given node, for the visitor functions used with :meth:`DynBlockRulesGroup:setSuffixMatchRule` and :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`.
- The number of labels in that node, for example 3 for 'www.powerdns.com'.
+ .. attribute:: StatNodeStats.bytes
- .. attribute:: StatNode.noerrors
+ The number of bytes for all responses returned for that node.
+
+ .. attribute:: StatNodeStats.drops
+
+ The number of drops for that node.
+
+ .. attribute:: StatNodeStats.noerrors
The number of No Error answers returned for that node.
- .. attribute:: StatNode.nxdomains
+ .. attribute:: StatNodeStats.hits
+
+ .. versionadded:: 1.8.0
+
+ The number of cache hits for that node.
+
+ .. attribute:: StatNodeStats.nxdomains
The number of NXDomain answers returned for that node.
- .. attribute:: StatNode.queries
+ .. attribute:: StatNodeStats.queries
The number of queries for that node.
- .. attribute:: StatNode.servfails
+ .. attribute:: StatNodeStats.servfails
The number of Server Failure answers returned for that node.
- .. method:: StatNode:numChildren
-
- The number of children of that node.
-
SuffixMatchNode
~~~~~~~~~~~~~~~
Other functions
---------------
+.. function:: addMaintenanceCallback(callback)
+
+ .. versionadded:: 1.10.0
+
+ Register a Lua function to be called as part of the ``maintenance`` hook, which is executed roughly every second.
+ The function should not block for a long period of time, as it would otherwise delay the execution of the other functions registered for this hook, as well as the execution of the :func:`maintenance` function.
+
+ :param function callback: The function to be called. It takes no parameter and returns no value.
+
+ .. code-block:: lua
+
+ function myCallback(hostname, ips)
+ print('called')
+ end
+ addMaintenanceCallback(myCallback)
+
+
+.. function:: getAddressInfo(hostname, callback)
+
+ .. versionadded:: 1.9.0
+
+ Asynchronously resolve, via the system resolver (using ``getaddrinfo()``), the supplied ``hostname`` to IPv4 and IPv6 addresses (if configured on the host) before invoking the supplied ``callback`` function with the ``hostname`` and a list of IPv4 and IPv6 addresses as :class:`ComboAddress`.
+ For example, to get the addresses of Quad9's resolver and dynamically add them as backends:
+
+ .. code-block:: lua
+
+ function resolveCB(hostname, ips)
+ for _, ip in ipairs(ips) do
+ newServer(ip:toString())
+ end
+ end
+ getAddressInfo('dns.quad9.net.', resolveCB)
+
+ :param str hostname: The hostname to resolve.
+ :param function callback: The function to invoke when the name has been resolved.
+
.. function:: getCurrentTime -> timespec
.. versionadded:: 1.8.0
:param str path: The path to the file, usually /etc/resolv.conf
+.. function:: getStatisticsCounters()
+
+ This function returns a Lua associative array of metrics, with the metric name as key and the current value
+ of the counter as value.
+
.. function:: maintenance()
If this function exists, it is called every second to do regular tasks.
This can be used for e.g. :doc:`Dynamic Blocks <../guides/dynblocks>`.
+ See also :func:`addMaintenanceCallback`.
.. function:: threadmessage(cmd, dict)
.. versionadded:: 1.8.0
- Creates a TLSCertificate object suited to be used with functions like :func:`addDOHLocal` and :func:`addTLSLocal` for TLS certificate configuration.
+ Creates a :class:`TLSCertificate` object suited to be used with functions like :func:`addDOHLocal`, :func:`addDOH3Local`, :func:`addDOQLocal` and :func:`addTLSLocal` for TLS certificate configuration.
- PKCS12 files are only supported by the ``openssl`` provider, password-protected or not.
+ ``PKCS12`` files are only supported by the ``openssl`` provider, password-protected or not.
- :param string pathToCert: Path to a file containing the certificate or a PKCS12 file containing both a certificate and a key.
+ :param string pathToCert: Path to a file containing the certificate or a ``PKCS12`` file containing both a certificate and a key.
:param table options: A table with key: value pairs with additional options.
Options:
* ``key="path/to/key"``: string - Path to a file containing the key corresponding to the certificate.
- * ``password="pass"``: string - Password protecting the PKCS12 file if appropriate.
+ * ``password="pass"``: string - Password protecting the ``PKCS12`` file if appropriate.
.. code-block:: lua
newTLSCertificate("path/to/pub.crt", {key="path/to/private.pem"})
- newTLSCertificate("path/to/domain.p12", {password="passphrase"}) -- use a password protected PKCS12 file
+ newTLSCertificate("path/to/domain.p12", {password="passphrase"}) -- use a password protected ``PKCS12`` file
DOHFrontend
~~~~~~~~~~~
:param str content: The content of the HTTP response, or a URL if the status is a redirection (3xx).
:param table of headers: The custom headers to set for the HTTP response, if any. The default is to use the value of the ``customResponseHeaders`` parameter passed to :func:`addDOHLocal`.
+DOH3Frontend
+~~~~~~~~~~~~
+
+.. class:: DOH3Frontend
+
+ .. versionadded:: 1.9.0
+
+ This object represents an address and port dnsdist is listening on for DNS over HTTP3 queries.
+
+ .. method:: DOH3Frontend:reloadCertificates()
+
+ Reload the current TLS certificate and key pairs.
+
+DOQFrontend
+~~~~~~~~~~~
+
+.. class:: DOQFrontend
+
+ .. versionadded:: 1.9.0
+
+ This object represents an address and port dnsdist is listening on for DNS over QUIC queries.
+
+ .. method:: DOQFrontend:reloadCertificates()
+
+ Reload the current TLS certificate and key pairs.
+
LuaRingEntry
~~~~~~~~~~~~
Number of remaining nanoseconds elapsed since Unix epoch after subtracting the seconds from the `tv_sec` field.
+TLSCertificate
+~~~~~~~~~~~~~~
+
+.. class:: TLSCertificate
+
+ This object represents a TLS certificate. It can be created with :func:`newTLSCertificate` and used with :func:`addDOHLocal`, :func:`addDOH3Local`, :func:`addDOQLocal` and :func:`addTLSLocal` for TLS certificate configuration. It is mostly useful to deal with password-protected ``PKCS12`` certificates.
+
TLSContext
~~~~~~~~~~
DNSResponseAction
-----------------
+.. versionchanged:: 1.9.0
+ The ``DNSResponseAction.Truncate`` value was added.
+
These constants represent an Action that can be returned from :func:`LuaResponseAction` functions.
* ``DNSResponseAction.Allow``: let the response pass, skipping other rules
* ``DNSResponseAction.HeaderModify``: indicate that the query has been turned into a response
* ``DNSResponseAction.None``: continue to the next rule
* ``DNSResponseAction.ServFail``: return a response with a ServFail rcode
+ * ``DNSResponseAction.Truncate``: truncate the response, removing all records from the answer, authority and additional sections if any
Custom Metrics
=====================================
-You can define at configuration time your own metrics that can be updated using Lua.
+You can define your own metrics that can be updated using Lua.
-The first step is to declare a new metric using :func:`declareMetric`.
+The first step is to declare a new metric using :func:`declareMetric`. In 1.8.0 the declaration had to be done at configuration time, but since 1.8.1 it can be done at any point.
Then you can update those at runtime using the following functions, depending on the metric type:
.. versionadded:: 1.8.0
+ .. versionchanged:: 1.8.1
+ This function can now be used at runtime, instead of only at configuration time.
+
Return true if declaration was successful
:param str name: The name of the metric, lowercase alphanumerical characters and dashes (-) only
:param str name: The description of the metric
:param str prometheusName: The name to use in the prometheus metrics, if supplied. Otherwise the regular name will be used, prefixed with ``dnsdist_`` and ``-`` replaced by ``_``.
-.. function:: incMetric(name) -> int
+.. function:: incMetric(name [, step]) -> int
.. versionadded:: 1.8.0
- Increment counter by one, will issue an error if the metric is not declared or not a ``counter``
+ .. versionchanged:: 1.8.1
+ Optional ``step`` parameter added.
+
+ Increment counter by one (or more, see the ``step`` parameter), will issue an error if the metric is not declared or not a ``counter``
Return the new value
:param str name: The name of the metric
+ :param int step: By how much the counter should be incremented, default to 1.
.. function:: decMetric(name) -> int
.. versionadded:: 1.8.0
- Decrement counter by one, will issue an error if the metric is not declared or not a ``counter``
+ .. versionchanged:: 1.8.1
+ Optional ``step`` parameter added.
+
+ Decrement counter by one (or more, see the ``step`` parameter), will issue an error if the metric is not declared or not a ``counter``
Return the new value
:param str name: The name of the metric
+ :param int step: By how much the counter should be decremented, default to 1.
.. function:: getMetric(name) -> double
Return the new value
:param str name: The name of the metric
+ :param double value: The new value
:param int code: The EDNS option code
:param string data: The EDNS option raw data
+ .. method:: DNSQuestion:setExtendedDNSError(infoCode [, extraText])
+
+ .. versionadded:: 1.9.0
+
+ Set an Extended DNS Error status that will be added to the response corresponding to the current query.
+
+ :param int infoCode: The EDNS Extended DNS Error code
+ :param string extraText: The optional EDNS Extended DNS Error extra text
+
.. method:: DNSQuestion:setHTTPResponse(status, body, contentType="")
.. versionadded:: 1.4.0
:param string tail: The new data
:returns: true if the operation succeeded, false otherwise
- .. method:: DNSQuestion:spoof(ip|ips|raw|raws)
+ .. method:: DNSQuestion:spoof(ip|ips|raw|raws [, typeForAny])
.. versionadded:: 1.6.0
+ .. versionchanged:: 1.9.0
+ Optional parameter ``typeForAny`` added.
+
Forge a response with the specified record data as raw bytes. If you specify list of raws (it is assumed they match the query type), all will get spoofed in.
:param ComboAddress ip: The `ComboAddress` to be spoofed, e.g. `newCA("192.0.2.1")`.
:param table ComboAddresses ips: The `ComboAddress`es to be spoofed, e.g. `{ newCA("192.0.2.1"), newCA("192.0.2.2") }`.
:param string raw: The raw string to be spoofed, e.g. `"\\192\\000\\002\\001"`.
:param table raws: The raw strings to be spoofed, e.g. `{ "\\192\\000\\002\\001", "\\192\\000\\002\\002" }`.
+ :param int typeForAny: The type to use for raw responses when the requested type is ``ANY``, as using ``ANY`` for the type of the response record would not make sense.
.. method:: DNSQuestion:suspend(asyncID, queryID, timeoutMS) -> bool
- ``useECS``
If the value is really needed while the response is being processed, it is possible to set a tag while the query is processed, as tags will be passed to the response object.
- It also has one additional method:
+ It also has additional methods:
+
+ .. method:: DNSResponse.getSelectedBackend() -> Server
+
+ .. versionadded:: 1.9.0
+
+ Get the selected backend :class:`Server` or nil
.. method:: DNSResponse:editTTLs(func)
return DNSAction.None
end
function restartOnServFail(dr)
- if dr.rcode == DNSRCode.SERVFAIL then
+ -- if the query was SERVFAIL and not already tried on the restarted pool
+ if dr.rcode == DNSRCode.SERVFAIL and dr.pool ~= 'restarted' then
-- assign this query to a new pool
dr.pool = 'restarted'
-- discard the received response and
Get recursion desired flag.
+ .. method:: DNSHeader:getTC() -> bool
+
+ .. versionadded:: 1.8.1
+
+ Get the TC flag.
+
.. method:: DNSHeader:setAA(aa)
Set authoritative answer flag.
This is the eBPF equivalent of :func:`addDynBlocks`, blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter.
The default number of seconds to block for is 10.
+ Since 1.6.0, the use of a :ref:`DynBlockRulesGroup` is a much more efficient way of doing the same thing.
:param addresses: set of Addresses as returned by an :ref:`exceed function <exceedfuncs>`
:param DynBPFFilter dynbpf: The dynamic eBPF filter to use
.. toctree::
:maxdepth: 3
+ actions
config
constants
comboaddress
kvs
logging
web
+ rules-management
+ selectors
svc
custommetrics
+ xsk
.. versionadded:: 1.4.0
Return a new KeyValueStore object associated to the corresponding CDB database. The modification time
- of the CDB file will be checked every 'refrehDelay' second and the database re-opened if needed.
+ of the CDB file will be checked every 'refreshDelay' second and the database re-opened if needed.
:param string filename: The path to an existing CDB database
:param int refreshDelays: The delay in seconds between two checks of the database modification time. 0 means disabled
:param string mask: Add this mask, prefix with `!` to exclude this mask from matching.
:param table masks: Adds the keys of the table to the :class:`NetmaskGroup`. It should be a table whose keys are :class:`ComboAddress` objects and whose values are integers. The integer values of the table entries are ignored. The table is of the same type as the table returned by the `exceed*` functions.
+ .. method:: NetmaskGroup:addNMG(otherNMG)
+
+ .. versionadded:: 1.9.0
+
+ Add all masks from an existing NMG to this NMG.
+
+ :param NetmaskGroup otherNMG: Add the masks from a :class:`NetmaskGroup` to this one.
+
.. method:: NetmaskGroup:match(address) -> bool
Checks if ``address`` is matched by this NetmaskGroup.
--- /dev/null
+Rules management
+================
+
+Incoming queries
+----------------
+
+For Rules related to the incoming query:
+
+.. function:: addAction(DNSrule, action [, options])
+
+ .. versionchanged:: 1.6.0
+ Added ``name`` to the ``options``.
+
+ .. versionchanged:: 1.9.0
+ Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+ Add a Rule and Action to the existing rules.
+ If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+ :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+ :param action: The action to take
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+ * ``name``: string - Name to assign to the new rule.
+
+.. function:: clearRules()
+
+ Remove all current rules.
+
+.. function:: getAction(n) -> DNSDistRuleAction
+
+ Returns the :class:`DNSDistRuleAction` associated with rule ``n``.
+
+ :param int n: The rule number
+
+.. function:: getCacheHitResponseRule(selector) -> DNSDistResponseRuleAction
+
+ .. versionadded:: 1.9.0
+
+ Return the cache-hit response rule corresponding to the selector, if any.
+ The selector can be the position of the rule in the list, as an integer,
+ its name as a string or its UUID as a string as well.
+
+ :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: getCacheInsertedResponseRule(selector) -> DNSDistResponseRuleAction
+
+ .. versionadded:: 1.9.0
+
+ Return the cache-inserted response rule corresponding to the selector, if any.
+ The selector can be the position of the rule in the list, as an integer,
+ its name as a string or its UUID as a string as well.
+
+ :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: getResponseRule(selector) -> DNSDistResponseRuleAction
+
+ .. versionadded:: 1.9.0
+
+ Return the response rule corresponding to the selector, if any.
+ The selector can be the position of the rule in the list, as an integer,
+ its name as a string or its UUID as a string as well.
+
+ :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: getRule(selector) -> DNSDistRuleAction
+
+ .. versionadded:: 1.9.0
+
+ Return the rule corresponding to the selector, if any.
+ The selector can be the position of the rule in the list, as an integer,
+ its name as a string or its UUID as a string as well.
+
+ :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: getSelfAnsweredResponseRule(selector) -> DNSDistResponseRuleAction
+
+ .. versionadded:: 1.9.0
+
+ Return the self-answered response rule corresponding to the selector, if any.
+ The selector can be the position of the rule in the list, as an integer,
+ its name as a string or its UUID as a string as well.
+
+ :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: mvRule(from, to)
+
+ Move rule ``from`` to a position where it is in front of ``to``.
+ ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+ :param int from: Rule number to move
+ :param int to: Location to more the Rule to
+
+.. function:: mvRuleToTop()
+
+ .. versionadded:: 1.6.0
+
+ This function moves the last rule to the first position. Before 1.6.0 this was handled by :func:`topRule`.
+
+.. function:: newRuleAction(rule, action[, options])
+
+ .. versionchanged:: 1.6.0
+ Added ``name`` to the ``options``.
+
+ Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
+
+ :param Rule rule: A Rule (see :doc:`selectors`)
+ :param Action action: The Action (see :doc:`actions`) to apply to the matched traffic
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+ * ``name``: string - Name to assign to the new rule.
+
+.. function:: setRules(rules)
+
+ Replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see :func:`newRuleAction`)
+
+ :param [RuleAction] rules: A list of RuleActions
+
+.. function:: showRules([options])
+
+ Show all defined rules for queries, optionally displaying their UUIDs.
+
+ :param table options: A table with key: value pairs with display options.
+
+ Options:
+
+ * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+ * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+.. function:: topRule()
+
+ .. versionchanged:: 1.6.0
+ Replaced by :func:`mvRuleToTop`
+
+ Before 1.6.0 this function used to move the last rule to the first position, which is now handled by :func:`mvRuleToTop`.
+
+.. function:: rmRule(id)
+
+ .. versionchanged:: 1.6.0
+ ``id`` can now be a string representing the name of the rule.
+
+ Remove rule ``id``.
+
+ :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+Responses
+---------
+
+For Rules related to responses:
+
+.. function:: addResponseAction(DNSRule, action [, options])
+
+ .. versionchanged:: 1.6.0
+ Added ``name`` to the ``options``.
+
+ .. versionchanged:: 1.9.0
+ Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+ Add a Rule and Action for responses to the existing rules.
+ If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+ :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+ :param action: The action to take
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+ * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvResponseRule(from, to)
+
+ Move response rule ``from`` to a position where it is in front of ``to``.
+ ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+ :param int from: Rule number to move
+ :param int to: Location to more the Rule to
+
+.. function:: mvResponseRuleToTop()
+
+ .. versionadded:: 1.6.0
+
+ This function moves the last response rule to the first position. Before 1.6.0 this was handled by :func:`topResponseRule`.
+
+.. function:: rmResponseRule(id)
+
+ .. versionchanged:: 1.6.0
+ ``id`` can now be a string representing the name of the rule.
+
+ Remove response rule ``id``.
+
+ :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showResponseRules([options])
+
+ Show all defined response rules, optionally displaying their UUIDs.
+
+ :param table options: A table with key: value pairs with display options.
+
+ Options:
+
+ * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+ * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+.. function:: topResponseRule()
+
+ .. versionchanged:: 1.6.0
+ Replaced by :func:`mvResponseRuleToTop`
+
+ Before 1.6.0 this function used to move the last response rule to the first position, which is now handled by :func:`mvResponseRuleToTop`.
+
+Cache hits
+----------
+
+Functions for manipulating Cache Hit Response Rules:
+
+.. function:: addCacheHitResponseAction(DNSRule, action [, options])
+
+ .. versionchanged:: 1.6.0
+ Added ``name`` to the ``options``.
+
+ .. versionchanged:: 1.9.0
+ Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+ Add a Rule and ResponseAction for Cache Hits to the existing rules.
+ If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+ :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+ :param action: The action to take
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+ * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvCacheHitResponseRule(from, to)
+
+ Move cache hit response rule ``from`` to a position where it is in front of ``to``.
+ ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+ :param int from: Rule number to move
+ :param int to: Location to more the Rule to
+
+.. function:: mvCacheHitResponseRuleToTop()
+
+ .. versionadded:: 1.6.0
+
+ This function moves the last cache hit response rule to the first position. Before 1.6.0 this was handled by :func:`topCacheHitResponseRule`.
+
+.. function:: rmCacheHitResponseRule(id)
+
+ .. versionchanged:: 1.6.0
+ ``id`` can now be a string representing the name of the rule.
+
+ :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showCacheHitResponseRules([options])
+
+ Show all defined cache hit response rules, optionally displaying their UUIDs.
+
+ :param table options: A table with key: value pairs with display options.
+
+ Options:
+
+ * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+ * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+.. function:: topCacheHitResponseRule()
+
+ .. versionchanged:: 1.6.0
+ Replaced by :func:`mvCacheHitResponseRuleToTop`
+
+ Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvCacheHitResponseRuleToTop`.
+
+Cache inserted
+--------------
+
+Functions for manipulating Cache Inserted Response Rules:
+
+.. function:: addCacheInsertedResponseAction(DNSRule, action [, options])
+
+ .. versionadded:: 1.8.0
+
+ .. versionchanged:: 1.9.0
+ Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+ Add a Rule and ResponseAction that is executed after a cache entry has been inserted to the existing rules.
+ If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+ :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+ :param action: The action to take
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+ * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvCacheInsertedResponseRule(from, to)
+
+ .. versionadded:: 1.8.0
+
+ Move cache inserted response rule ``from`` to a position where it is in front of ``to``.
+ ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+ :param int from: Rule number to move
+ :param int to: Location to more the Rule to
+
+.. function:: mvCacheInsertedResponseRuleToTop()
+
+ .. versionadded:: 1.8.0
+
+ This function moves the last cache inserted response rule to the first position.
+
+.. function:: rmCacheInsertedResponseRule(id)
+
+ .. versionadded:: 1.8.0
+
+ :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showCacheInsertedResponseRules([options])
+
+ .. versionadded:: 1.8.0
+
+ Show all defined cache inserted response rules, optionally displaying their UUIDs.
+
+ :param table options: A table with key: value pairs with display options.
+
+ Options:
+
+ * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+ * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+Self-answered responses
+-----------------------
+
+Functions for manipulating Self-Answered Response Rules:
+
+.. function:: addSelfAnsweredResponseAction(DNSRule, action [, options])
+
+ .. versionchanged:: 1.6.0
+ Added ``name`` to the ``options``.
+
+ .. versionchanged:: 1.9.0
+ Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+ Add a Rule and Action for Self-Answered queries to the existing rules.
+ If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+ :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+ :param action: The action to take
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+ * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvSelfAnsweredResponseRule(from, to)
+
+ Move self answered response rule ``from`` to a position where it is in front of ``to``.
+ ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+ :param int from: Rule number to move
+ :param int to: Location to more the Rule to
+
+.. function:: mvSelfAnsweredResponseRuleToTop()
+
+ .. versionadded:: 1.6.0
+
+ This function moves the last self-answered response rule to the first position. Before 1.6.0 this was handled by :func:`topSelfAnsweredResponseRule`.
+
+.. function:: rmSelfAnsweredResponseRule(id)
+
+ .. versionchanged:: 1.6.0
+ ``id`` can now be a string representing the name of the rule.
+
+ Remove self answered response rule ``id``.
+
+ :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showSelfAnsweredResponseRules([options])
+
+ Show all defined self answered response rules, optionally displaying their UUIDs.
+
+ :param table options: A table with key: value pairs with display options.
+
+ Options:
+
+ * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+ * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+.. function:: topSelfAnsweredResponseRule()
+
+ .. versionchanged:: 1.6.0
+ Replaced by :func:`mvSelfAnsweredResponseRuleToTop`
+
+ Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvSelfAnsweredResponseRuleToTop`.
+
+ Move the last self answered response rule to the first position.
+
+Convenience Functions
+---------------------
+
+.. function:: makeRule(rule)
+
+ .. versionchanged:: 1.9.0
+ This function is deprecated, please use :func:`NetmaskGroupRule` or :func:`QnameSuffixRule` instead
+
+ Make a :func:`NetmaskGroupRule` or a :func:`SuffixMatchNodeRule`, depending on how it is called.
+ The `rule` parameter can be a string, or a list of strings, that should contain either:
+
+ * netmasks: in which case it will behave as :func:`NetmaskGroupRule`, or
+ * domain names: in which case it will behave as :func:`SuffixMatchNodeRule`
+
+ Mixing both netmasks and domain names is not supported, and will result in domain names being ignored!
+
+ ``makeRule("0.0.0.0/0")`` will for example match all IPv4 traffic, ``makeRule({"be","nl","lu"})`` will match all Benelux DNS traffic.
+
+ :param string rule: A string, or list of strings, to convert to a rule.
--- /dev/null
+Rule selectors
+==============
+
+Packets can be matched by selectors, called a ``DNSRule``.
+
+These ``DNSRule``\ s be one of the following items:
+
+ * A string that is either a domain name or netmask
+ * A list of strings that are either domain names or netmasks
+ * A :class:`DNSName`
+ * A list of :class:`DNSName`\ s
+ * A (compounded) ``Rule``
+
+Selectors can be combined via :func:`AndRule`, :func:`OrRule` and :func:`NotRule`.
+
+.. function:: AllRule()
+
+ Matches all traffic
+
+.. function:: DNSSECRule()
+
+ Matches queries with the DO flag set
+
+.. function:: DSTPortRule(port)
+
+ Matches questions received to the destination port.
+
+ :param int port: Match destination port.
+
+.. function:: EDNSOptionRule(optcode)
+
+ .. versionadded:: 1.4.0
+
+ Matches queries or responses with the specified EDNS option present.
+ ``optcode`` is specified as an integer, or a constant such as `EDNSOptionCode.ECS`.
+
+.. function:: EDNSVersionRule(version)
+
+ .. versionadded:: 1.4.0
+
+ Matches queries or responses with an OPT record whose EDNS version is greater than the specified EDNS version.
+
+ :param int version: The EDNS version to match on
+
+.. function:: ERCodeRule(rcode)
+
+ Matches queries or responses with the specified ``rcode``.
+ ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+ The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0.
+
+ :param int rcode: The RCODE to match on
+
+.. function:: HTTPHeaderRule(name, regex)
+
+ .. versionadded:: 1.4.0
+
+ .. versionchanged:: 1.8.0
+ see ``keepIncomingHeaders`` on :func:`addDOHLocal`
+
+ Matches DNS over HTTPS queries with a HTTP header ``name`` whose content matches the regular expression ``regex``.
+ Since 1.8.0 it is necessary to set the ``keepIncomingHeaders`` option to true on :func:`addDOHLocal` to be able to use this rule.
+
+ :param str name: The case-insensitive name of the HTTP header to match on
+ :param str regex: A regular expression to match the content of the specified header
+
+.. function:: HTTPPathRegexRule(regex)
+
+ .. versionadded:: 1.4.0
+
+ Matches DNS over HTTPS queries with a HTTP path matching the regular expression supplied in ``regex``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
+ Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead.
+
+ :param str regex: The regex to match on
+
+.. function:: HTTPPathRule(path)
+
+ .. versionadded:: 1.4.0
+
+ Matches DNS over HTTPS queries with a HTTP path of ``path``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
+ Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead.
+
+ :param str path: The exact HTTP path to match on
+
+.. function:: KeyValueStoreLookupRule(kvs, lookupKey)
+
+ .. versionadded:: 1.4.0
+
+ Return true if the key returned by 'lookupKey' exists in the key value store referenced by 'kvs'.
+ The store can be a CDB (:func:`newCDBKVStore`) or a LMDB database (:func:`newLMDBKVStore`).
+ The key can be based on the qname (:func:`KeyValueLookupKeyQName` and :func:`KeyValueLookupKeySuffix`),
+ source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
+
+ :param KeyValueStore kvs: The key value store to query
+ :param KeyValueLookupKey lookupKey: The key to use for the lookup
+
+.. function:: KeyValueStoreRangeLookupRule(kvs, lookupKey)
+
+ .. versionadded:: 1.7.0
+
+ Does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey' and returns true if there is a range covering that key.
+
+ This assumes that there is a key, in network byte order, for the last element of the range (for example 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff for 2001:db8::/32) which contains the first element of the range (2001:0db8:0000:0000:0000:0000:0000:0000) (optionally followed by any data) as value, still in network byte order, and that there is no overlapping ranges in the database.
+ This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB.
+
+ :param KeyValueStore kvs: The key value store to query
+ :param KeyValueLookupKey lookupKey: The key to use for the lookup
+
+.. function:: LuaFFIPerThreadRule(function)
+
+ .. versionadded:: 1.7.0
+
+ Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+ The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
+
+ The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
+ as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
+ are not available.
+
+ :param string function: a Lua string returning a Lua function
+
+.. function:: LuaFFIRule(function)
+
+ .. versionadded:: 1.5.0
+
+ Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+ The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
+
+ :param string function: the name of a Lua function
+
+.. function:: LuaRule(function)
+
+ .. versionadded:: 1.5.0
+
+ Invoke a Lua function that accepts a :class:`DNSQuestion` object.
+
+ The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
+
+ :param string function: the name of a Lua function
+
+.. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction [, shards]]]]]]])
+
+ .. versionchanged:: 1.8.0
+ ``shards`` parameter added
+
+ Matches traffic for a subnet specified by ``v4Mask`` or ``v6Mask`` exceeding ``qps`` queries per second up to ``burst`` allowed.
+ This rule keeps track of QPS by netmask or source IP. This state is cleaned up regularly if ``cleanupDelay`` is greater than zero,
+ removing existing netmasks or IP addresses that have not been seen in the last ``expiration`` seconds.
+
+ :param int qps: The number of queries per second allowed, above this number traffic is matched
+ :param int v4Mask: The IPv4 netmask to match on. Default is 32 (the whole address)
+ :param int v6Mask: The IPv6 netmask to match on. Default is 64
+ :param int burst: The number of burstable queries per second allowed. Default is same as qps
+ :param int expiration: How long to keep netmask or IP addresses after they have last been seen, in seconds. Default is 300
+ :param int cleanupDelay: The number of seconds between two cleanups. Default is 60
+ :param int scanFraction: The maximum fraction of the store to scan for expired entries, for example 5 would scan at most 20% of it. Default is 10 so 10%
+ :param int shards: How many shards to use, to decrease lock contention between threads. Default is 10 and is a safe default unless a very high number of threads are used to process incoming queries
+
+.. function:: MaxQPSRule(qps)
+
+ Matches traffic **not** exceeding this qps limit. If e.g. this is set to 50, starting at the 51st query of the current second traffic stops being matched.
+ This can be used to enforce a global QPS limit.
+
+ :param int qps: The number of queries per second allowed, above this number the traffic is **not** matched anymore
+
+.. function:: NetmaskGroupRule(nmg[, src[, quiet]])
+
+ .. versionchanged:: 1.4.0
+ ``quiet`` parameter added
+
+ .. versionchanged:: 1.9.0
+ The ``nmg`` parameter now accepts a string or a list of strings in addition to a class:`NetmaskGroup` object.
+
+ Matches traffic from/to the network range specified in the ``nmg``, which can be a string, a list of strings,
+ or a :class:`NetmaskGroup` object created via :func:`newNMG`.
+
+ Set the ``src`` parameter to false to match ``nmg`` against destination address instead of source address.
+ This can be used to differentiate between clients
+
+ :param NetmaskGroup nmg: The netmasks to match, can be a string, a list of strings or a :class:`NetmaskGroup` object.
+ :param bool src: Whether to match source or destination address of the packet. Defaults to true (matches source)
+ :param bool quiet: Do not display the list of matched netmasks in Rules. Default is false.
+
+.. function:: OpcodeRule(code)
+
+ Matches queries with opcode ``code``.
+ ``code`` can be directly specified as an integer, or one of the :ref:`built-in DNSOpcodes <DNSOpcode>`.
+
+ :param int code: The opcode to match
+
+.. function:: PayloadSizeRule(comparison, size)
+
+ .. versionadded:: 1.9.0
+
+ Matches queries or responses whose DNS payload size fits the given comparison.
+
+ :param str comparison: The comparison operator to use. Supported values are ``equal``, ``greater``, ``greaterOrEqual``, ``smaller`` and ``smallerOrEqual``.
+ :param int size: The size to compare to.
+
+.. function:: ProbaRule(probability)
+
+ Matches queries with a given probability. 1.0 means "always"
+
+ :param double probability: Probability of a match
+
+.. function:: ProxyProtocolValueRule(type [, value])
+
+ .. versionadded:: 1.6.0
+
+ Matches queries that have a proxy protocol TLV value of the specified type. If ``value`` is set,
+ the content of the value should also match the content of ``value``.
+
+ :param int type: The type of the value, ranging from 0 to 255 (both included)
+ :param str value: The optional binary-safe value to match
+
+.. function:: QClassRule(qclass)
+
+ Matches queries with the specified ``qclass``.
+ ``class`` can be specified as an integer or as one of the built-in :ref:`DNSClass`.
+
+ :param int qclass: The Query Class to match on
+
+.. function:: QNameRule(qname)
+
+ Matches queries with the specified qname exactly.
+
+ :param string qname: Qname to match
+
+.. function:: QNameSetRule(set)
+
+ .. versionadded:: 1.4.0
+
+ Matches if the set contains exact qname.
+
+ To match subdomain names, see :func:`QNameSuffixRule`.
+
+ :param DNSNameSet set: Set with qnames of type class:`DNSNameSet` created with :func:`newDNSNameSet`.
+
+.. function:: QNameSuffixRule(suffixes [, quiet])
+
+ .. versionadded:: 1.9.0
+
+ Matches based on a group of domain suffixes for rapid testing of membership.
+ The first parameter, ``suffixes``, can be a string, list of strings or a class:`SuffixMatchNode` object created with :func:`newSuffixMatchNode`.
+ Pass true as second parameter to prevent listing of all domains matched.
+
+ To match domain names exactly, see :func:`QNameSetRule`.
+
+ This rule existed before 1.9.0 but was called :func:`SuffixMatchNodeRule`, only accepting a :class:`SuffixMatchNode` parameter.
+
+ :param suffixes: A string, list of strings, or a :class:`SuffixMatchNode` to match on
+ :param bool quiet: Do not display the list of matched domains in Rules. Default is false.
+
+ Matches queries with the specified qname exactly.
+
+ :param string qname: Qname to match
+
+.. function:: QNameLabelsCountRule(min, max)
+
+ Matches if the qname has less than ``min`` or more than ``max`` labels.
+
+ :param int min: Minimum number of labels
+ :param int max: Maximum nimber of labels
+
+.. function:: QNameWireLengthRule(min, max)
+
+ Matches if the qname's length on the wire is less than ``min`` or more than ``max`` bytes.
+
+ :param int min: Minimum number of bytes
+ :param int max: Maximum nimber of bytes
+
+.. function:: QTypeRule(qtype)
+
+ Matches queries with the specified ``qtype``
+ ``qtype`` may be specified as an integer or as one of the built-in QTypes.
+ For instance ``DNSQType.A``, ``DNSQType.TXT`` and ``DNSQType.ANY``.
+
+ :param int qtype: The QType to match on
+
+.. function:: RCodeRule(rcode)
+
+ Matches queries or responses with the specified ``rcode``.
+ ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+ Only the non-extended RCode is matched (lower 4bits).
+
+ :param int rcode: The RCODE to match on
+
+.. function:: RDRule()
+
+ Matches queries with the RD flag set.
+
+.. function:: RegexRule(regex)
+
+ Matches the query name against the ``regex``.
+
+ .. code-block:: Lua
+
+ addAction(RegexRule("[0-9]{5,}"), DelayAction(750)) -- milliseconds
+ addAction(RegexRule("[0-9]{4,}\\.example$"), DropAction())
+
+ This delays any query for a domain name with 5 or more consecutive digits in it.
+ The second rule drops anything with more than 4 consecutive digits within a .EXAMPLE domain.
+
+ Note that the query name is presented without a trailing dot to the regex.
+ The regex is applied case insensitively.
+
+ :param string regex: A regular expression to match the traffic on
+
+.. function:: RecordsCountRule(section, minCount, maxCount)
+
+ Matches if there is at least ``minCount`` and at most ``maxCount`` records in the section ``section``.
+ ``section`` can be specified as an integer or as a :ref:`DNSSection`.
+
+ :param int section: The section to match on
+ :param int minCount: The minimum number of entries
+ :param int maxCount: The maximum number of entries
+
+.. function:: RecordsTypeCountRule(section, qtype, minCount, maxCount)
+
+ Matches if there is at least ``minCount`` and at most ``maxCount`` records of type ``type`` in the section ``section``.
+ ``section`` can be specified as an integer or as a :ref:`DNSSection`.
+ ``qtype`` may be specified as an integer or as one of the :ref:`built-in QTypes <DNSQType>`, for instance ``DNSQType.A`` or ``DNSQType.TXT``.
+
+ :param int section: The section to match on
+ :param int qtype: The QTYPE to match on
+ :param int minCount: The minimum number of entries
+ :param int maxCount: The maximum number of entries
+
+.. function:: RE2Rule(regex)
+
+ Matches the query name against the supplied regex using the RE2 engine.
+
+ For an example of usage, see :func:`RegexRule`.
+
+ :note: Only available when :program:`dnsdist` was built with libre2 support.
+
+ :param str regex: The regular expression to match the QNAME.
+
+.. function:: SNIRule(name)
+
+ .. versionadded:: 1.4.0
+
+ Matches against the TLS Server Name Indication value sent by the client, if any. Only makes
+ sense for DoT or DoH, and for that last one matching on the HTTP Host header using :func:`HTTPHeaderRule`
+ might provide more consistent results.
+ As of the version 2.3.0-beta of h2o, it is unfortunately not possible to extract the SNI value from DoH
+ connections, and it is therefore necessary to use the HTTP Host header until version 2.3.0 is released,
+ or ``nghttp2`` is used for incoming DoH instead (1.9.0+).
+
+ :param str name: The exact SNI name to match.
+
+.. function:: SuffixMatchNodeRule(smn[, quiet])
+
+ .. versionchanged:: 1.9.0
+ The ``smn`` parameter now accepts a string or a list of strings in addition to a class:`SuffixMatchNode` object.
+
+ Matches based on a group of domain suffixes for rapid testing of membership.
+ The first parameter, ``smn``, can be a string, list of strings or a class:`SuffixMatchNode` object created with :func:`newSuffixMatchNode`.
+ Pass true as second parameter to prevent listing of all domains matched.
+
+ To match domain names exactly, see :func:`QNameSetRule`.
+
+ Since 1.9.0, this rule can also be used via the alias :func:`QNameSuffixRule`.
+
+ :param SuffixMatchNode smn: A string, list of strings, or a :class:`SuffixMatchNode` to match on
+ :param bool quiet: Do not display the list of matched domains in Rules. Default is false.
+
+.. function:: TagRule(name [, value])
+
+ Matches question or answer with a tag named ``name`` set. If ``value`` is specified, the existing tag value should match too.
+
+ :param string name: The name of the tag that has to be set
+ :param string value: If set, the value the tag has to be set to. Default is unset
+
+.. function:: TCPRule(tcp)
+
+ Matches question received over TCP if ``tcp`` is true, over UDP otherwise.
+
+ :param bool tcp: Match TCP traffic if true, UDP traffic if false.
+
+.. function:: TrailingDataRule()
+
+ Matches if the query has trailing data.
+
+.. function:: PoolAvailableRule(poolname)
+
+ Check whether a pool has any servers available to handle queries
+
+ .. code-block:: Lua
+
+ --- Send queries to default pool when servers are available
+ addAction(PoolAvailableRule(""), PoolAction(""))
+ --- Send queries to fallback pool if not
+ addAction(AllRule(), PoolAction("fallback"))
+
+ :param string poolname: Pool to check
+
+.. function:: PoolOutstandingRule(poolname, limit)
+
+ .. versionadded:: 1.7.0
+
+ Check whether a pool has total outstanding queries above limit
+
+ .. code-block:: Lua
+
+ --- Send queries to spill over pool if default pool is under pressure
+ addAction(PoolOutstandingRule("", 5000), PoolAction("spillover"))
+
+ :param string poolname: Pool to check
+ :param int limit: Total outstanding limit
+
+Combining Rules
+---------------
+
+.. function:: AndRule(selectors)
+
+ Matches traffic if all ``selectors`` match.
+
+ :param {Rule} selectors: A table of Rules
+
+.. function:: NotRule(selector)
+
+ Matches the traffic if the ``selector`` rule does not match;
+
+ :param Rule selector: A Rule
+
+.. function:: OrRule(selectors)
+
+ Matches the traffic if one or more of the ``selectors`` Rules does match.
+
+ :param {Rule} selector: A table of Rules
+
+Objects
+-------
+
+.. class:: DNSDistRuleAction
+
+ .. versionadded:: 1.9.0
+
+ Represents a rule composed of a :class:`DNSRule` selector, to select the queries this applies to,
+ and a :class:`DNSAction` action to apply when the selector matches.
+
+ .. method:: DNSDistRuleAction:getAction()
+
+ Return the :class:`DNSAction` action of this rule.
+
+ .. method:: DNSDistRuleAction:getSelector()
+
+ Return the :class:`DNSRule` selector of this rule.
+
+.. class:: DNSDistResponseRuleAction
+
+ .. versionadded:: 1.9.0
+
+ Represents a rule composed of a :class:`DNSRule` selector, to select the responses this applies to,
+ and a :class:`DNSResponseAction` action to apply when the selector matches.
+
+ .. method:: DNSDistResponseRuleAction:getAction()
+
+ Return the :class:`DNSResponseAction` action of this rule.
+
+ .. method:: DNSDistResponseRuleAction:getSelector()
+
+ Return the :class:`DNSRule` selector of this rule.
+
+.. class:: DNSRule
+
+ .. versionadded:: 1.9.0
+
+ .. method:: DNSRule:getMatches() -> int
+
+ Return the number of times this selector matched a query or a response. Note that if the same selector is reused for different ``DNSDistRuleAction``
+ objects, the counter will be common to all these objects.
See also :func:`setRandomizedOutgoingSockets`.
The default is to use a linearly increasing counter from 0 to 65535, wrapping back to 0 when necessary.
-.. function:: setRandomizedOutgoingSockets(val):
+.. function:: setRandomizedOutgoingSockets(val)
.. versionadded:: 1.8.0
.. function:: setTCPRecvTimeout(num)
- Set the read timeout on TCP connections from the client, in seconds
+ Set the read timeout on TCP connections from the client, in seconds. Defaults to 2
:param int num:
.. function:: setTCPSendTimeout(num)
- Set the write timeout on TCP connections from the client, in seconds
+ Set the write timeout on TCP connections from the client, in seconds. Defaults to 2
:param int num:
Set the size of the receive (``SO_RCVBUF``) and send (``SO_SNDBUF``) buffers for incoming UDP sockets. On Linux the default
values correspond to ``net.core.rmem_default`` and ``net.core.wmem_default`` , and the maximum values are restricted
by ``net.core.rmem_max`` and ``net.core.wmem_max``.
+ Since 1.9.0, on Linux, dnsdist will automatically try to raise the buffer sizes to the maximum value allowed by the system (``net.core.rmem_max`` and ``net.core.wmem_max``) if :func:`setUDPSocketBufferSizes` is not set.
:param int recv: ``SO_RCVBUF`` value. Default is 0, meaning the system value will be kept.
:param int send: ``SO_SNDBUF`` value. Default is 0, meaning the system value will be kept.
--- /dev/null
+XSK / AF_XDP functions and objects
+==================================
+
+These are all the functions, objects and methods related to :doc:`../advanced/xsk`.
+
+.. function:: newXSK(options)
+
+ .. versionadded:: 1.9.0
+
+ This function creates a new :class:`XskSocket` object, tied to a network interface and queue, to accept ``XSK`` / ``AF_XDP`` packet from the Linux kernel. The returned object can be passed as a parameter to :func:`addLocal` to use XSK for ``UDP`` packets between clients and dnsdist. It can also be passed to ``newServer`` to use XSK for ``UDP`` packets between dnsdist a backend.
+
+ :param table options: A table with key: value pairs with listen options.
+
+ Options:
+
+ * ``ifName``: str - The name of the network interface this object will be tied to.
+ * ``NIC_queue_id``: int - The queue of the network interface this object will be tied to.
+ * ``frameNums``: int - The number of ``UMEM`` frames to allocate for this socket. More frames mean that a higher number of packets can be processed at the same time. 65535 is a good choice for maximum performance.
+ * ``xskMapPath``: str - The path of the BPF map used to communicate with the kernel space XDP program, usually ``/sys/fs/bpf/dnsdist/xskmap``.
+
+.. class:: XskSocket
+
+ .. versionadded:: 1.9.0
+
+ Represents a ``XSK`` / ``AF_XDP`` socket tied to a specific network interface and queue. This object can be created via :func:``newXSK`` and passed to :func:`addLocal` to use XSK for ``UDP`` packets between clients and dnsdist. It can also be passed to ``newServer`` to use XSK for ``UDP`` packets between dnsdist a backend.
+
+ .. method:: XskSocket:getMetrics() -> str
+
+ Returns a string containing ``XSK`` / ``AF_XDP`` metrics for this object, as reported by the Linux kernel.
sphinxcontrib-fulltoc
docutils!=0.15,<0.18
jinja2<3.1.0
+alabaster==0.7.13
Packet Policies
===============
+.. figure:: imgs/DNSDistFlow.v2.png
+ :align: center
+ :alt: DNSdist packet flows
+
:program:`dnsdist` works in essence like any other loadbalancer:
It receives packets on one or several addresses it listens on, and determines whether it will process this packet based on the :doc:`advanced/acl`. Should the packet be processed, :program:`dnsdist` attempts to match any of the configured rules in order and when one matches, the associated action is performed.
-These rule and action combinations are considered policies.
+These rule and action combinations are considered policies. The complete list of selectors (rules) can be found in :doc:`reference/selectors`, and the list of actions in :doc:`reference/actions`.
Packet Actions
--------------
addAction(MaxQPSIPRule(5, 32, 48), DelayAction(100))
-This measures traffic per IPv4 address and per /48 of IPv6, and if traffic for such an address (range) exceeds 5 qps, it gets delayed by 100ms. (Please note: :func:`DelayAction` can only delay UDP traffic).
+This measures traffic per IPv4 address and per /48 of IPv6, and if traffic for such an address (range) exceeds 5 qps, it gets delayed by 100ms. (Please note: :func:`DelayAction` can only delay UDP traffic).
As another example::
Alternatively, if compiled in, :func:`RE2Rule` provides similar functionality, but against libre2.
-Note that to check if a name is in a list of domains, :func:`SuffixMatchNodeRule` is preferred over complex regular expressions or multiple instances of :func:`RegexRule`.
-The :func:`makeRule` convenience function can be used to create a :func:`SuffixMatchNodeRule`.
-
-Rule Generators
----------------
-
-:program:`dnsdist` contains several functions that make it easier to add actions and rules.
-
-.. function:: addLuaAction(DNSrule, function [, options])
-
- .. deprecated:: 1.4.0
- Removed in 1.4.0, use :func:`LuaAction` with :func:`addAction` instead.
-
- Invoke a Lua function that accepts a :class:`DNSQuestion`.
- This function works similar to using :func:`LuaAction`.
- The ``function`` should return both a :ref:`DNSAction` and its argument `rule`. The `rule` is used as an argument
- of the following :ref:`DNSAction`: `DNSAction.Spoof`, `DNSAction.Pool` and `DNSAction.Delay`.
- If the Lua code fails, ServFail is returned.
-
- :param DNSRule: match queries based on this rule
- :param string function: the name of a Lua function
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
-
- ::
-
- function luaaction(dq)
- if(dq.qtype==DNSQType.NAPTR)
- then
- return DNSAction.Pool, "abuse" -- send to abuse pool
- else
- return DNSAction.None, "" -- no action
- -- return DNSAction.None -- as of dnsdist version 1.3.0
- end
- end
-
- addLuaAction(AllRule(), luaaction)
-
-.. function:: addLuaResponseAction(DNSrule, function [, options])
-
- .. deprecated:: 1.4.0
- Removed in 1.4.0, use :func:`LuaResponseAction` with :func:`addResponseAction` instead.
-
- Invoke a Lua function that accepts a :class:`DNSResponse`.
- This function works similar to using :func:`LuaResponseAction`.
- The ``function`` should return both a :ref:`DNSResponseAction` and its argument `rule`. The `rule` is used as an argument
- of the `DNSResponseAction.Delay`.
- If the Lua code fails, ServFail is returned.
-
- :param DNSRule: match queries based on this rule
- :param string function: the name of a Lua function
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+Note that to check if a name is in a list of domains, :func:`QNameSuffixRule` is preferred over complex regular expressions or multiple instances of :func:`RegexRule`.
Managing Rules
--------------
1 0 130.161.0.0/16, 145.14.0.0/16 qps limit to 20
2 0 nl., be. qps limit to 1
-For Rules related to the incoming query:
-
-.. function:: addAction(DNSrule, action [, options])
-
- .. versionchanged:: 1.6.0
- Added ``name`` to the ``options``.
-
- Add a Rule and Action to the existing rules.
-
- :param DNSrule rule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
- :param action: The action to take
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
- * ``name``: string - Name to assign to the new rule.
-
-.. function:: clearRules()
-
- Remove all current rules.
-
-.. function:: getAction(n) -> Action
-
- Returns the Action associated with rule ``n``.
-
- :param int n: The rule number
-
-.. function:: mvRule(from, to)
-
- Move rule ``from`` to a position where it is in front of ``to``.
- ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
- :param int from: Rule number to move
- :param int to: Location to more the Rule to
-
-.. function:: mvRuleToTop()
-
- .. versionadded:: 1.6.0
-
- This function moves the last rule to the first position. Before 1.6.0 this was handled by :func:`topRule`.
-
-.. function:: newRuleAction(rule, action[, options])
-
- .. versionchanged:: 1.6.0
- Added ``name`` to the ``options``.
-
- Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
-
- :param Rule rule: A Rule (see `Matching Packets (Selectors)`_)
- :param Action action: The Action (see `Actions`_) to apply to the matched traffic
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
- * ``name``: string - Name to assign to the new rule.
-
-.. function:: setRules(rules)
-
- Replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see :func:`newRuleAction`)
-
- :param [RuleAction] rules: A list of RuleActions
-
-.. function:: showRules([options])
-
- Show all defined rules for queries, optionally displaying their UUIDs.
-
- :param table options: A table with key: value pairs with display options.
-
- Options:
-
- * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
- * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-.. function:: topRule()
-
- .. versionchanged:: 1.6.0
- Replaced by :func:`mvRuleToTop`
-
- Before 1.6.0 this function used to move the last rule to the first position, which is now handled by :func:`mvRuleToTop`.
-
-.. function:: rmRule(id)
-
- .. versionchanged:: 1.6.0
- ``id`` can now be a string representing the name of the rule.
-
- Remove rule ``id``.
-
- :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-For Rules related to responses:
-
-.. function:: addResponseAction(DNSRule, action [, options])
-
- .. versionchanged:: 1.6.0
- Added ``name`` to the ``options``.
-
- Add a Rule and Action for responses to the existing rules.
-
- :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
- :param action: The action to take
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
- * ``name``: string - Name to assign to the new rule.
-
-.. function:: mvResponseRule(from, to)
-
- Move response rule ``from`` to a position where it is in front of ``to``.
- ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
- :param int from: Rule number to move
- :param int to: Location to more the Rule to
-
-.. function:: mvResponseRuleToTop()
-
- .. versionadded:: 1.6.0
-
- This function moves the last response rule to the first position. Before 1.6.0 this was handled by :func:`topResponseRule`.
-
-.. function:: rmResponseRule(id)
-
- .. versionchanged:: 1.6.0
- ``id`` can now be a string representing the name of the rule.
-
- Remove response rule ``id``.
-
- :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-.. function:: showResponseRules([options])
-
- Show all defined response rules, optionally displaying their UUIDs.
-
- :param table options: A table with key: value pairs with display options.
-
- Options:
-
- * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
- * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-.. function:: topResponseRule()
-
- .. versionchanged:: 1.6.0
- Replaced by :func:`mvResponseRuleToTop`
-
- Before 1.6.0 this function used to move the last response rule to the first position, which is now handled by :func:`mvResponseRuleToTop`.
-
-Functions for manipulating Cache Hit Response Rules:
-
-.. function:: addCacheHitResponseAction(DNSRule, action [, options])
-
- .. versionchanged:: 1.6.0
- Added ``name`` to the ``options``.
-
- Add a Rule and ResponseAction for Cache Hits to the existing rules.
-
- :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
- :param action: The action to take
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
- * ``name``: string - Name to assign to the new rule.
-
-.. function:: mvCacheHitResponseRule(from, to)
-
- Move cache hit response rule ``from`` to a position where it is in front of ``to``.
- ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
- :param int from: Rule number to move
- :param int to: Location to more the Rule to
-
-.. function:: mvCacheHitResponseRuleToTop()
-
- .. versionadded:: 1.6.0
-
- This function moves the last cache hit response rule to the first position. Before 1.6.0 this was handled by :func:`topCacheHitResponseRule`.
-
-.. function:: rmCacheHitResponseRule(id)
-
- .. versionchanged:: 1.6.0
- ``id`` can now be a string representing the name of the rule.
-
- :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-.. function:: showCacheHitResponseRules([options])
-
- Show all defined cache hit response rules, optionally displaying their UUIDs.
-
- :param table options: A table with key: value pairs with display options.
-
- Options:
-
- * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
- * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-.. function:: topCacheHitResponseRule()
-
- .. versionchanged:: 1.6.0
- Replaced by :func:`mvCacheHitResponseRuleToTop`
-
- Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvCacheHitResponseRuleToTop`.
-
-Functions for manipulating Cache Inserted Response Rules:
-
-.. function:: addCacheInsertedResponseAction(DNSRule, action [, options])
-
- .. versionadded:: 1.8.0
-
- Add a Rule and ResponseAction that is executed after a cache entry has been inserted to the existing rules.
-
- :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
- :param action: The action to take
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
- * ``name``: string - Name to assign to the new rule.
-
-.. function:: mvCacheInsertedResponseRule(from, to)
-
- .. versionadded:: 1.8.0
-
- Move cache inserted response rule ``from`` to a position where it is in front of ``to``.
- ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
- :param int from: Rule number to move
- :param int to: Location to more the Rule to
-
-.. function:: mvCacheInsertedResponseRuleToTop()
-
- .. versionadded:: 1.8.0
-
- This function moves the last cache inserted response rule to the first position.
-
-.. function:: rmCacheInsertedResponseRule(id)
-
- .. versionadded:: 1.8.0
-
- :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-.. function:: showCacheInsertedResponseRules([options])
-
- .. versionadded:: 1.8.0
-
- Show all defined cache inserted response rules, optionally displaying their UUIDs.
-
- :param table options: A table with key: value pairs with display options.
-
- Options:
-
- * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
- * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-Functions for manipulating Self-Answered Response Rules:
-
-.. function:: addSelfAnsweredResponseAction(DNSRule, action [, options])
-
- .. versionchanged:: 1.6.0
- Added ``name`` to the ``options``.
-
- Add a Rule and Action for Self-Answered queries to the existing rules.
-
- :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
- :param action: The action to take
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
- * ``name``: string - Name to assign to the new rule.
-
-.. function:: mvSelfAnsweredResponseRule(from, to)
-
- Move self answered response rule ``from`` to a position where it is in front of ``to``.
- ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
- :param int from: Rule number to move
- :param int to: Location to more the Rule to
-
-.. function:: mvSelfAnsweredResponseRuleToTop()
-
- .. versionadded:: 1.6.0
-
- This function moves the last self-answered response rule to the first position. Before 1.6.0 this was handled by :func:`topSelfAnsweredResponseRule`.
-
-.. function:: rmSelfAnsweredResponseRule(id)
-
- .. versionchanged:: 1.6.0
- ``id`` can now be a string representing the name of the rule.
-
- Remove self answered response rule ``id``.
-
- :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-.. function:: showSelfAnsweredResponseRules([options])
-
- Show all defined self answered response rules, optionally displaying their UUIDs.
-
- :param table options: A table with key: value pairs with display options.
-
- Options:
-
- * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
- * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-.. function:: topSelfAnsweredResponseRule()
-
- .. versionchanged:: 1.6.0
- Replaced by :func:`mvSelfAnsweredResponseRuleToTop`
-
- Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvSelfAnsweredResponseRuleToTop`.
-
- Move the last self answered response rule to the first position.
-
-.. _RulesIntro:
-
-Matching Packets (Selectors)
-----------------------------
-
-Packets can be matched by selectors, called a ``DNSRule``.
-These ``DNSRule``\ s be one of the following items:
-
- * A string that is either a domain name or netmask
- * A list of strings that are either domain names or netmasks
- * A :class:`DNSName`
- * A list of :class:`DNSName`\ s
- * A (compounded) ``Rule``
-
-.. function:: AllRule()
-
- Matches all traffic
-
-.. function:: DNSSECRule()
-
- Matches queries with the DO flag set
-
-.. function:: DSTPortRule(port)
-
- Matches questions received to the destination port.
-
- :param int port: Match destination port.
-
-.. function:: EDNSOptionRule(optcode)
-
- .. versionadded:: 1.4.0
-
- Matches queries or responses with the specified EDNS option present.
- ``optcode`` is specified as an integer, or a constant such as `EDNSOptionCode.ECS`.
-
-.. function:: EDNSVersionRule(version)
-
- .. versionadded:: 1.4.0
-
- Matches queries or responses with an OPT record whose EDNS version is greater than the specified EDNS version.
-
- :param int version: The EDNS version to match on
-
-.. function:: ERCodeRule(rcode)
-
- Matches queries or responses with the specified ``rcode``.
- ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
- The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0.
-
- :param int rcode: The RCODE to match on
-
-.. function:: HTTPHeaderRule(name, regex)
-
- .. versionadded:: 1.4.0
-
- .. versionchanged:: 1.8.0
- see ``keepIncomingHeaders`` on :func:`addDOHLocal`
-
- Matches DNS over HTTPS queries with a HTTP header ``name`` whose content matches the regular expression ``regex``.
- Since 1.8.0 it is necessary to set the ``keepIncomingHeaders`` option to true on :func:`addDOHLocal` to be able to use this rule.
-
- :param str name: The case-insensitive name of the HTTP header to match on
- :param str regex: A regular expression to match the content of the specified header
-
-.. function:: HTTPPathRegexRule(regex)
-
- .. versionadded:: 1.4.0
-
- Matches DNS over HTTPS queries with a HTTP path matching the regular expression supplied in ``regex``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
- Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead.
-
- :param str regex: The regex to match on
-
-.. function:: HTTPPathRule(path)
-
- .. versionadded:: 1.4.0
-
- Matches DNS over HTTPS queries with a HTTP path of ``path``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
- Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead.
-
- :param str path: The exact HTTP path to match on
-
-.. function:: KeyValueStoreLookupRule(kvs, lookupKey)
-
- .. versionadded:: 1.4.0
-
- Return true if the key returned by 'lookupKey' exists in the key value store referenced by 'kvs'.
- The store can be a CDB (:func:`newCDBKVStore`) or a LMDB database (:func:`newLMDBKVStore`).
- The key can be based on the qname (:func:`KeyValueLookupKeyQName` and :func:`KeyValueLookupKeySuffix`),
- source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
-
- :param KeyValueStore kvs: The key value store to query
- :param KeyValueLookupKey lookupKey: The key to use for the lookup
-
-.. function:: KeyValueStoreRangeLookupRule(kvs, lookupKey)
-
- .. versionadded:: 1.7.0
-
- Does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey' and returns true if there is a range covering that key.
-
- This assumes that there is a key, in network byte order, for the last element of the range (for example 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff for 2001:db8::/32) which contains the first element of the range (2001:0db8:0000:0000:0000:0000:0000:0000) (optionally followed by any data) as value, still in network byte order, and that there is no overlapping ranges in the database.
- This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB.
-
- :param KeyValueStore kvs: The key value store to query
- :param KeyValueLookupKey lookupKey: The key to use for the lookup
-
-.. function:: LuaFFIPerThreadRule(function)
-
- .. versionadded:: 1.7.0
-
- Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
- The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
-
- The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
- as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
- are not available.
-
- :param string function: a Lua string returning a Lua function
-
-.. function:: LuaFFIRule(function)
-
- .. versionadded:: 1.5.0
-
- Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
- The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
-
- :param string function: the name of a Lua function
-
-.. function:: LuaRule(function)
-
- .. versionadded:: 1.5.0
-
- Invoke a Lua function that accepts a :class:`DNSQuestion` object.
-
- The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
-
- :param string function: the name of a Lua function
-
-.. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction [, shards]]]]]]])
-
- .. versionchanged:: 1.8.0
- ``shards`` parameter added
-
- Matches traffic for a subnet specified by ``v4Mask`` or ``v6Mask`` exceeding ``qps`` queries per second up to ``burst`` allowed.
- This rule keeps track of QPS by netmask or source IP. This state is cleaned up regularly if ``cleanupDelay`` is greater than zero,
- removing existing netmasks or IP addresses that have not been seen in the last ``expiration`` seconds.
-
- :param int qps: The number of queries per second allowed, above this number traffic is matched
- :param int v4Mask: The IPv4 netmask to match on. Default is 32 (the whole address)
- :param int v6Mask: The IPv6 netmask to match on. Default is 64
- :param int burst: The number of burstable queries per second allowed. Default is same as qps
- :param int expiration: How long to keep netmask or IP addresses after they have last been seen, in seconds. Default is 300
- :param int cleanupDelay: The number of seconds between two cleanups. Default is 60
- :param int scanFraction: The maximum fraction of the store to scan for expired entries, for example 5 would scan at most 20% of it. Default is 10 so 10%
- :param int shards: How many shards to use, to decrease lock contention between threads. Default is 10 and is a safe default unless a very high number of threads are used to process incoming queries
-
-.. function:: MaxQPSRule(qps)
-
- Matches traffic **not** exceeding this qps limit. If e.g. this is set to 50, starting at the 51st query of the current second traffic stops being matched.
- This can be used to enforce a global QPS limit.
-
- :param int qps: The number of queries per second allowed, above this number the traffic is **not** matched anymore
-
-.. function:: NetmaskGroupRule(nmg[, src[, quiet]])
-
- .. versionchanged:: 1.4.0
- ``quiet`` parameter added
-
- Matches traffic from/to the network range specified in ``nmg``.
-
- Set the ``src`` parameter to false to match ``nmg`` against destination address instead of source address.
- This can be used to differentiate between clients
-
- :param NetMaskGroup nmg: The NetMaskGroup to match on
- :param bool src: Whether to match source or destination address of the packet. Defaults to true (matches source)
- :param bool quiet: Do not display the list of matched netmasks in Rules. Default is false.
-
-.. function:: OpcodeRule(code)
-
- Matches queries with opcode ``code``.
- ``code`` can be directly specified as an integer, or one of the :ref:`built-in DNSOpcodes <DNSOpcode>`.
-
- :param int code: The opcode to match
-
-.. function:: ProbaRule(probability)
-
- Matches queries with a given probability. 1.0 means "always"
-
- :param double probability: Probability of a match
-
-.. function:: ProxyProtocolValueRule(type [, value])
-
- .. versionadded:: 1.6.0
-
- Matches queries that have a proxy protocol TLV value of the specified type. If ``value`` is set,
- the content of the value should also match the content of ``value``.
-
- :param int type: The type of the value, ranging from 0 to 255 (both included)
- :param str value: The optional binary-safe value to match
-
-.. function:: QClassRule(qclass)
-
- Matches queries with the specified ``qclass``.
- ``class`` can be specified as an integer or as one of the built-in :ref:`DNSClass`.
-
- :param int qclass: The Query Class to match on
-
-.. function:: QNameRule(qname)
-
- Matches queries with the specified qname exactly.
-
- :param string qname: Qname to match
-
-.. function:: QNameSetRule(set)
-
- .. versionadded:: 1.4.0
-
- Matches if the set contains exact qname.
-
- To match subdomain names, see :func:`SuffixMatchNodeRule`.
-
- :param DNSNameSet set: Set with qnames.
-
-.. function:: QNameLabelsCountRule(min, max)
-
- Matches if the qname has less than ``min`` or more than ``max`` labels.
-
- :param int min: Minimum number of labels
- :param int max: Maximum nimber of labels
-
-.. function:: QNameWireLengthRule(min, max)
-
- Matches if the qname's length on the wire is less than ``min`` or more than ``max`` bytes.
-
- :param int min: Minimum number of bytes
- :param int max: Maximum nimber of bytes
-
-.. function:: QTypeRule(qtype)
-
- Matches queries with the specified ``qtype``
- ``qtype`` may be specified as an integer or as one of the built-in QTypes.
- For instance ``DNSQType.A``, ``DNSQType.TXT`` and ``DNSQType.ANY``.
-
- :param int qtype: The QType to match on
-
-.. function:: RCodeRule(rcode)
-
- Matches queries or responses with the specified ``rcode``.
- ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
- Only the non-extended RCode is matched (lower 4bits).
-
- :param int rcode: The RCODE to match on
-
-.. function:: RDRule()
-
- Matches queries with the RD flag set.
-
-.. function:: RegexRule(regex)
-
- Matches the query name against the ``regex``.
-
- .. code-block:: Lua
-
- addAction(RegexRule("[0-9]{5,}"), DelayAction(750)) -- milliseconds
- addAction(RegexRule("[0-9]{4,}\\.example$"), DropAction())
-
- This delays any query for a domain name with 5 or more consecutive digits in it.
- The second rule drops anything with more than 4 consecutive digits within a .EXAMPLE domain.
-
- Note that the query name is presented without a trailing dot to the regex.
- The regex is applied case insensitively.
-
- :param string regex: A regular expression to match the traffic on
-
-.. function:: RecordsCountRule(section, minCount, maxCount)
-
- Matches if there is at least ``minCount`` and at most ``maxCount`` records in the section ``section``.
- ``section`` can be specified as an integer or as a :ref:`DNSSection`.
-
- :param int section: The section to match on
- :param int minCount: The minimum number of entries
- :param int maxCount: The maximum number of entries
-
-.. function:: RecordsTypeCountRule(section, qtype, minCount, maxCount)
-
- Matches if there is at least ``minCount`` and at most ``maxCount`` records of type ``type`` in the section ``section``.
- ``section`` can be specified as an integer or as a :ref:`DNSSection`.
- ``qtype`` may be specified as an integer or as one of the :ref:`built-in QTypes <DNSQType>`, for instance ``DNSQType.A`` or ``DNSQType.TXT``.
-
- :param int section: The section to match on
- :param int qtype: The QTYPE to match on
- :param int minCount: The minimum number of entries
- :param int maxCount: The maximum number of entries
-
-.. function:: RE2Rule(regex)
-
- Matches the query name against the supplied regex using the RE2 engine.
-
- For an example of usage, see :func:`RegexRule`.
-
- :note: Only available when :program:`dnsdist` was built with libre2 support.
-
- :param str regex: The regular expression to match the QNAME.
-
-.. function:: SNIRule(name)
-
- .. versionadded:: 1.4.0
-
- Matches against the TLS Server Name Indication value sent by the client, if any. Only makes
- sense for DoT or DoH, and for that last one matching on the HTTP Host header using :func:`HTTPHeaderRule`
- might provide more consistent results.
- As of the version 2.3.0-beta of h2o, it is unfortunately not possible to extract the SNI value from DoH
- connections, and it is therefore necessary to use the HTTP Host header until version 2.3.0 is released.
-
- :param str name: The exact SNI name to match.
-
-.. function:: SuffixMatchNodeRule(smn[, quiet])
-
- Matches based on a group of domain suffixes for rapid testing of membership.
- Pass true as second parameter to prevent listing of all domains matched.
-
- To match domain names exactly, see :func:`QNameSetRule`.
-
- :param SuffixMatchNode smn: The SuffixMatchNode to match on
- :param bool quiet: Do not display the list of matched domains in Rules. Default is false.
-
-.. function:: TagRule(name [, value])
-
- Matches question or answer with a tag named ``name`` set. If ``value`` is specified, the existing tag value should match too.
-
- :param string name: The name of the tag that has to be set
- :param string value: If set, the value the tag has to be set to. Default is unset
-
-.. function:: TCPRule(tcp)
-
- Matches question received over TCP if ``tcp`` is true, over UDP otherwise.
-
- :param bool tcp: Match TCP traffic if true, UDP traffic if false.
-
-.. function:: TrailingDataRule()
-
- Matches if the query has trailing data.
-
-.. function:: PoolAvailableRule(poolname)
-
- Check whether a pool has any servers available to handle queries
-
- .. code-block:: Lua
-
- --- Send queries to default pool when servers are available
- addAction(PoolAvailableRule(""), PoolAction(""))
- --- Send queries to fallback pool if not
- addAction(AllRule(), PoolAction("fallback"))
-
- :param string poolname: Pool to check
-
-.. function:: PoolOutstandingRule(poolname, limit)
-
- .. versionadded:: 1.7.0
-
- Check whether a pool has total outstanding queries above limit
-
- .. code-block:: Lua
-
- --- Send queries to spill over pool if default pool is under pressure
- addAction(PoolOutstandingRule("", 5000), PoolAction("spillover"))
-
- :param string poolname: Pool to check
- :param int limit: Total outstanding limit
-
-
-Combining Rules
-~~~~~~~~~~~~~~~
-
-.. function:: AndRule(selectors)
-
- Matches traffic if all ``selectors`` match.
-
- :param {Rule} selectors: A table of Rules
-
-.. function:: NotRule(selector)
-
- Matches the traffic if the ``selector`` rule does not match;
-
- :param Rule selector: A Rule
-
-.. function:: OrRule(selectors)
-
- Matches the traffic if one or more of the ``selectors`` Rules does match.
-
- :param {Rule} selector: A table of Rules
-
-Convenience Functions
-~~~~~~~~~~~~~~~~~~~~~
-
-.. function:: makeRule(rule)
-
- Make a :func:`NetmaskGroupRule` or a :func:`SuffixMatchNodeRule`, depending on it is called.
- ``makeRule("0.0.0.0/0")`` will for example match all IPv4 traffic, ``makeRule({"be","nl","lu"})`` will match all Benelux DNS traffic.
-
- :param string rule: A string to convert to a rule.
-
-
-Actions
--------
-
-:ref:`RulesIntro` need to be combined with an action for them to actually do something with the matched packets.
-Some actions allow further processing of rules, this is noted in their description. Most of these start with 'Set' with a few exceptions, mostly for logging actions. These exceptions are:
-
-- :func:`ClearRecordTypesResponseAction`
-- :func:`KeyValueStoreLookupAction`
-- :func:`DnstapLogAction`
-- :func:`DnstapLogResponseAction`
-- :func:`LimitTTLResponseAction`
-- :func:`LogAction`
-- :func:`NoneAction`
-- :func:`RemoteLogAction`
-- :func:`RemoteLogResponseAction`
-- :func:`SetMaxReturnedTTLResponseAction`
-- :func:`SetMaxReturnedTTLAction`
-- :func:`SetMinTTLResponseAction`
-- :func:`SetMaxTTLResponseAction`
-- :func:`SNMPTrapAction`
-- :func:`SNMPTrapResponseAction`
-- :func:`TeeAction`
-
-The following actions exist.
-
-.. function:: AllowAction()
-
- Let these packets go through.
-
-.. function:: AllowResponseAction()
-
- Let these packets go through.
-
-.. function:: ClearRecordTypesResponseAction(types)
-
- .. versionadded:: 1.8.0
-
- Removes given type(s) records from the response. Beware you can accidentally turn the answer into a NODATA response
- without a SOA record in the additional section in which case you may want to use :func:`NegativeAndSOAAction` to generate an answer,
- see example bellow.
- Subsequent rules are processed after this action.
-
- .. code-block:: Lua
-
- -- removes any HTTPS record in the response
- addResponseAction(
- QNameRule('www.example.com.'),
- ClearRecordTypesResponseAction(DNSQType.HTTPS)
- )
- -- reply directly with NODATA and a SOA record as we know the answer will be empty
- addAction(
- AndRule{QNameRule('www.example.com.'), QTypeRule(DNSQType.HTTPS)},
- NegativeAndSOAAction(false, 'example.com.', 3600, 'ns.example.com.', 'postmaster.example.com.', 1, 1800, 900, 604800, 86400)
- )
-
- :param int types: a single type or a list of types to remove
-
-.. function:: ContinueAction(action)
-
- .. versionadded:: 1.4.0
-
- Execute the specified action and override its return with None, making it possible to continue the processing.
- Subsequent rules are processed after this action.
-
- :param int action: Any other action
-
-.. function:: DelayAction(milliseconds)
-
- Delay the response by the specified amount of milliseconds (UDP-only). Note that the sending of the query to the backend, if needed,
- is not delayed. Only the sending of the response to the client will be delayed.
- Subsequent rules are processed after this action.
-
- :param int milliseconds: The amount of milliseconds to delay the response
-
-.. function:: DelayResponseAction(milliseconds)
-
- Delay the response by the specified amount of milliseconds (UDP-only).
- The only difference between this action and :func:`DelayAction` is that they can only be applied on, respectively, responses and queries.
- Subsequent rules are processed after this action.
-
- :param int milliseconds: The amount of milliseconds to delay the response
-
-.. function:: DisableECSAction()
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetDisableECSAction` instead.
-
- Disable the sending of ECS to the backend.
- Subsequent rules are processed after this action.
-
-.. function:: DisableValidationAction()
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetDisableValidationAction` instead.
-
- Set the CD bit in the query and let it go through.
- Subsequent rules are processed after this action.
-
-.. function:: DnstapLogAction(identity, logger[, alterFunction])
-
- Send the current query to a remote logger as a :doc:`dnstap <reference/dnstap>` message.
- ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
- Subsequent rules are processed after this action.
-
- :param string identity: Server identity to store in the dnstap message
- :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
- :param alterFunction: A Lua function to alter the message before sending
-
-.. function:: DnstapLogResponseAction(identity, logger[, alterFunction])
-
- Send the current response to a remote logger as a :doc:`dnstap <reference/dnstap>` message.
- ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
- Subsequent rules are processed after this action.
-
- :param string identity: Server identity to store in the dnstap message
- :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
- :param alterFunction: A Lua function to alter the message before sending
-
-.. function:: DropAction()
-
- Drop the packet.
-
-.. function:: DropResponseAction()
-
- Drop the packet.
-
-.. function:: ECSOverrideAction(override)
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetECSOverrideAction` instead.
-
- Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
- Subsequent rules are processed after this action.
-
- :param bool override: Whether or not to override ECS value
-
-.. function:: ECSPrefixLengthAction(v4, v6)
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetECSPrefixLengthAction` instead.
-
- Set the ECS prefix length.
- Subsequent rules are processed after this action.
-
- :param int v4: The IPv4 netmask length
- :param int v6: The IPv6 netmask length
-
-.. function:: ERCodeAction(rcode [, options])
-
- .. versionadded:: 1.4.0
-
- .. versionchanged:: 1.5.0
- Added the optional parameter ``options``.
-
- Reply immediately by turning the query into a response with the specified EDNS extended ``rcode``.
- ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
-
- :param int rcode: The extended RCODE to respond with.
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
-
-.. function:: HTTPStatusAction(status, body, contentType="" [, options])
-
- .. versionadded:: 1.4.0
-
- .. versionchanged:: 1.5.0
- Added the optional parameter ``options``.
-
- Return an HTTP response with a status code of ''status''. For HTTP redirects, ''body'' should be the redirect URL.
-
- :param int status: The HTTP status code to return.
- :param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx).
- :param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ''application/dns-message''.
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
-
-.. function:: KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)
-
- .. versionadded:: 1.4.0
-
- Does a lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey',
- and storing the result if any into the tag named 'destinationTag'.
- The store can be a CDB (:func:`newCDBKVStore`) or a LMDB database (:func:`newLMDBKVStore`).
- The key can be based on the qname (:func:`KeyValueLookupKeyQName` and :func:`KeyValueLookupKeySuffix`),
- source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
- Subsequent rules are processed after this action.
- Note that the tag is always created, even if there was no match, but in that case the content is empty.
-
- :param KeyValueStore kvs: The key value store to query
- :param KeyValueLookupKey lookupKey: The key to use for the lookup
- :param string destinationTag: The name of the tag to store the result into
-
-.. function:: KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag)
-
- .. versionadded:: 1.7.0
-
- Does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey',
- and storing the result if any into the tag named 'destinationTag'.
- This assumes that there is a key in network byte order for the last element of the range (for example 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff for 2001:db8::/32) which contains the first element of the range (2001:0db8:0000:0000:0000:0000:0000:0000) (optionally followed by any data) as value, also in network byte order, and that there is no overlapping ranges in the database.
- This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB.
-
- Subsequent rules are processed after this action.
-
- :param KeyValueStore kvs: The key value store to query
- :param KeyValueLookupKey lookupKey: The key to use for the lookup
- :param string destinationTag: The name of the tag to store the result into
-
-.. function:: LimitTTLResponseAction(min[, max [, types]])
-
- .. versionadded:: 1.8.0
-
- Cap the TTLs of the response to the given boundaries.
-
- :param int min: The minimum allowed value
- :param int max: The maximum allowed value
- :param list of int: The record types to cap the TTL for. Default is empty which means all records will be capped.
-
-.. function:: LogAction([filename[, binary[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
-
- .. versionchanged:: 1.4.0
- Added the optional parameters ``verboseOnly`` and ``includeTimestamp``, made ``filename`` optional.
-
- .. versionchanged:: 1.7.0
- Added the ``reload`` method.
-
- Log a line for each query, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
-
- If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
-
- When logging to a file, the ``binary`` optional parameter specifies whether we log in binary form (default) or in textual form. Before 1.4.0 the binary log format only included the qname and qtype. Since 1.4.0 it includes an optional timestamp, the query ID, qname, qtype, remote address and port.
-
- The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
- The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
-
- Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
-
- Subsequent rules are processed after this action.
-
- :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
- :param bool binary: Do binary logging. Default true
- :param bool append: Append to the log. Default false
- :param bool buffered: Use buffered I/O. Default true
- :param bool verboseOnly: Whether to log only in verbose mode when logging to stdout. Default is true
- :param bool includeTimestamp: Whether to include a timestamp for every entry. Default is false
-
-.. function:: LogResponseAction([filename[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
-
- .. versionadded:: 1.5.0
-
- .. versionchanged:: 1.7.0
- Added the ``reload`` method.
-
- Log a line for each response, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
-
- If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
-
- The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
- The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
-
- Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
-
- Subsequent rules are processed after this action.
-
- :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
- :param bool append: Append to the log. Default false
- :param bool buffered: Use buffered I/O. Default true
- :param bool verboseOnly: Whether to log only in verbose mode when logging to stdout. Default is true
- :param bool includeTimestamp: Whether to include a timestamp for every entry. Default is false
-
-.. function:: LuaAction(function)
-
- Invoke a Lua function that accepts a :class:`DNSQuestion`.
-
- The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
-
- :param string function: the name of a Lua function
-
-.. function:: LuaFFIAction(function)
-
- .. versionadded:: 1.5.0
-
- Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
- The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
-
- :param string function: the name of a Lua function
-
-.. function:: LuaFFIPerThreadAction(function)
-
- .. versionadded:: 1.7.0
-
- Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
- The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
-
- The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
- as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
- are not available.
-
- :param string function: a Lua string returning a Lua function
-
-.. function:: LuaFFIPerThreadResponseAction(function)
-
- .. versionadded:: 1.7.0
-
- Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
- The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
-
- The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
- as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
- are not available.
-
- :param string function: a Lua string returning a Lua function
-
-.. function:: LuaFFIResponseAction(function)
-
- .. versionadded:: 1.5.0
-
- Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
- The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
-
- :param string function: the name of a Lua function
-
-.. function:: LuaResponseAction(function)
-
- Invoke a Lua function that accepts a :class:`DNSResponse`.
-
- The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
-
- :param string function: the name of a Lua function
-
-.. function:: MacAddrAction(option)
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetMacAddrAction` instead.
-
- Add the source MAC address to the query as EDNS0 option ``option``.
- This action is currently only supported on Linux.
- Subsequent rules are processed after this action.
-
- :param int option: The EDNS0 option number
-
-.. function:: NegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
-
- .. versionadded:: 1.6.0
-
- .. versionchanged:: 1.8.0
- Added the ``soaInAuthoritySection`` option.
-
- Turn a question into a response, either a NXDOMAIN or a NODATA one based on ''nxd'', setting the QR bit to 1 and adding a SOA record in the additional section.
- Note that this function was called :func:`SetNegativeAndSOAAction` before 1.6.0.
-
- :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
- :param string zone: The owner name for the SOA record
- :param int ttl: The TTL of the SOA record
- :param string mname: The mname of the SOA record
- :param string rname: The rname of the SOA record
- :param int serial: The value of the serial field in the SOA record
- :param int refresh: The value of the refresh field in the SOA record
- :param int retry: The value of the retry field in the SOA record
- :param int expire: The value of the expire field in the SOA record
- :param int minimum: The value of the minimum field in the SOA record
- :param table options: A table with key: value pairs with options
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
- * ``soaInAuthoritySection``: bool - Place the SOA record in the authority section for a complete NXDOMAIN/NODATA response that works as a cacheable negative response, rather than the RPZ-style response with a purely informational SOA in the additional section. Default is false (SOA in additional section).
-
-.. function:: NoneAction()
-
- Does nothing.
- Subsequent rules are processed after this action.
-
-.. function:: NoRecurseAction()
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetNoRecurseAction` instead.
-
- Strip RD bit from the question, let it go through.
- Subsequent rules are processed after this action.
-
-.. function:: PoolAction(poolname [, stop])
-
- .. versionchanged:: 1.8.0
- Added the ``stop`` optional parameter.
-
- Send the packet into the specified pool. If ``stop`` is set to false, subsequent rules will be processed after this action.
-
- :param string poolname: The name of the pool
- :param bool stop: Whether to stop processing rules after this action. Default is true, meaning the remaining rules will not be processed.
-
-.. function:: QPSAction(maxqps)
-
- Drop a packet if it does exceed the ``maxqps`` queries per second limits.
- Letting the subsequent rules apply otherwise.
-
- :param int maxqps: The QPS limit
-
-.. function:: QPSPoolAction(maxqps, poolname)
-
- .. versionchanged:: 1.8.0
- Added the ``stop`` optional parameter.
-
- Send the packet into the specified pool only if it does not exceed the ``maxqps`` queries per second limits. If ``stop`` is set to false, subsequent rules will be processed after this action.
- Letting the subsequent rules apply otherwise.
-
- :param int maxqps: The QPS limit for that pool
- :param string poolname: The name of the pool
- :param bool stop: Whether to stop processing rules after this action. Default is true, meaning the remaining rules will not be processed.
-
-.. function:: RCodeAction(rcode [, options])
-
- .. versionchanged:: 1.5.0
- Added the optional parameter ``options``.
-
- Reply immediately by turning the query into a response with the specified ``rcode``.
- ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
-
- :param int rcode: The RCODE to respond with.
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
-
-.. function:: RemoteLogAction(remoteLogger[, alterFunction [, options [, metas]]])
-
- .. versionchanged:: 1.4.0
- ``ipEncryptKey`` optional key added to the options table.
-
- .. versionchanged:: 1.8.0
- ``metas`` optional parameter added.
- ``exportTags`` optional key added to the options table.
-
- Send the content of this query to a remote logger via Protocol Buffer.
- ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
- Since 1.8.0 it is possible to add configurable meta-data fields to the Protocol Buffer message via the ``metas`` parameter, which takes a list of ``name``=``key`` pairs. For each entry in the list, a new value named ``name``
- will be added to the message with the value corresponding to the ``key``. Available keys are:
-
- * ``doh-header:<HEADER>``: the content of the corresponding ``<HEADER>`` HTTP header for DoH queries, empty otherwise
- * ``doh-host``: the ``Host`` header for DoH queries, empty otherwise
- * ``doh-path``: the HTTP path for DoH queries, empty otherwise
- * ``doh-query-string``: the HTTP query string for DoH queries, empty otherwise
- * ``doh-scheme``: the HTTP scheme for DoH queries, empty otherwise
- * ``pool``: the currently selected pool of servers
- * ``proxy-protocol-value:<TYPE>``: the content of the proxy protocol value of type ``<TYPE>``, if any
- * ``proxy-protocol-values``: the content of all proxy protocol values as a "<type1>:<value1>", ..., "<typeN>:<valueN>" strings
- * ``b64-content``: the base64-encoded DNS payload of the current query
- * ``sni``: the Server Name Indication value for queries received over DoT or DoH. Empty otherwise.
- * ``tag:<TAG>``: the content of the corresponding ``<TAG>`` if any
- * ``tags``: the list of all tags, and their values, as a "<key1>:<value1>", ..., "<keyN>:<valueN>" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:".
-
- Subsequent rules are processed after this action.
-
- :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
- :param string alterFunction: Name of a function to modify the contents of the logs before sending
- :param table options: A table with key: value pairs.
- :param table metas: A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message.
-
- Options:
-
- * ``serverID=""``: str - Set the Server Identity field.
- * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
- * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
-
-.. function:: RemoteLogResponseAction(remoteLogger[, alterFunction[, includeCNAME [, options [, metas]]]])
-
- .. versionchanged:: 1.4.0
- ``ipEncryptKey`` optional key added to the options table.
-
- .. versionchanged:: 1.8.0
- ``metas`` optional parameter added.
- ``exportTags`` optional key added to the options table.
-
- Send the content of this response to a remote logger via Protocol Buffer.
- ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
- ``includeCNAME`` indicates whether CNAME records inside the response should be parsed and exported.
- The default is to only exports A and AAAA records.
- Since 1.8.0 it is possible to add configurable meta-data fields to the Protocol Buffer message via the ``metas`` parameter, which takes a list of ``name``=``key`` pairs. See :func:`RemoteLogAction` for the list of available keys.
- Subsequent rules are processed after this action.
-
- :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
- :param string alterFunction: Name of a function to modify the contents of the logs before sending
- :param bool includeCNAME: Whether or not to parse and export CNAMEs. Default false
- :param table options: A table with key: value pairs.
- :param table metas: A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message.
-
- Options:
-
- * ``serverID=""``: str - Set the Server Identity field.
- * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
- * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
-
-.. function:: SetAdditionalProxyProtocolValueAction(type, value)
-
- .. versionadded:: 1.6.0
-
- Add a Proxy-Protocol Type-Length value to be sent to the server along with this query. It does not replace any
- existing value with the same type but adds a new value.
- Be careful that Proxy Protocol values are sent once at the beginning of the TCP connection for TCP and DoT queries.
- That means that values received on an incoming TCP connection will be inherited by subsequent queries received over
- the same incoming TCP connection, if any, but values set to a query will not be inherited by subsequent queries.
- Subsequent rules are processed after this action.
-
- :param int type: The type of the value to send, ranging from 0 to 255 (both included)
- :param str value: The binary-safe value
-
-.. function:: SetDisableECSAction()
-
- .. versionadded:: 1.6.0
-
- Disable the sending of ECS to the backend.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`DisableECSAction` before 1.6.0.
-
-.. function:: SetDisableValidationAction()
-
- .. versionadded:: 1.6.0
-
- Set the CD bit in the query and let it go through.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`DisableValidationAction` before 1.6.0.
-
-.. function:: SetECSAction(v4 [, v6])
-
- Set the ECS prefix and prefix length sent to backends to an arbitrary value.
- If both IPv4 and IPv6 masks are supplied the IPv4 one will be used for IPv4 clients
- and the IPv6 one for IPv6 clients. Otherwise the first mask is used for both, and
- can actually be an IPv6 mask.
- Subsequent rules are processed after this action.
-
- :param string v4: The IPv4 netmask, for example "192.0.2.1/32"
- :param string v6: The IPv6 netmask, if any
-
-.. function:: SetECSOverrideAction(override)
-
- .. versionadded:: 1.6.0
-
- Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
- Subsequent rules are processed after this action.
- Note that this function was called :func:`ECSOverrideAction` before 1.6.0.
-
- :param bool override: Whether or not to override ECS value
-
-.. function:: SetECSPrefixLengthAction(v4, v6)
-
- .. versionadded:: 1.6.0
-
- Set the ECS prefix length.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`ECSPrefixLengthAction` before 1.6.0.
-
- :param int v4: The IPv4 netmask length
- :param int v6: The IPv6 netmask length
-
-.. function:: SetEDNSOptionAction(option)
-
- .. versionadded:: 1.7.0
-
- Add arbitrary EDNS option and data to the query. Any existing EDNS content with the same option code will be overwritten.
- Subsequent rules are processed after this action.
-
- :param int option: The EDNS option number
- :param string data: The EDNS0 option raw content
-
-.. function:: SetMacAddrAction(option)
-
- .. versionadded:: 1.6.0
-
- Add the source MAC address to the query as EDNS0 option ``option``.
- This action is currently only supported on Linux.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`MacAddrAction` before 1.6.0.
-
- :param int option: The EDNS0 option number
-
-.. function:: SetMaxReturnedTTLAction(max)
-
- .. versionadded:: 1.8.0
-
- Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values.
-
- :param int max: The maximum allowed value
-
-.. function:: SetMaxReturnedTTLResponseAction(max)
-
- .. versionadded:: 1.8.0
-
- Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values.
-
- :param int max: The maximum allowed value
-
-.. function:: SetMaxTTLResponseAction(max)
-
- .. versionadded:: 1.8.0
-
- Cap the TTLs of the response to the given maximum.
-
- :param int max: The maximum allowed value
-
-.. function:: SetMinTTLResponseAction(min)
-
- .. versionadded:: 1.8.0
-
- Cap the TTLs of the response to the given minimum.
-
- :param int min: The minimum allowed value
-
-.. function:: SetNoRecurseAction()
-
- .. versionadded:: 1.6.0
-
- Strip RD bit from the question, let it go through.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`NoRecurseAction` before 1.6.0.
-
-.. function:: SetNegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
-
- .. versionadded:: 1.5.0
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`NegativeAndSOAAction` instead.
-
- Turn a question into a response, either a NXDOMAIN or a NODATA one based on ''nxd'', setting the QR bit to 1 and adding a SOA record in the additional section.
-
- :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
- :param string zone: The owner name for the SOA record
- :param int ttl: The TTL of the SOA record
- :param string mname: The mname of the SOA record
- :param string rname: The rname of the SOA record
- :param int serial: The value of the serial field in the SOA record
- :param int refresh: The value of the refresh field in the SOA record
- :param int retry: The value of the retry field in the SOA record
- :param int expire: The value of the expire field in the SOA record
- :param int minimum: The value of the minimum field in the SOA record
- :param table options: A table with key: value pairs with options
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
-
-.. function:: SetProxyProtocolValuesAction(values)
-
- .. versionadded:: 1.5.0
-
- Set the Proxy-Protocol Type-Length values to be sent to the server along with this query to ``values``.
- Subsequent rules are processed after this action.
-
- :param table values: A table of types and values to send, for example: ``{ [0] = foo", [42] = "bar" }``
-
-.. function:: SetReducedTTLResponseAction(percentage)
-
- .. versionadded:: 1.8.0
-
- Reduce the TTL of records in a response to a percentage of the original TTL. For example,
- passing 50 means that the original TTL will be cut in half.
- Subsequent rules are processed after this action.
-
- :param int percentage: The percentage to use
-
-.. function:: SetSkipCacheAction()
-
- .. versionadded:: 1.6.0
-
- Don't lookup the cache for this query, don't store the answer.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`SkipCacheAction` before 1.6.0.
-
-.. function:: SetSkipCacheResponseAction()
-
- .. versionadded:: 1.6.0
-
- Don't store this answer into the cache.
- Subsequent rules are processed after this action.
-
-.. function:: SetTagAction(name, value)
-
- .. versionadded:: 1.6.0
-
- .. versionchanged:: 1.7.0
- Prior to 1.7.0 :func:`SetTagAction` would not overwrite an existing tag value if already set.
-
- Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
- This function will overwrite any existing tag value.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`TagAction` before 1.6.0.
-
- :param string name: The name of the tag to set
- :param string value: The value of the tag
-
-.. function:: SetTagResponseAction(name, value)
-
- .. versionadded:: 1.6.0
-
- .. versionchanged:: 1.7.0
- Prior to 1.7.0 :func:`SetTagResponseAction` would not overwrite an existing tag value if already set.
-
- Associate a tag named ``name`` with a value of ``value`` to this response.
- This function will overwrite any existing tag value.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`TagResponseAction` before 1.6.0.
-
- :param string name: The name of the tag to set
- :param string value: The value of the tag
-
-.. function:: SetTempFailureCacheTTLAction(ttl)
-
- .. versionadded:: 1.6.0
-
- Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
- Subsequent rules are processed after this action.
- Note that this function was called :func:`TempFailureCacheTTLAction` before 1.6.0.
-
- :param int ttl: Cache TTL for temporary failure replies
-
-.. function:: SkipCacheAction()
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetSkipAction` instead.
-
- Don't lookup the cache for this query, don't store the answer.
- Subsequent rules are processed after this action.
-
-.. function:: SNMPTrapAction([message])
-
- Send an SNMP trap, adding the optional ``message`` string as the query description.
- Subsequent rules are processed after this action.
-
- :param string message: The message to include
-
-.. function:: SNMPTrapResponseAction([message])
-
- Send an SNMP trap, adding the optional ``message`` string as the query description.
- Subsequent rules are processed after this action.
-
- :param string message: The message to include
-
-.. function:: SpoofAction(ip [, options])
- SpoofAction(ips [, options])
-
- .. versionchanged:: 1.5.0
- Added the optional parameter ``options``.
-
- .. versionchanged:: 1.6.0
- Up to 1.6.0, the syntax for this function was ``SpoofAction(ips[, ip[, options]])``.
-
- Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
- If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
-
- :param string ip: An IPv4 and/or IPv6 address to spoof
- :param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
- * ``ttl``: int - The TTL of the record.
-
-.. function:: SpoofCNAMEAction(cname [, options])
-
- .. versionchanged:: 1.5.0
- Added the optional parameter ``options``.
-
- Forge a response with the specified CNAME value.
-
- :param string cname: The name to respond with
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
- * ``ttl``: int - The TTL of the record.
-
-.. function:: SpoofRawAction(rawAnswer [, options])
- SpoofRawAction(rawAnswers [, options])
-
- .. versionadded:: 1.5.0
-
- .. versionchanged:: 1.6.0
- Up to 1.6.0, it was only possible to spoof one answer.
-
- Forge a response with the specified raw bytes as record data.
-
- .. code-block:: Lua
-
- -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with both a "aaa" "bbbb" and "ccc" TXT record:
- addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction({"\003aaa\004bbbb", "\003ccc"}))
- -- select queries for the 'raw-srv.powerdns.com.' name and SRV type, and answer with a '0 0 65535 srv.powerdns.com.' SRV record, setting the AA bit to 1 and the TTL to 3600s
- addAction(AndRule({QNameRule('raw-srv.powerdns.com.'), QTypeRule(DNSQType.SRV)}), SpoofRawAction("\000\000\000\000\255\255\003srv\008powerdns\003com\000", { aa=true, ttl=3600 }))
- -- select reverse queries for '127.0.0.1' and answer with 'localhost'
- addAction(AndRule({QNameRule('1.0.0.127.in-addr.arpa.'), QTypeRule(DNSQType.PTR)}), SpoofRawAction("\009localhost\000"))
-
- :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``.
-
- ``sdig dumpluaraw`` and ``pdnsutil raw-lua-from-content`` from PowerDNS can generate raw answers for you:
-
- .. code-block:: Shell
-
- $ pdnsutil raw-lua-from-content SRV '0 0 65535 srv.powerdns.com.'
- "\000\000\000\000\255\255\003srv\008powerdns\003com\000"
- $ sdig 127.0.0.1 53 open-xchange.com MX recurse dumpluaraw
- Reply to question for qname='open-xchange.com.', qtype=MX
- Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
- 0 open-xchange.com. IN MX "\000c\004mx\049\049\012open\045xchange\003com\000"
- 0 open-xchange.com. IN MX "\000\010\003mx\049\012open\045xchange\003com\000"
- 0 open-xchange.com. IN MX "\000\020\003mx\050\012open\045xchange\003com\000"
-
- :param string rawAnswer: The raw record data
- :param {string} rawAnswers: A table of raw record data to spoof
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
- * ``ttl``: int - The TTL of the record.
-
-.. function:: SpoofSVCAction(svcParams [, options])
-
- .. versionadded:: 1.7.0
-
- Forge a response with the specified SVC record data. If the list contains more than one class:`SVCRecordParameters` (generated via :func:`newSVCRecordParameters`) object, they are all returned,
- and should have different priorities.
- The hints provided in the SVC parameters, if any, will also be added as A/AAAA records in the additional section, using the target name present in the parameters as owner name if it's not empty (root) and the qname instead.
-
- :param list of class:`SVCRecordParameters` svcParams: The record data to return
- :param table options: A table with key: value pairs with options.
-
- Options:
-
- * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
- * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
- * ``ttl``: int - The TTL of the record.
-
-.. function:: SpoofPacketAction(rawPacket, len)
-
- .. versionadded:: 1.8.0
-
- Spoof a raw self-generated answer
-
- :param string rawPacket: The raw wire-ready DNS answer
- :param int len: The length of the packet
-
-.. function:: TagAction(name, value)
-
- .. deprecated:: 1.6.0
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTagAction` instead.
-
- Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
- Subsequent rules are processed after this action.
-
- :param string name: The name of the tag to set
- :param string value: The value of the tag
-
-.. function:: TagResponseAction(name, value)
-
- .. deprecated:: 1.6.0
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTagResponseAction` instead.
-
- Associate a tag named ``name`` with a value of ``value`` to this response.
- Subsequent rules are processed after this action.
-
- :param string name: The name of the tag to set
- :param string value: The value of the tag
-
-.. function:: TCAction()
-
- .. versionchanged:: 1.7.0
- This action is now only performed over UDP transports.
-
- Create answer to query with the TC bit set, and the RA bit set to the value of RD in the query, to force the client to TCP.
- Before 1.7.0 this action was performed even when the query had been received over TCP, which required the use of :func:`TCPRule` to
- prevent the TC bit from being set over TCP transports.
-
-.. function:: TeeAction(remote[, addECS[, local]])
-
- .. versionchanged:: 1.8.0
- Added the optional parameter ``local``.
-
- Send copy of query to ``remote``, keep stats on responses.
- If ``addECS`` is set to true, EDNS Client Subnet information will be added to the query.
- If ``local`` has provided a value like "192.0.2.53", :program:`dnsdist` will try binding that address as local address when sending the queries.
- Subsequent rules are processed after this action.
-
- :param string remote: An IP:PORT combination to send the copied queries to
- :param bool addECS: Whether or not to add ECS information. Default false
-
-.. function:: TempFailureCacheTTLAction(ttl)
-
- .. deprecated:: 1.6.0
-
- This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTempFailureCacheTTLAction` instead.
-
- Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
- Subsequent rules are processed after this action.
-
- :param int ttl: Cache TTL for temporary failure replies
+See :doc:`reference/rules-management` for more information.
queries - noncompliant-queries
=
- responses - noncompliant-responses + cache-hits + downstream-timeouts + self-answered + no-policy
- + rule-drop
+ responses - noncompliant-responses + downstream-timeouts + no-policy + rule-drop
+
+Before 1.8.0, cache hits and self-answered responses were not accounted in the responses counters, so the relation was:
+
+ responses - noncompliant-responses + cache-hits + downstream-timeouts + self-answered + no-policy + rule-drop
Note that packets dropped by eBPF (see :doc:`../advanced/ebpf`) are
accounted for in the eBPF statistics, and do not show up in the metrics
----------------------
Number of responses dropped because the internal DoH pipe was full.
+doq-response-pipe-full
+----------------------
+Number of responses dropped because the internal DoQ pipe was full.
+
downstream-send-errors
----------------------
Number of errors when sending a query to a backend.
latency-avg100
--------------
-Average response latency in microseconds of the last 100 packets
+Average response latency in microseconds of the last 100 packets received over UDP.
latency-avg1000
---------------
-Average response latency in microseconds of the last 1000 packets.
+Average response latency in microseconds of the last 1000 packets received over UDP.
latency-avg10000
----------------
-Average response latency in microseconds of the last 10000 packets.
+Average response latency in microseconds of the last 10000 packets received over UDP.
latency-avg1000000
------------------
-Average response latency in microseconds of the last 1000000 packets.
+Average response latency in microseconds of the last 1000000 packets received over UDP.
latency-bucket
--------------
-Histogram of response time latencies.
+Histogram of response time latencies for queries received over UDP.
latency-count
-------------
--------------------
Average response latency, in microseconds, of the last 10000 packets received over DoH.
-latency-doh-avg100000
----------------------
-Average response latency, in microseconds, of the last 100000 packets received over DoH.
+latency-doh-avg1000000
+----------------------
+Average response latency, in microseconds, of the last 1000000 packets received over DoH.
+
+latency-doq-avg100
+------------------
+Average response latency, in microseconds, of the last 100 packets received over DoQ.
+
+latency-doq-avg1000
+-------------------
+Average response latency, in microseconds, of the last 1000 packets received over DoQ.
+
+latency-doq-avg10000
+--------------------
+Average response latency, in microseconds, of the last 10000 packets received over DoQ.
+
+latency-doq-avg1000000
+----------------------
+Average response latency, in microseconds, of the last 1000000 packets received over DoQ.
latency-dot-avg100
------------------
latency-slow
------------
-Number of queries answered in more than 1 second.
+Number of queries received over UDP answered in more than 1 second.
latency-sum
-----------
-Total response time of all queries combined in milliseconds since the start of :program:`dnsdist`. Can be used to calculate the
-average response time over all queries.
+Total response time of all queries received over UDP combined in milliseconds since the start of :program:`dnsdist`. Can be used to calculate the average response time over all queries received over UDP.
latency-tcp-avg100
------------------
latency0-1
----------
-Number of queries answered in less than 1 ms.
+Number of queries received over UDP answered in less than 1 ms.
latency1-10
-----------
-Number of queries answered in 1-10 ms.
+Number of queries received over UDP answered in 1-10 ms.
latency10-50
------------
-Number of queries answered in 10-50 ms.
+Number of queries received over UDP answered in 10-50 ms.
latency50-100
-------------
-Number of queries answered in 50-100 ms.
+Number of queries received over UDP answered in 50-100 ms.
latency100-1000
---------------
-Number of queries answered in 100-1000 ms.
+Number of queries received over UDP answered in 100-1000 ms.
no-policy
---------
responses
---------
-Number of responses received from backends. Note! This is not the number of
-responses sent to clients. To get that number, add 'cache-hits' and
-'responses'.
+Number of response sent to clients.
+
+Before 1.8.0, it was the number of responses received from backends, not accounting for cache hits or self-answered responses.
rule-drop
---------
Upgrade Guide
=============
+1.8.x to 1.9.0
+--------------
+
+dnsdist now supports a new library for dealing with incoming DNS over HTTPS queries: ``nghttp2``. The previously used library, ``h2o``, can still be used
+but is now deprecated, disabled by default (see ``--with-h2o`` to enable it back) and will be removed in the future, as it is unfortunately no longer maintained in a way that is suitable for use as a library
+(see https://github.com/h2o/h2o/issues/3230). See the ``library`` parameter on the :func:`addDOHLocal` directive for more information on how to select
+the library used when dnsdist is built with support for both ``h2o`` and ``nghttp2``. The default is now ``nghttp2`` whenever possible.
+Note that ``nghttp2`` only supports HTTP/2, and not HTTP/1, while ``h2o`` supported both. This is not an issue for actual DNS over HTTPS clients that
+support HTTP/2, but might be one in setups running dnsdist behind a reverse-proxy that does not support HTTP/2. See :doc:`guides/dns-over-https` for some work-around.
+
+SNMP support is no longer enabled by default during ``configure``, requiring ``--with-net-snmp`` to be built.
+
+The use of :func:`makeRule` is now deprecated, please use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead.
+Passing a string or list of strings instead of a :class:`DNSRule` to these functions is deprecated as well, :func:`NetmaskGroupRule` and :func:`QNameSuffixRule` should there again be used instead:
+
+* :func:`addAction`
+* :func:`addResponseAction`
+* :func:`addCacheHitResponseAction`
+* :func:`addCacheInsertedResponseAction`
+* :func:`addSelfAnsweredResponseAction`
+
1.7.x to 1.8.0
--------------
Cache-hits are now counted as responses in our metrics.
+Cache hits are now inserted into the in-memory ring buffers, while before 1.8.0 only cache misses were inserted. This has a very noticeable impact on :doc:`guides/dynblocks` since cache hits now considered when computing the rcode rates and ratios, as well as the response bandwidth rate.
+
The :func:`setMaxTCPConnectionsPerClient` limit is now properly applied to DNS over HTTPS connections, in addition to DNS over TCP and DNS over TLS ones.
The configuration check will now fail if the configuration file does not exist. For this reason we now create a default configuration file, based on the file previously called ``dnsdistconf.lua``, which contains commented-out examples of how to set up dnsdist.
#include "doh.hh"
#ifdef HAVE_DNS_OVER_HTTPS
+#ifdef HAVE_LIBH2OEVLOOP
#define H2O_USE_EPOLL 1
#include <cerrno>
#include "dns.hh"
#include "dolog.hh"
#include "dnsdist-concurrent-connections.hh"
+#include "dnsdist-dnsparser.hh"
#include "dnsdist-ecs.hh"
+#include "dnsdist-metrics.hh"
#include "dnsdist-proxy-protocol.hh"
#include "dnsdist-rules.hh"
#include "dnsdist-xpf.hh"
*/
/* 'Intermediate' compatibility from https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 */
-#define DOH_DEFAULT_CIPHERS "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
+static constexpr string_view DOH_DEFAULT_CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS";
class DOHAcceptContext
{
d_rotatingTicketsKey.clear();
}
DOHAcceptContext(const DOHAcceptContext&) = delete;
+ DOHAcceptContext(DOHAcceptContext&&) = delete;
DOHAcceptContext& operator=(const DOHAcceptContext&) = delete;
+ DOHAcceptContext& operator=(DOHAcceptContext&&) = delete;
h2o_accept_ctx_t* get()
{
d_h2o_accept_ctx.ssl_ctx = nullptr;
}
- void decrementConcurrentConnections()
+ void decrementConcurrentConnections() const
{
if (d_cs != nullptr) {
--d_cs->tcpCurrentConnections;
}
}
- time_t getNextTicketsKeyRotation() const
+ [[nodiscard]] time_t getNextTicketsKeyRotation() const
{
return d_ticketsKeyNextRotation;
}
- size_t getTicketsKeysCount() const
+ [[nodiscard]] size_t getTicketsKeysCount() const
{
size_t res = 0;
if (d_ticketKeys) {
std::map<int, std::string> d_ocspResponses;
std::unique_ptr<OpenSSLTLSTicketKeysRing> d_ticketKeys{nullptr};
- std::unique_ptr<FILE, int(*)(FILE*)> d_keyLogFile{nullptr, fclose};
+ // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)
+ pdns::UniqueFilePtr d_keyLogFile{nullptr};
ClientState* d_cs{nullptr};
time_t d_ticketsKeyRotationDelay{0};
private:
- h2o_accept_ctx_t d_h2o_accept_ctx;
- std::atomic<uint64_t> d_refcnt{1};
+ h2o_accept_ctx_t d_h2o_accept_ctx{};
time_t d_ticketsKeyNextRotation{0};
std::atomic_flag d_rotatingTicketsKey;
};
+struct DOHUnit;
+
// we create one of these per thread, and pass around a pointer to it
// through the bowels of h2o
struct DOHServerConfig
{
DOHServerConfig(uint32_t idleTimeout, uint32_t internalPipeBufferSize): accept_ctx(std::make_shared<DOHAcceptContext>())
{
- int fd[2];
#ifndef USE_SINGLE_ACCEPTOR_THREAD
- if (pipe(fd) < 0) {
- unixDie("Creating a pipe for DNS over HTTPS");
- }
- dohquerypair[0] = fd[1];
- dohquerypair[1] = fd[0];
-
- setNonBlocking(dohquerypair[0]);
- if (internalPipeBufferSize > 0) {
- setPipeBufferSize(dohquerypair[0], internalPipeBufferSize);
+ {
+ auto [sender, receiver] = pdns::channel::createObjectQueue<DOHUnit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverBlocking, internalPipeBufferSize);
+ d_querySender = std::move(sender);
+ d_queryReceiver = std::move(receiver);
}
#endif /* USE_SINGLE_ACCEPTOR_THREAD */
- if (pipe(fd) < 0) {
-#ifndef USE_SINGLE_ACCEPTOR_THREAD
- close(dohquerypair[0]);
- close(dohquerypair[1]);
-#endif /* USE_SINGLE_ACCEPTOR_THREAD */
- unixDie("Creating a pipe for DNS over HTTPS");
- }
-
- dohresponsepair[0] = fd[1];
- dohresponsepair[1] = fd[0];
-
- setNonBlocking(dohresponsepair[0]);
- if (internalPipeBufferSize > 0) {
- setPipeBufferSize(dohresponsepair[0], internalPipeBufferSize);
+ {
+ auto [sender, receiver] = pdns::channel::createObjectQueue<DOHUnit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, internalPipeBufferSize);
+ d_responseSender = std::move(sender);
+ d_responseReceiver = std::move(receiver);
}
- setNonBlocking(dohresponsepair[1]);
-
h2o_config_init(&h2o_config);
- h2o_config.http2.idle_timeout = idleTimeout * 1000;
+ h2o_config.http2.idle_timeout = static_cast<uint64_t>(idleTimeout) * 1000;
/* if you came here for a way to make the number of concurrent streams (concurrent requests per connection)
configurable, or even just bigger, I have bad news for you.
h2o_config.http2.max_concurrent_requests_per_connection (default of 100) is capped by
*/
}
DOHServerConfig(const DOHServerConfig&) = delete;
+ DOHServerConfig(DOHServerConfig&&) = delete;
DOHServerConfig& operator=(const DOHServerConfig&) = delete;
+ DOHServerConfig& operator=(DOHServerConfig&&) = delete;
+ ~DOHServerConfig() = default;
LocalHolders holders;
std::set<std::string, std::less<>> paths;
- h2o_globalconf_t h2o_config;
- h2o_context_t h2o_ctx;
+ h2o_globalconf_t h2o_config{};
+ h2o_context_t h2o_ctx{};
std::shared_ptr<DOHAcceptContext> accept_ctx{nullptr};
- ClientState* cs{nullptr};
- std::shared_ptr<DOHFrontend> df{nullptr};
+ ClientState* clientState{nullptr};
+ std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
#ifndef USE_SINGLE_ACCEPTOR_THREAD
- int dohquerypair[2]{-1,-1};
+ pdns::channel::Sender<DOHUnit> d_querySender;
+ pdns::channel::Receiver<DOHUnit> d_queryReceiver;
#endif /* USE_SINGLE_ACCEPTOR_THREAD */
- int dohresponsepair[2]{-1,-1};
+ pdns::channel::Sender<DOHUnit> d_responseSender;
+ pdns::channel::Receiver<DOHUnit> d_responseReceiver;
};
-/* This internal function sends back the object to the main thread to send a reply.
- The caller should NOT release or touch the unit after calling this function */
-static void sendDoHUnitToTheMainThread(DOHUnitUniquePtr&& du, const char* description)
+struct DOHUnit : public DOHUnitInterface
{
- /* taking a naked pointer since we are about to send that pointer over a pipe */
- auto ptr = du.release();
- /* increasing the reference counter. This should not be strictly needed because
- we already hold a reference and will only release it if we failed to send the
- pointer over the pipe, but TSAN seems confused when the responder thread gets
- a reply from a backend before the send() syscall sending the corresponding query
- to that backend has returned in the initial thread.
- The memory barrier needed to increase that counter seems to work around that.
+ DOHUnit(PacketBuffer&& query_, std::string&& path_, std::string&& host_): path(std::move(path_)), host(std::move(host_)), query(std::move(query_))
+ {
+ ids.ednsAdded = false;
+ }
+ ~DOHUnit() override
+ {
+ if (self != nullptr) {
+ *self = nullptr;
+ }
+ }
+
+ DOHUnit(const DOHUnit&) = delete;
+ DOHUnit(DOHUnit&&) = delete;
+ DOHUnit& operator=(const DOHUnit&) = delete;
+ DOHUnit& operator=(DOHUnit&&) = delete;
+
+ InternalQueryState ids;
+ std::string sni;
+ std::string path;
+ std::string scheme;
+ std::string host;
+ std::string contentType;
+ PacketBuffer query;
+ PacketBuffer response;
+ std::unique_ptr<std::unordered_map<std::string, std::string>> headers;
+ st_h2o_req_t* req{nullptr};
+ DOHUnit** self{nullptr};
+ DOHServerConfig* dsc{nullptr};
+ pdns::channel::Sender<DOHUnit>* responseSender{nullptr};
+ size_t query_at{0};
+ int rsock{-1};
+ /* the status_code is set from
+ processDOHQuery() (which is executed in
+ the DOH client thread) so that the correct
+ response can be sent in on_dnsdist(),
+ after the DOHUnit has been passed back to
+ the main DoH thread.
*/
- ptr->get();
- static_assert(sizeof(ptr) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
+ uint16_t status_code{200};
+ /* whether the query was re-sent to the backend over
+ TCP after receiving a truncated answer over UDP */
+ bool tcp{false};
+ bool truncated{false};
+
+ [[nodiscard]] std::string getHTTPPath() const override;
+ [[nodiscard]] std::string getHTTPQueryString() const override;
+ [[nodiscard]] const std::string& getHTTPHost() const override;
+ [[nodiscard]] const std::string& getHTTPScheme() const override;
+ [[nodiscard]] const std::unordered_map<std::string, std::string>& getHTTPHeaders() const override;
+ void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType="") override;
+ void handleTimeout() override;
+ void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, [[maybe_unused]] const std::shared_ptr<DownstreamState>& downstream) override;
+};
+using DOHUnitUniquePtr = std::unique_ptr<DOHUnit>;
- ssize_t sent = write(ptr->rsock, &ptr, sizeof(ptr));
- if (sent != sizeof(ptr)) {
- if (errno == EAGAIN || errno == EWOULDBLOCK) {
- ++g_stats.dohResponsePipeFull;
+/* This internal function sends back the object to the main thread to send a reply.
+ The caller should NOT release or touch the unit after calling this function */
+static void sendDoHUnitToTheMainThread(DOHUnitUniquePtr&& dohUnit, const char* description)
+{
+ if (dohUnit->responseSender == nullptr) {
+ return;
+ }
+ try {
+ if (!dohUnit->responseSender->send(std::move(dohUnit))) {
+ ++dnsdist::metrics::g_stats.dohResponsePipeFull;
vinfolog("Unable to pass a %s to the DoH worker thread because the pipe is full", description);
}
- else {
- vinfolog("Unable to pass a %s to the DoH worker thread because we couldn't write to the pipe: %s", description, stringerror());
- }
-
- /* we fail to write over the pipe so we do not need to hold to that ref anymore */
- ptr->release();
+ } catch (const std::exception& e) {
+ vinfolog("Unable to pass a %s to the DoH worker thread because we couldn't write to the pipe: %s", description, e.what());
}
- /* we decrement the counter incremented above at the beginning of that function */
- ptr->release();
}
/* This function is called from other threads than the main DoH one,
- instructing it to send a 502 error to the client.
- It takes ownership of the unit. */
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU)
+ instructing it to send a 502 error to the client. */
+void DOHUnit::handleTimeout()
{
- if (oldDU == nullptr) {
- return;
- }
-
- /* we are about to erase an existing DU */
- oldDU->status_code = 502;
-
- sendDoHUnitToTheMainThread(std::move(oldDU), "DoH timeout");
+ status_code = 502;
+ sendDoHUnitToTheMainThread(std::unique_ptr<DOHUnit>(this), "DoH timeout");
}
struct DOHConnection
static void on_socketclose(void *data)
{
- auto conn = reinterpret_cast<DOHConnection*>(data);
+ auto* conn = static_cast<DOHConnection*>(data);
if (conn != nullptr) {
if (conn->d_acceptCtx) {
- struct timeval now;
+ struct timeval now{};
gettimeofday(&now, nullptr);
auto diff = now - conn->d_connectionStartTime;
};
static const std::string unknown = "Unknown";
- const auto it = reasons.find(statusCode);
- if (it == reasons.end()) {
+ const auto reasonIt = reasons.find(statusCode);
+ if (reasonIt == reasons.end()) {
return unknown;
}
- else {
- return it->second;
- }
+ return reasonIt->second;
}
/* Always called from the main DoH thread */
-static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCode, const PacketBuffer& response, const std::unordered_map<std::string, std::string>& customResponseHeaders, const std::string& contentType, bool addContentType)
+static void handleResponse(DOHFrontend& dohFrontend, st_h2o_req_t* req, uint16_t statusCode, const PacketBuffer& response, const std::unordered_map<std::string, std::string>& customResponseHeaders, const std::string& contentType, bool addContentType)
{
constexpr int overwrite_if_exists = 1;
constexpr int maybe_token = 1;
}
if (statusCode == 200) {
- ++df.d_validresponses;
+ ++dohFrontend.d_validresponses;
req->res.status = 200;
if (addContentType) {
}
else {
/* we need to duplicate the header content because h2o keeps a pointer and we will be deleted before the response has been sent */
- h2o_iovec_t ct = h2o_strdup(&req->pool, contentType.c_str(), contentType.size());
- h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, ct.base, ct.len);
+ h2o_iovec_t contentTypeVect = h2o_strdup(&req->pool, contentType.c_str(), contentType.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, contentTypeVect.base, contentTypeVect.len);
}
}
- if (df.d_sendCacheControlHeaders && response.size() > sizeof(dnsheader)) {
+ if (dohFrontend.d_sendCacheControlHeaders && response.size() > sizeof(dnsheader)) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
uint32_t minTTL = getDNSPacketMinTTL(reinterpret_cast<const char*>(response.data()), response.size());
if (minTTL != std::numeric_limits<uint32_t>::max()) {
std::string cacheControlValue = "max-age=" + std::to_string(minTTL);
}
req->res.content_length = response.size();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
h2o_send_inline(req, reinterpret_cast<const char*>(response.data()), response.size());
}
else if (statusCode >= 300 && statusCode < 400) {
/* in that case the response is actually a URL */
/* we need to duplicate the URL because h2o uses it for the location header, keeping a pointer, and we will be deleted before the response has been sent */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
h2o_iovec_t url = h2o_strdup(&req->pool, reinterpret_cast<const char*>(response.data()), response.size());
h2o_send_redirect(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), url.base, url.len);
- ++df.d_redirectresponses;
+ ++dohFrontend.d_redirectresponses;
}
else {
// we need to make sure it's null-terminated */
if (!response.empty() && response.at(response.size() - 1) == 0) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
h2o_send_error_generic(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), reinterpret_cast<const char*>(response.data()), H2O_SEND_ERROR_KEEP_HEADERS);
}
else {
h2o_send_error_400(req, getReasonFromStatusCode(statusCode).c_str(), "invalid DNS query" , 0);
break;
case 403:
- h2o_send_error_403(req, getReasonFromStatusCode(statusCode).c_str(), "dns query not allowed", 0);
+ h2o_send_error_403(req, getReasonFromStatusCode(statusCode).c_str(), "DoH query not allowed", 0);
break;
case 502:
h2o_send_error_502(req, getReasonFromStatusCode(statusCode).c_str(), "no downstream server available", 0);
}
}
- ++df.d_errorresponses;
+ ++dohFrontend.d_errorresponses;
}
}
-class DoHTCPCrossQuerySender : public TCPQuerySender
+static std::unique_ptr<DOHUnit> getDUFromIDS(InternalQueryState& ids)
{
-public:
- DoHTCPCrossQuerySender()
- {
- }
+ auto dohUnit = std::unique_ptr<DOHUnit>(dynamic_cast<DOHUnit*>(ids.du.release()));
+ return dohUnit;
+}
- bool active() const override
+class DoHTCPCrossQuerySender final : public TCPQuerySender
+{
+public:
+ DoHTCPCrossQuerySender() = default;
+ DoHTCPCrossQuerySender(const DoHTCPCrossQuerySender&) = delete;
+ DoHTCPCrossQuerySender(DoHTCPCrossQuerySender&&) = delete;
+ DoHTCPCrossQuerySender& operator=(const DoHTCPCrossQuerySender&) = delete;
+ DoHTCPCrossQuerySender& operator=(DoHTCPCrossQuerySender&&) = delete;
+ ~DoHTCPCrossQuerySender() final = default;
+
+ [[nodiscard]] bool active() const override
{
return true;
}
return;
}
- auto du = std::move(response.d_idstate.du);
- if (du->rsock == -1) {
+ auto dohUnit = getDUFromIDS(response.d_idstate);
+ if (dohUnit->responseSender == nullptr) {
return;
}
- du->response = std::move(response.d_buffer);
- du->ids = std::move(response.d_idstate);
- DNSResponse dr(du->ids, du->response, du->downstream);
+ dohUnit->response = std::move(response.d_buffer);
+ dohUnit->ids = std::move(response.d_idstate);
+ DNSResponse dr(dohUnit->ids, dohUnit->response, dohUnit->downstream);
- dnsheader cleartextDH;
- memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
+ dnsheader cleartextDH{};
+ memcpy(&cleartextDH, dr.getHeader().get(), sizeof(cleartextDH));
if (!response.isAsync()) {
- static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
- static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
- dr.ids.du = std::move(du);
+ dr.ids.du = std::move(dohUnit);
- if (!processResponse(dr.ids.du->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dr, false)) {
+ if (!processResponse(dynamic_cast<DOHUnit*>(dr.ids.du.get())->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dr, false)) {
if (dr.ids.du) {
- dr.ids.du->status_code = 503;
- sendDoHUnitToTheMainThread(std::move(dr.ids.du), "Response dropped by rules");
+ dohUnit = getDUFromIDS(dr.ids);
+ dohUnit->status_code = 503;
+ sendDoHUnitToTheMainThread(std::move(dohUnit), "Response dropped by rules");
}
return;
}
return;
}
- du = std::move(dr.ids.du);
+ dohUnit = getDUFromIDS(dr.ids);
}
- if (!du->ids.selfGenerated) {
- double udiff = du->ids.queryRealTime.udiff();
- vinfolog("Got answer from %s, relayed to %s (https), took %f us", du->downstream->d_config.remote.toStringWithPort(), du->ids.origRemote.toStringWithPort(), udiff);
+ if (!dohUnit->ids.selfGenerated) {
+ double udiff = dohUnit->ids.queryRealTime.udiff();
+ vinfolog("Got answer from %s, relayed to %s (https), took %f us", dohUnit->downstream->d_config.remote.toStringWithPort(), dohUnit->ids.origRemote.toStringWithPort(), udiff);
- auto backendProtocol = du->downstream->getProtocol();
- if (backendProtocol == dnsdist::Protocol::DoUDP && du->tcp) {
+ auto backendProtocol = dohUnit->downstream->getProtocol();
+ if (backendProtocol == dnsdist::Protocol::DoUDP && dohUnit->tcp) {
backendProtocol = dnsdist::Protocol::DoTCP;
}
- handleResponseSent(du->ids, udiff, du->ids.origRemote, du->downstream->d_config.remote, du->response.size(), cleartextDH, backendProtocol, true);
+ handleResponseSent(dohUnit->ids, udiff, dohUnit->ids.origRemote, dohUnit->downstream->d_config.remote, dohUnit->response.size(), cleartextDH, backendProtocol, true);
}
- ++g_stats.responses;
- if (du->ids.cs) {
- ++du->ids.cs->responses;
+ ++dnsdist::metrics::g_stats.responses;
+ if (dohUnit->ids.cs != nullptr) {
+ ++dohUnit->ids.cs->responses;
}
- sendDoHUnitToTheMainThread(std::move(du), "cross-protocol response");
+ sendDoHUnitToTheMainThread(std::move(dohUnit), "cross-protocol response");
}
void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
return handleResponse(now, std::move(response));
}
- void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
+ void notifyIOError(const struct timeval& now, TCPResponse&& response) override
{
+ auto& query = response.d_idstate;
if (!query.du) {
return;
}
- if (query.du->rsock == -1) {
+ auto dohUnit = getDUFromIDS(query);
+ if (dohUnit->responseSender == nullptr) {
return;
}
- auto du = std::move(query.du);
- du->ids = std::move(query);
- du->status_code = 502;
- sendDoHUnitToTheMainThread(std::move(du), "cross-protocol error response");
+ dohUnit->ids = std::move(query);
+ dohUnit->status_code = 502;
+ sendDoHUnitToTheMainThread(std::move(dohUnit), "cross-protocol error response");
}
};
class DoHCrossProtocolQuery : public CrossProtocolQuery
{
public:
- DoHCrossProtocolQuery(DOHUnitUniquePtr&& du, bool isResponse)
+ DoHCrossProtocolQuery(DOHUnitUniquePtr&& dohUnit, bool isResponse)
{
if (isResponse) {
/* happens when a response becomes async */
- query = InternalQuery(std::move(du->response), std::move(du->ids));
+ query = InternalQuery(std::move(dohUnit->response), std::move(dohUnit->ids));
}
else {
/* we need to duplicate the query here because we might need
the existing query later if we get a truncated answer */
- query = InternalQuery(PacketBuffer(du->query), std::move(du->ids));
+ query = InternalQuery(PacketBuffer(dohUnit->query), std::move(dohUnit->ids));
}
- /* it might have been moved when we moved du->ids */
- if (du) {
- query.d_idstate.du = std::move(du);
+ /* it might have been moved when we moved dohUnit->ids */
+ if (dohUnit) {
+ query.d_idstate.du = std::move(dohUnit);
}
/* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload,
- clearing query.d_proxyProtocolPayloadAdded and du->proxyProtocolPayloadSize.
+ clearing query.d_proxyProtocolPayloadAdded and dohUnit->proxyProtocolPayloadSize.
Leave it for now because we know that the onky case where the payload has been
added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT,
and we know the TCP/DoT code can handle it. */
- query.d_proxyProtocolPayloadAdded = query.d_idstate.du->proxyProtocolPayloadSize > 0;
+ query.d_proxyProtocolPayloadAdded = query.d_idstate.d_proxyProtocolPayloadSize > 0;
downstream = query.d_idstate.du->downstream;
- proxyProtocolPayloadSize = query.d_idstate.du->proxyProtocolPayloadSize;
}
void handleInternalError()
{
- query.d_idstate.du->status_code = 502;
- sendDoHUnitToTheMainThread(std::move(query.d_idstate.du), "DoH internal error");
+ auto dohUnit = getDUFromIDS(query.d_idstate);
+ if (dohUnit == nullptr) {
+ return;
+ }
+ dohUnit->status_code = 502;
+ sendDoHUnitToTheMainThread(std::move(dohUnit), "DoH internal error");
}
std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
{
- query.d_idstate.du->downstream = downstream;
+ auto* unit = dynamic_cast<DOHUnit*>(query.d_idstate.du.get());
+ if (unit != nullptr) {
+ unit->downstream = downstream;
+ }
return s_sender;
}
return dr;
}
- DOHUnitUniquePtr&& releaseDU()
+ DOHUnitUniquePtr releaseDU()
{
- return std::move(query.d_idstate.du);
+ return getDUFromIDS(query.d_idstate);
}
private:
throw std::runtime_error("Trying to create a DoH cross protocol query without a valid DoH unit");
}
- auto du = std::move(dq.ids.du);
- if (&dq.ids != &du->ids) {
- du->ids = std::move(dq.ids);
+ auto dohUnit = getDUFromIDS(dq.ids);
+ if (&dq.ids != &dohUnit->ids) {
+ dohUnit->ids = std::move(dq.ids);
}
- du->ids.origID = dq.getHeader()->id;
+ dohUnit->ids.origID = dq.getHeader()->id;
if (!isResponse) {
- if (du->query.data() != dq.getMutableData().data()) {
- du->query = std::move(dq.getMutableData());
+ if (dohUnit->query.data() != dq.getMutableData().data()) {
+ dohUnit->query = std::move(dq.getMutableData());
}
}
else {
- if (du->response.data() != dq.getMutableData().data()) {
- du->response = std::move(dq.getMutableData());
+ if (dohUnit->response.data() != dq.getMutableData().data()) {
+ dohUnit->response = std::move(dq.getMutableData());
}
}
- return std::make_unique<DoHCrossProtocolQuery>(std::move(du), isResponse);
+ return std::make_unique<DoHCrossProtocolQuery>(std::move(dohUnit), isResponse);
}
/*
*/
static void processDOHQuery(DOHUnitUniquePtr&& unit, bool inMainThread = false)
{
- const auto handleImmediateResponse = [inMainThread](DOHUnitUniquePtr&& du, const char* reason) {
+ const auto handleImmediateResponse = [inMainThread](DOHUnitUniquePtr&& dohUnit, const char* reason) {
if (inMainThread) {
- handleResponse(*du->dsc->df, du->req, du->status_code, du->response, du->dsc->df->d_customResponseHeaders, du->contentType, true);
+ handleResponse(*dohUnit->dsc->dohFrontend, dohUnit->req, dohUnit->status_code, dohUnit->response, dohUnit->dsc->dohFrontend->d_customResponseHeaders, dohUnit->contentType, true);
/* so the unique pointer is stored in the InternalState which itself is stored in the unique pointer itself. We likely need
a better design, but for now let's just reset the internal one since we know it is no longer needed. */
- du->ids.du.reset();
+ dohUnit->ids.du.reset();
}
else {
- sendDoHUnitToTheMainThread(std::move(du), reason);
+ sendDoHUnitToTheMainThread(std::move(dohUnit), reason);
}
};
auto& ids = unit->ids;
- ids.du = std::move(unit);
- auto& du = ids.du;
uint16_t queryId = 0;
ComboAddress remote;
try {
- if (!du->req) {
+ if (unit->req == nullptr) {
// we got closed meanwhile. XXX small race condition here
- // but we should be fine as long as we don't touch du->req
+ // but we should be fine as long as we don't touch dohUnit->req
// outside of the main DoH thread
- du->status_code = 500;
- handleImmediateResponse(std::move(du), "DoH killed in flight");
+ unit->status_code = 500;
+ handleImmediateResponse(std::move(unit), "DoH killed in flight");
return;
}
- {
- // if there was no EDNS, we add it with a large buffer size
- // so we can use UDP to talk to the backend.
- auto dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.data()));
-
- if (!dh->arcount) {
- if (generateOptRR(std::string(), du->query, 4096, 4096, 0, false)) {
- dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.data())); // may have reallocated
- dh->arcount = htons(1);
- du->ids.ednsAdded = true;
- }
- }
- else {
- // we leave existing EDNS in place
- }
- }
-
- remote = du->ids.origRemote;
- DOHServerConfig* dsc = du->dsc;
+ remote = ids.origRemote;
+ DOHServerConfig* dsc = unit->dsc;
auto& holders = dsc->holders;
- ClientState& cs = *dsc->cs;
+ ClientState& clientState = *dsc->clientState;
- if (du->query.size() < sizeof(dnsheader)) {
- ++g_stats.nonCompliantQueries;
- ++cs.nonCompliantQueries;
- du->status_code = 400;
- handleImmediateResponse(std::move(du), "DoH non-compliant query");
+ if (unit->query.size() < sizeof(dnsheader) || unit->query.size() > std::numeric_limits<uint16_t>::max()) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ unit->status_code = 400;
+ handleImmediateResponse(std::move(unit), "DoH non-compliant query");
return;
}
- ++cs.queries;
- ++g_stats.queries;
- du->ids.queryRealTime.start();
+ ++clientState.queries;
+ ++dnsdist::metrics::g_stats.queries;
+ ids.queryRealTime.start();
{
/* don't keep that pointer around, it will be invalidated if the buffer is ever resized */
- struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(du->query.data());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ const dnsheader_aligned dnsHeader(unit->query.data());
- if (!checkQueryHeaders(dh, cs)) {
- du->status_code = 400;
- handleImmediateResponse(std::move(du), "DoH invalid headers");
+ if (!checkQueryHeaders(*dnsHeader, clientState)) {
+ unit->status_code = 400;
+ handleImmediateResponse(std::move(unit), "DoH invalid headers");
return;
}
- if (dh->qdcount == 0) {
- dh->rcode = RCode::NotImp;
- dh->qr = true;
- du->response = std::move(du->query);
+ if (dnsHeader->qdcount == 0U) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+ header.rcode = RCode::NotImp;
+ header.qr = true;
+ return true;
+ });
+ unit->response = std::move(unit->query);
- handleImmediateResponse(std::move(du), "DoH empty query");
+ handleImmediateResponse(std::move(unit), "DoH empty query");
return;
}
- queryId = ntohs(dh->id);
+ queryId = ntohs(dnsHeader->id);
}
- auto downstream = du->downstream;
- du->ids.qname = DNSName(reinterpret_cast<const char*>(du->query.data()), du->query.size(), sizeof(dnsheader), false, &du->ids.qtype, &du->ids.qclass);
- DNSQuestion dq(du->ids, du->query);
- const uint16_t* flags = getFlagsFromDNSHeader(dq.getHeader());
- ids.origFlags = *flags;
- du->ids.cs = &cs;
- dq.sni = std::move(du->sni);
+ {
+ // if there was no EDNS, we add it with a large buffer size
+ // so we can use UDP to talk to the backend.
+ dnsheader_aligned dnsHeader(unit->query.data());
+ if (dnsHeader.get()->arcount == 0U) {
+ if (addEDNS(unit->query, 4096, false, 4096, 0)) {
+ ids.ednsAdded = true;
+ }
+ }
+ }
- auto result = processQuery(dq, holders, downstream);
+ auto downstream = unit->downstream;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(unit->query.data()), static_cast<int>(unit->query.size()), static_cast<int>(sizeof(dnsheader)), false, &ids.qtype, &ids.qclass);
+ DNSQuestion dnsQuestion(ids, unit->query);
+ const uint16_t* flags = getFlagsFromDNSHeader(dnsQuestion.getHeader().get());
+ ids.origFlags = *flags;
+ ids.cs = &clientState;
+ dnsQuestion.sni = std::move(unit->sni);
+ ids.du = std::move(unit);
+ auto result = processQuery(dnsQuestion, holders, downstream);
if (result == ProcessQueryResult::Drop) {
- du->status_code = 403;
- handleImmediateResponse(std::move(du), "DoH dropped query");
+ unit = getDUFromIDS(ids);
+ unit->status_code = 403;
+ handleImmediateResponse(std::move(unit), "DoH dropped query");
return;
}
- else if (result == ProcessQueryResult::Asynchronous) {
+ if (result == ProcessQueryResult::Asynchronous) {
return;
}
- else if (result == ProcessQueryResult::SendAnswer) {
- if (du->response.empty()) {
- du->response = std::move(du->query);
+ if (result == ProcessQueryResult::SendAnswer) {
+ unit = getDUFromIDS(ids);
+ if (unit->response.empty()) {
+ unit->response = std::move(unit->query);
}
- if (du->response.size() >= sizeof(dnsheader) && du->contentType.empty()) {
- auto dh = reinterpret_cast<const struct dnsheader*>(du->response.data());
-
- handleResponseSent(du->ids.qname, QType(du->ids.qtype), 0., du->ids.origDest, ComboAddress(), du->response.size(), *dh, dnsdist::Protocol::DoH, dnsdist::Protocol::DoH, false);
+ if (unit->response.size() >= sizeof(dnsheader) && unit->contentType.empty()) {
+ dnsheader_aligned dnsHeader(unit->response.data());
+ handleResponseSent(unit->ids.qname, QType(unit->ids.qtype), 0., unit->ids.origDest, ComboAddress(), unit->response.size(), *(dnsHeader.get()), dnsdist::Protocol::DoH, dnsdist::Protocol::DoH, false);
}
- handleImmediateResponse(std::move(du), "DoH self-answered response");
+ handleImmediateResponse(std::move(unit), "DoH self-answered response");
return;
}
+ unit = getDUFromIDS(ids);
if (result != ProcessQueryResult::PassToBackend) {
- du->status_code = 500;
- handleImmediateResponse(std::move(du), "DoH no backend available");
+ unit->status_code = 500;
+ handleImmediateResponse(std::move(unit), "DoH no backend available");
return;
}
if (downstream == nullptr) {
- du->status_code = 502;
- handleImmediateResponse(std::move(du), "DoH no backend available");
+ unit->status_code = 502;
+ handleImmediateResponse(std::move(unit), "DoH no backend available");
return;
}
- du->downstream = downstream;
+ unit->downstream = downstream;
if (downstream->isTCPOnly()) {
std::string proxyProtocolPayload;
/* we need to do this _before_ creating the cross protocol query because
after that the buffer will have been moved */
if (downstream->d_config.useProxyProtocol) {
- proxyProtocolPayload = getProxyProtocolPayload(dq);
+ proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
}
- du->ids.origID = htons(queryId);
- du->tcp = true;
+ unit->ids.origID = htons(queryId);
+ unit->tcp = true;
/* this moves du->ids, careful! */
- auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(du), false);
+ auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(unit), false);
+ if (!cpq) {
+ // make linters happy
+ return;
+ }
cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
if (downstream->passCrossProtocolQuery(std::move(cpq))) {
return;
}
- else {
- if (inMainThread) {
- du = cpq->releaseDU();
- du->status_code = 502;
- handleImmediateResponse(std::move(du), "DoH internal error");
+
+ if (inMainThread) {
+ // cpq is not altered if the call fails but linters are not smart enough to notice that
+ if (cpq) {
+ // NOLINTNEXTLINE(bugprone-use-after-move): cpq is not altered if the call fails
+ unit = cpq->releaseDU();
}
- else {
+ unit->status_code = 502;
+ handleImmediateResponse(std::move(unit), "DoH internal error");
+ }
+ else {
+ // cpq is not altered if the call fails but linters are not smart enough to notice that
+ if (cpq) {
+ // NOLINTNEXTLINE(bugprone-use-after-move): cpq is not altered if the call fails
cpq->handleInternalError();
}
- return;
}
+ return;
}
- ComboAddress dest = dq.ids.origDest;
- if (!assignOutgoingUDPQueryToBackend(downstream, htons(queryId), dq, du->query, dest)) {
- du->status_code = 502;
- handleImmediateResponse(std::move(du), "DoH internal error");
+ auto& query = unit->query;
+ ids.du = std::move(unit);
+ if (!assignOutgoingUDPQueryToBackend(downstream, htons(queryId), dnsQuestion, query)) {
+ unit = getDUFromIDS(ids);
+ unit->status_code = 502;
+ handleImmediateResponse(std::move(unit), "DoH internal error");
return;
}
}
catch (const std::exception& e) {
vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
- du->status_code = 500;
- handleImmediateResponse(std::move(du), "DoH internal error");
+ unit->status_code = 500;
+ handleImmediateResponse(std::move(unit), "DoH internal error");
return;
}
-
- return;
}
/* called when a HTTP response is about to be sent, from the main DoH thread */
return;
}
- DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
+ auto* dsc = static_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
DOHFrontend::HTTPVersionStats* stats = nullptr;
if (req->version < 0x200) {
/* HTTP 1.x */
- stats = &dsc->df->d_http1Stats;
+ stats = &dsc->dohFrontend->d_http1Stats;
}
else {
/* HTTP 2.0 */
- stats = &dsc->df->d_http2Stats;
+ stats = &dsc->dohFrontend->d_http2Stats;
}
switch (req->res.status) {
We use this to signal to the 'du' that this req is no longer alive */
static void on_generator_dispose(void *_self)
{
- DOHUnit** du = reinterpret_cast<DOHUnit**>(_self);
- if (*du) { // if 0, on_dnsdist cleaned up du already
- (*du)->self = nullptr;
- (*du)->req = nullptr;
+ auto* dohUnit = static_cast<DOHUnit**>(_self);
+ if (*dohUnit != nullptr) { // if nullptr, on_dnsdist cleaned up dohUnit already
+ (*dohUnit)->self = nullptr;
+ (*dohUnit)->req = nullptr;
}
}
{
try {
/* we only parse it there as a sanity check, we will parse it again later */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
DNSPacketMangler mangler(reinterpret_cast<char*>(query.data()), query.size());
mangler.skipDomainName();
mangler.skipBytes(4);
/* we are doing quite some copies here, sorry about that,
but we can't keep accessing the req object once we are in a different thread
because the request might get killed by h2o at pretty much any time */
- auto du = std::make_unique<DOHUnit>(std::move(query), std::move(path), std::string(req->authority.base, req->authority.len));
- du->dsc = dsc;
- du->req = req;
- du->ids.origDest = local;
- du->ids.origRemote = remote;
- du->ids.protocol = dnsdist::Protocol::DoH;
- du->rsock = dsc->dohresponsepair[0];
+ auto dohUnit = std::make_unique<DOHUnit>(std::move(query), std::move(path), std::string(req->authority.base, req->authority.len));
+ dohUnit->dsc = dsc;
+ dohUnit->req = req;
+ dohUnit->ids.origDest = local;
+ dohUnit->ids.origRemote = remote;
+ dohUnit->ids.protocol = dnsdist::Protocol::DoH;
+ dohUnit->responseSender = &dsc->d_responseSender;
if (req->scheme != nullptr) {
- du->scheme = std::string(req->scheme->name.base, req->scheme->name.len);
+ dohUnit->scheme = std::string(req->scheme->name.base, req->scheme->name.len);
}
- du->query_at = req->query_at;
+ dohUnit->query_at = req->query_at;
- if (dsc->df->d_keepIncomingHeaders) {
- du->headers = std::make_unique<std::unordered_map<std::string, std::string>>();
- du->headers->reserve(req->headers.size);
+ if (dsc->dohFrontend->d_keepIncomingHeaders) {
+ dohUnit->headers = std::make_unique<std::unordered_map<std::string, std::string>>();
+ dohUnit->headers->reserve(req->headers.size);
for (size_t i = 0; i < req->headers.size; ++i) {
- (*du->headers)[std::string(req->headers.entries[i].name->base, req->headers.entries[i].name->len)] = std::string(req->headers.entries[i].value.base, req->headers.entries[i].value.len);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
+ (*dohUnit->headers)[std::string(req->headers.entries[i].name->base, req->headers.entries[i].name->len)] = std::string(req->headers.entries[i].value.base, req->headers.entries[i].value.len);
}
}
h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
const char * sni = h2o_socket_get_ssl_server_name(sock);
if (sni != nullptr) {
- du->sni = sni;
+ dohUnit->sni = sni;
}
#endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */
- du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
- auto ptr = du.release();
- *(ptr->self) = ptr;
+ dohUnit->self = static_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
+ *(dohUnit->self) = dohUnit.get();
#ifdef USE_SINGLE_ACCEPTOR_THREAD
- processDOHQuery(DOHUnitUniquePtr(ptr, DOHUnit::release), true);
+ processDOHQuery(std::move(dohUnit), true);
#else /* USE_SINGLE_ACCEPTOR_THREAD */
- try {
- static_assert(sizeof(ptr) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
- ssize_t sent = write(dsc->dohquerypair[0], &ptr, sizeof(ptr));
- if (sent != sizeof(ptr)) {
- if (errno == EAGAIN || errno == EWOULDBLOCK) {
- ++g_stats.dohQueryPipeFull;
- vinfolog("Unable to pass a DoH query to the DoH worker thread because the pipe is full");
- }
- else {
- vinfolog("Unable to pass a DoH query to the DoH worker thread because we couldn't write to the pipe: %s", stringerror());
- }
- ptr->release();
- ptr = nullptr;
+ try {
+ if (!dsc->d_querySender.send(std::move(dohUnit))) {
+ ++dnsdist::metrics::g_stats.dohQueryPipeFull;
+ vinfolog("Unable to pass a DoH query to the DoH worker thread because the pipe is full");
h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0);
}
}
catch (...) {
- if (ptr != nullptr) {
- ptr->release();
- }
+ vinfolog("Unable to pass a DoH query to the DoH worker thread because we couldn't write to the pipe: %s", stringerror());
+ h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0);
}
#endif /* USE_SINGLE_ACCEPTOR_THREAD */
}
std::string_view headerNameView(headerName);
for (size_t i = 0; i < req->headers.size; ++i) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
if (std::string_view(req->headers.entries[i].name->base, req->headers.entries[i].name->len) == headerNameView) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
value = std::string_view(req->headers.entries[i].value.base, req->headers.entries[i].value.len);
/* don't stop there, we might have more than one header with the same name, and we want the last one */
found = true;
}
/* can only be called from the main DoH thread */
-static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote)
+static std::optional<ComboAddress> processForwardedForHeader(const h2o_req_t* req, const ComboAddress& remote)
{
static const std::string headerName = "x-forwarded-for";
std::string_view value;
value = value.substr(pos);
}
}
- auto newRemote = ComboAddress(std::string(value));
- remote = newRemote;
+ return ComboAddress(std::string(value));
}
catch (const std::exception& e) {
vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.what());
vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.reason);
}
}
+
+ return std::nullopt;
}
/*
static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
{
try {
- if (!req->conn->ctx->storage.size) {
+ if (req->conn->ctx->storage.size == 0) {
return 0; // although we might was well crash on this
}
- DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
+ auto* dsc = static_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
const int descriptor = h2o_socket_get_fd(sock);
++conn.d_nbQueries;
if (conn.d_nbQueries == 1) {
if (h2o_socket_get_ssl_session_reused(sock) == 0) {
- ++dsc->cs->tlsNewSessions;
+ ++dsc->clientState->tlsNewSessions;
}
else {
- ++dsc->cs->tlsResumptions;
+ ++dsc->clientState->tlsResumptions;
}
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&conn.d_local));
}
- if (dsc->df->d_trustForwardedForHeader) {
- processForwardedForHeader(req, conn.d_remote);
+ auto remote = conn.d_remote;
+ if (dsc->dohFrontend->d_trustForwardedForHeader) {
+ auto newRemote = processForwardedForHeader(req, remote);
+ if (newRemote) {
+ remote = *newRemote;
+ }
}
auto& holders = dsc->holders;
- if (!holders.acl->match(conn.d_remote)) {
- ++g_stats.aclDrops;
- vinfolog("Query from %s (DoH) dropped because of ACL", conn.d_remote.toStringWithPort());
- h2o_send_error_403(req, "Forbidden", "dns query not allowed because of ACL", 0);
+ if (!holders.acl->match(remote)) {
+ ++dnsdist::metrics::g_stats.aclDrops;
+ vinfolog("Query from %s (DoH) dropped because of ACL", remote.toStringWithPort());
+ h2o_send_error_403(req, "Forbidden", "DoH query not allowed because of ACL", 0);
return 0;
}
- if (auto tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
- if(!strcmp(tlsversion, "TLSv1.0"))
- ++dsc->cs->tls10queries;
- else if(!strcmp(tlsversion, "TLSv1.1"))
- ++dsc->cs->tls11queries;
- else if(!strcmp(tlsversion, "TLSv1.2"))
- ++dsc->cs->tls12queries;
- else if(!strcmp(tlsversion, "TLSv1.3"))
- ++dsc->cs->tls13queries;
- else
- ++dsc->cs->tlsUnknownqueries;
+ if (const auto* tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
+ if (strcmp(tlsversion, "TLSv1.0") == 0) {
+ ++dsc->clientState->tls10queries;
+ }
+ else if (strcmp(tlsversion, "TLSv1.1") == 0) {
+ ++dsc->clientState->tls11queries;
+ }
+ else if (strcmp(tlsversion, "TLSv1.2") == 0) {
+ ++dsc->clientState->tls12queries;
+ }
+ else if (strcmp(tlsversion, "TLSv1.3") == 0) {
+ ++dsc->clientState->tls13queries;
+ }
+ else {
+ ++dsc->clientState->tlsUnknownqueries;
+ }
}
- if (dsc->df->d_exactPathMatching) {
+ if (dsc->dohFrontend->d_exactPathMatching) {
const std::string_view pathOnly(req->path_normalized.base, req->path_normalized.len);
if (dsc->paths.count(pathOnly) == 0) {
h2o_send_error_404(req, "Not Found", "there is no endpoint configured for this path", 0);
string path(req->path.base, req->path.len);
/* the responses map can be updated at runtime, so we need to take a copy of
the shared pointer, increasing the reference counter */
- auto responsesMap = dsc->df->d_responsesMap;
+ auto responsesMap = dsc->dohFrontend->d_responsesMap;
/* 1 byte for the root label, 2 type, 2 class, 4 TTL (fake), 2 record length, 2 option length, 2 option code, 2 family, 1 source, 1 scope, 16 max for a full v6 */
const size_t maxAdditionalSizeForEDNS = 35U;
if (responsesMap) {
for (const auto& entry : *responsesMap) {
if (entry->matches(path)) {
const auto& customHeaders = entry->getHeaders();
- handleResponse(*dsc->df, req, entry->getStatusCode(), entry->getContent(), customHeaders ? *customHeaders : dsc->df->d_customResponseHeaders, std::string(), false);
+ handleResponse(*dsc->dohFrontend, req, entry->getStatusCode(), entry->getContent(), customHeaders ? *customHeaders : dsc->dohFrontend->d_customResponseHeaders, std::string(), false);
return 0;
}
}
}
- if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST"))) {
- ++dsc->df->d_postqueries;
- if(req->version >= 0x0200)
- ++dsc->df->d_http2Stats.d_nbQueries;
- else
- ++dsc->df->d_http1Stats.d_nbQueries;
+ if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST")) != 0) {
+ ++dsc->dohFrontend->d_postqueries;
+ if (req->version >= 0x0200) {
+ ++dsc->dohFrontend->d_http2Stats.d_nbQueries;
+ }
+ else {
+ ++dsc->dohFrontend->d_http1Stats.d_nbQueries;
+ }
PacketBuffer query;
/* We reserve a few additional bytes to be able to add EDNS later */
query.reserve(req->entity.len + maxAdditionalSizeForEDNS);
query.resize(req->entity.len);
memcpy(query.data(), req->entity.base, req->entity.len);
- doh_dispatch_query(dsc, self, req, std::move(query), conn.d_local, conn.d_remote, std::move(path));
+ doh_dispatch_query(dsc, self, req, std::move(query), conn.d_local, remote, std::move(path));
}
else if(req->query_at != SIZE_MAX && (req->path.len - req->query_at > 5)) {
auto pos = path.find("?dns=");
- if(pos == string::npos)
+ if (pos == string::npos) {
pos = path.find("&dns=");
- if(pos != string::npos) {
+ }
+ if (pos != string::npos) {
// need to base64url decode this
string sdns(path.substr(pos+5));
boost::replace_all(sdns,"-", "+");
decoded.reserve(estimate + maxAdditionalSizeForEDNS);
if(B64Decode(sdns, decoded) < 0) {
h2o_send_error_400(req, "Bad Request", "Unable to decode BASE64-URL", 0);
- ++dsc->df->d_badrequests;
+ ++dsc->dohFrontend->d_badrequests;
return 0;
}
- else {
- ++dsc->df->d_getqueries;
- if(req->version >= 0x0200)
- ++dsc->df->d_http2Stats.d_nbQueries;
- else
- ++dsc->df->d_http1Stats.d_nbQueries;
- doh_dispatch_query(dsc, self, req, std::move(decoded), conn.d_local, conn.d_remote, std::move(path));
+ ++dsc->dohFrontend->d_getqueries;
+ if (req->version >= 0x0200) {
+ ++dsc->dohFrontend->d_http2Stats.d_nbQueries;
}
+ else {
+ ++dsc->dohFrontend->d_http1Stats.d_nbQueries;
+ }
+
+ doh_dispatch_query(dsc, self, req, std::move(decoded), conn.d_local, remote, std::move(path));
}
else
{
vinfolog("HTTP request without DNS parameter: %s", req->path.base);
h2o_send_error_400(req, "Bad Request", "Unable to find the DNS parameter", 0);
- ++dsc->df->d_badrequests;
+ ++dsc->dohFrontend->d_badrequests;
return 0;
}
}
else {
h2o_send_error_400(req, "Bad Request", "Unable to parse the request", 0);
- ++dsc->df->d_badrequests;
+ ++dsc->dohFrontend->d_badrequests;
}
return 0;
}
- catch(const std::exception& e)
- {
- errlog("DOH Handler function failed with error %s", e.what());
+ catch (const std::exception& e) {
+ vinfolog("DOH Handler function failed with error: '%s'", e.what());
return 0;
}
}
-HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex)
- : d_header(toLower(header)), d_regex(regex), d_visual("http[" + header+ "] ~ " + regex)
-{
-}
-
-bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
-{
- if (!dq->ids.du || !dq->ids.du->headers) {
- return false;
- }
-
- for (const auto& header : *dq->ids.du->headers) {
- if (header.first == d_header) {
- return d_regex.match(header.second);
- }
- }
- return false;
-}
-
-string HTTPHeaderRule::toString() const
-{
- return d_visual;
-}
-
-HTTPPathRule::HTTPPathRule(const std::string& path)
- : d_path(path)
-{
-
-}
-
-bool HTTPPathRule::matches(const DNSQuestion* dq) const
-{
- if (!dq->ids.du) {
- return false;
- }
-
- if (dq->ids.du->query_at == SIZE_MAX) {
- return dq->ids.du->path == d_path;
- }
- else {
- return d_path.compare(0, d_path.size(), dq->ids.du->path, 0, dq->ids.du->query_at) == 0;
- }
-}
-
-string HTTPPathRule::toString() const
-{
- return "url path == " + d_path;
-}
-
-HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex): d_regex(regex), d_visual("http path ~ " + regex)
-{
-}
-
-bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
-{
- if (!dq->ids.du) {
- return false;
- }
-
- return d_regex.match(dq->ids.du->getHTTPPath());
-}
-
-string HTTPPathRegexRule::toString() const
+const std::unordered_map<std::string, std::string>& DOHUnit::getHTTPHeaders() const
{
- return d_visual;
-}
-
-std::unordered_map<std::string, std::string> DOHUnit::getHTTPHeaders() const
-{
- std::unordered_map<std::string, std::string> results;
- if (headers) {
- results.reserve(headers->size());
-
- for (const auto& header : *headers) {
- results.insert(header);
- }
+ if (!headers) {
+ static const HeadersMap empty{};
+ return empty;
}
-
- return results;
+ return *headers;
}
std::string DOHUnit::getHTTPPath() const
if (query_at == SIZE_MAX) {
return path;
}
- else {
- return std::string(path, 0, query_at);
- }
+ return {path, 0, query_at};
}
-std::string DOHUnit::getHTTPHost() const
+const std::string& DOHUnit::getHTTPHost() const
{
return host;
}
-std::string DOHUnit::getHTTPScheme() const
+const std::string& DOHUnit::getHTTPScheme() const
{
return scheme;
}
std::string DOHUnit::getHTTPQueryString() const
{
if (query_at == SIZE_MAX) {
- return std::string();
- }
- else {
- return path.substr(query_at);
+ return {};
}
+ return path.substr(query_at);
}
void DOHUnit::setHTTPResponse(uint16_t statusCode, PacketBuffer&& body_, const std::string& contentType_)
/* query has been parsed by h2o, which called doh_handler() in the main DoH thread.
In order not to block for long, doh_handler() called doh_dispatch_query() which allocated
a DOHUnit object and passed it to us */
-static void dnsdistclient(int qsock)
+static void dnsdistclient(pdns::channel::Receiver<DOHUnit>&& receiver)
{
setThreadName("dnsdist/doh-cli");
for(;;) {
try {
- DOHUnit* ptr = nullptr;
- ssize_t got = read(qsock, &ptr, sizeof(ptr));
- if (got < 0) {
- warnlog("Error receiving internal DoH query: %s", strerror(errno));
- continue;
- }
- else if (static_cast<size_t>(got) < sizeof(ptr)) {
+ auto tmp = receiver.receive();
+ if (!tmp) {
continue;
}
-
- DOHUnitUniquePtr du(ptr, DOHUnit::release);
+ auto dohUnit = std::move(*tmp);
/* we are not in the main DoH thread anymore, so there is a real risk of
a race condition where h2o kills the query while we are processing it,
- so we can't touch the content of du->req until we are back into the
+ so we can't touch the content of dohUnit->req until we are back into the
main DoH thread */
- if (!du->req) {
+ if (dohUnit->req == nullptr) {
// it got killed in flight already
- du->self = nullptr;
+ dohUnit->self = nullptr;
continue;
}
- processDOHQuery(std::move(du), false);
+ processDOHQuery(std::move(dohUnit), false);
}
catch (const std::exception& e) {
- errlog("Error while processing query received over DoH: %s", e.what());
+ vinfolog("Error while processing query received over DoH: %s", e.what());
}
catch (...) {
- errlog("Unspecified error while processing query received over DoH");
+ vinfolog("Unspecified error while processing query received over DoH");
}
}
}
#endif /* USE_SINGLE_ACCEPTOR_THREAD */
/* Called in the main DoH thread if h2o finds that dnsdist gave us an answer by writing into
- the dohresponsepair[0] side of the pipe so from:
+ the response channel so from:
- handleDOHTimeout() when we did not get a response fast enough (called
either from the health check thread (active) or from the frontend ones (reused))
- dnsdistclient (error 500 because processDOHQuery() returned a negative value)
for the CPU, the first thing we need to do is to send responses to free slots
anyway, otherwise queries and responses are piling up in our pipes, consuming
memory and likely coming up too late after the client has gone away */
+ auto* dsc = static_cast<DOHServerConfig*>(listener->data);
while (true) {
- DOHUnit *ptr = nullptr;
- DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
- ssize_t got = read(dsc->dohresponsepair[1], &ptr, sizeof(ptr));
-
- if (got < 0) {
- if (errno != EWOULDBLOCK && errno != EAGAIN) {
- errlog("Error reading a DOH internal response: %s", strerror(errno));
+ DOHUnitUniquePtr dohUnit{nullptr};
+ try {
+ auto tmp = dsc->d_responseReceiver.receive();
+ if (!tmp) {
+ return;
}
- return;
+ dohUnit = std::move(*tmp);
}
- else if (static_cast<size_t>(got) != sizeof(ptr)) {
- errlog("Error reading a DoH internal response, got %d bytes instead of the expected %d", got, sizeof(ptr));
+ catch (const std::exception& e) {
+ warnlog("Error reading a DOH internal response: %s", e.what());
return;
}
- DOHUnitUniquePtr du(ptr, DOHUnit::release);
- if (!du->req) { // it got killed in flight
- du->self = nullptr;
+ if (dohUnit->req == nullptr) { // it got killed in flight
+ dohUnit->self = nullptr;
continue;
}
- if (!du->tcp &&
- du->truncated &&
- du->query.size() > du->proxyProtocolPayloadSize &&
- (du->query.size() - du->proxyProtocolPayloadSize) > sizeof(dnsheader)) {
+ if (!dohUnit->tcp &&
+ dohUnit->truncated &&
+ dohUnit->query.size() > dohUnit->ids.d_proxyProtocolPayloadSize &&
+ (dohUnit->query.size() - dohUnit->ids.d_proxyProtocolPayloadSize) > sizeof(dnsheader)) {
/* restoring the original ID */
- dnsheader* queryDH = reinterpret_cast<struct dnsheader*>(du->query.data() + du->proxyProtocolPayloadSize);
- queryDH->id = du->ids.origID;
- du->ids.forwardedOverUDP = false;
- du->tcp = true;
- du->truncated = false;
- du->response.clear();
+ dnsdist::PacketMangling::editDNSHeaderFromRawPacket(&dohUnit->query.at(dohUnit->ids.d_proxyProtocolPayloadSize), [oldID=dohUnit->ids.origID](dnsheader& header) {
+ header.id = oldID;
+ return true;
+ });
+ dohUnit->ids.forwardedOverUDP = false;
+ dohUnit->tcp = true;
+ dohUnit->truncated = false;
+ dohUnit->response.clear();
- auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(du), false);
+ auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(dohUnit), false);
if (g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq))) {
continue;
}
- else {
- vinfolog("Unable to pass DoH query to a TCP worker thread after getting a TC response over UDP");
- continue;
- }
+ vinfolog("Unable to pass DoH query to a TCP worker thread after getting a TC response over UDP");
+ continue;
}
- if (du->self) {
+ if (dohUnit->self != nullptr) {
// we are back in the h2o main thread now, so we don't risk
- // a race (h2o killing the query) when accessing du->req anymore
- *du->self = nullptr; // so we don't clean up again in on_generator_dispose
- du->self = nullptr;
+ // a race (h2o killing the query) when accessing dohUnit->req anymore
+ *dohUnit->self = nullptr; // so we don't clean up again in on_generator_dispose
+ dohUnit->self = nullptr;
}
- handleResponse(*dsc->df, du->req, du->status_code, du->response, dsc->df->d_customResponseHeaders, du->contentType, true);
+ handleResponse(*dsc->dohFrontend, dohUnit->req, dohUnit->status_code, dohUnit->response, dsc->dohFrontend->d_customResponseHeaders, dohUnit->contentType, true);
}
}
/* called when a TCP connection has been accepted, the TLS session has not been established */
static void on_accept(h2o_socket_t *listener, const char *err)
{
- DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
- h2o_socket_t *sock = nullptr;
+ auto* dsc = static_cast<DOHServerConfig*>(listener->data);
if (err != nullptr) {
return;
}
- if ((sock = h2o_evloop_socket_accept(listener)) == nullptr) {
+ h2o_socket_t* sock = h2o_evloop_socket_accept(listener);
+ if (sock == nullptr) {
return;
}
}
ComboAddress remote;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
if (h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote)) == 0) {
vinfolog("Dropping DoH connection because we could not retrieve the remote host");
h2o_socket_close(sock);
return;
}
+ if (dsc->dohFrontend->d_earlyACLDrop && !dsc->dohFrontend->d_trustForwardedForHeader && !dsc->holders.acl->match(remote)) {
+ ++dnsdist::metrics::g_stats.aclDrops;
+ vinfolog("Dropping DoH connection from %s because of ACL", remote.toStringWithPort());
+ h2o_socket_close(sock);
+ return;
+ }
+
if (!dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(remote)) {
vinfolog("Dropping DoH connection from %s because we have too many from this client already", remote.toStringWithPort());
h2o_socket_close(sock);
return;
}
- auto concurrentConnections = ++dsc->cs->tcpCurrentConnections;
- if (dsc->cs->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > dsc->cs->d_tcpConcurrentConnectionsLimit) {
- --dsc->cs->tcpCurrentConnections;
+ auto concurrentConnections = ++dsc->clientState->tcpCurrentConnections;
+ if (dsc->clientState->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > dsc->clientState->d_tcpConcurrentConnectionsLimit) {
+ --dsc->clientState->tcpCurrentConnections;
h2o_socket_close(sock);
return;
}
- if (concurrentConnections > dsc->cs->tcpMaxConcurrentConnections.load()) {
- dsc->cs->tcpMaxConcurrentConnections.store(concurrentConnections);
+ if (concurrentConnections > dsc->clientState->tcpMaxConcurrentConnections.load()) {
+ dsc->clientState->tcpMaxConcurrentConnections.store(concurrentConnections);
}
auto& conn = t_conns[descriptor];
sock->on_close.data = &conn;
sock->data = dsc;
- ++dsc->df->d_httpconnects;
+ ++dsc->dohFrontend->d_httpconnects;
h2o_accept(conn.d_acceptCtx->get(), sock);
}
-static int create_listener(std::shared_ptr<DOHServerConfig>& dsc, int fd)
+static int create_listener(std::shared_ptr<DOHServerConfig>& dsc, int descriptor)
{
- auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
+ auto* sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, descriptor, H2O_SOCKET_FLAG_DONT_READ);
sock->data = dsc.get();
h2o_socket_read_start(sock, on_accept);
if (ssl == nullptr || arg == nullptr) {
return SSL_TLSEXT_ERR_NOACK;
}
- const auto ocspMap = reinterpret_cast<std::map<int, std::string>*>(arg);
+ const auto* ocspMap = static_cast<std::map<int, std::string>*>(arg);
return libssl_ocsp_stapling_callback(ssl, *ocspMap);
}
#endif /* DISABLE_OCSP_STAPLING */
#if OPENSSL_VERSION_MAJOR >= 3
-static int ticket_key_callback(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, EVP_MAC_CTX *hctx, int enc)
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays): OpenSSL API
+static int ticket_key_callback(SSL* sslContext, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* ivector, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx, int enc)
#else
-static int ticket_key_callback(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays): OpenSSL API
+static int ticket_key_callback(SSL *sslContext, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* ivector, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx, int enc)
#endif
{
- DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(libssl_get_ticket_key_callback_data(s));
+ auto* ctx = static_cast<DOHAcceptContext*>(libssl_get_ticket_key_callback_data(sslContext));
if (ctx == nullptr || !ctx->d_ticketKeys) {
return -1;
}
ctx->handleTicketsKeyRotation();
- auto ret = libssl_ticket_key_callback(s, *ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc);
+ auto ret = libssl_ticket_key_callback(sslContext, *ctx->d_ticketKeys, keyName, ivector, ectx, hctx, enc);
if (enc == 0) {
if (ret == 0) {
++ctx->d_cs->tlsUnknownTicketKey;
TLSErrorCounters& counters)
{
if (tlsConfig.d_ciphers.empty()) {
- tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS;
+ tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS.data();
}
auto [ctx, warnings] = libssl_init_server_context(tlsConfig, acceptCtx.d_ocspResponses);
acceptCtx.loadTicketsKeys(tlsConfig.d_ticketKeyFile);
}
- auto nativeCtx = acceptCtx.get();
+ auto* nativeCtx = acceptCtx.get();
nativeCtx->ssl_ctx = ctx.release();
}
static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool setupTLS)
{
- auto nativeCtx = ctx.get();
+ auto* nativeCtx = ctx.get();
nativeCtx->ctx = &dsc.h2o_ctx;
nativeCtx->hosts = dsc.h2o_config.hosts;
- ctx.d_ticketsKeyRotationDelay = dsc.df->d_tlsConfig.d_ticketsKeyRotationDelay;
+ auto dohFrontend = std::atomic_load_explicit(&dsc.dohFrontend, std::memory_order_acquire);
+ ctx.d_ticketsKeyRotationDelay = dohFrontend->d_tlsContext.d_tlsConfig.d_ticketsKeyRotationDelay;
- if (setupTLS && dsc.df->isHTTPS()) {
+ if (setupTLS && dohFrontend->isHTTPS()) {
try {
setupTLSContext(ctx,
- dsc.df->d_tlsConfig,
- dsc.df->d_tlsCounters);
- }
- catch (const std::runtime_error& e) {
- throw std::runtime_error("Error setting up TLS context for DoH listener on '" + dsc.df->d_local.toStringWithPort() + "': " + e.what());
- }
- }
- ctx.d_cs = dsc.cs;
-}
-
-void DOHFrontend::rotateTicketsKey(time_t now)
-{
- if (d_dsc && d_dsc->accept_ctx) {
- d_dsc->accept_ctx->rotateTicketsKey(now);
- }
-}
-
-void DOHFrontend::loadTicketsKeys(const std::string& keyFile)
-{
- if (d_dsc && d_dsc->accept_ctx) {
- d_dsc->accept_ctx->loadTicketsKeys(keyFile);
- }
-}
-
-void DOHFrontend::handleTicketsKeyRotation()
-{
- if (d_dsc && d_dsc->accept_ctx) {
- d_dsc->accept_ctx->handleTicketsKeyRotation();
- }
-}
-
-time_t DOHFrontend::getNextTicketsKeyRotation() const
-{
- if (d_dsc && d_dsc->accept_ctx) {
- return d_dsc->accept_ctx->getNextTicketsKeyRotation();
- }
- return 0;
-}
-
-size_t DOHFrontend::getTicketsKeysCount() const
-{
- size_t res = 0;
- if (d_dsc && d_dsc->accept_ctx) {
- res = d_dsc->accept_ctx->getTicketsKeysCount();
- }
- return res;
-}
-
-void DOHFrontend::reloadCertificates()
-{
- auto newAcceptContext = std::make_shared<DOHAcceptContext>();
- setupAcceptContext(*newAcceptContext, *d_dsc, true);
- std::atomic_store_explicit(&d_dsc->accept_ctx, newAcceptContext, std::memory_order_release);
-}
-
-void DOHFrontend::setup()
-{
- registerOpenSSLUser();
-
- d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout, d_internalPipeBufferSize);
-
- if (isHTTPS()) {
- try {
- setupTLSContext(*d_dsc->accept_ctx,
- d_tlsConfig,
- d_tlsCounters);
+ dohFrontend->d_tlsContext.d_tlsConfig,
+ dohFrontend->d_tlsContext.d_tlsCounters);
}
catch (const std::runtime_error& e) {
- throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_local.toStringWithPort() + "': " + e.what());
+ throw std::runtime_error("Error setting up TLS context for DoH listener on '" + dohFrontend->d_tlsContext.d_addr.toStringWithPort() + "': " + e.what());
}
}
+ ctx.d_cs = dsc.clientState;
}
static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *))
return pathconf;
}
h2o_filter_t *filter = h2o_create_filter(pathconf, sizeof(*filter));
- if (filter) {
+ if (filter != nullptr) {
filter->on_setup_ostream = on_response_ready_cb;
}
}
// this is the entrypoint from dnsdist.cc
-void dohThread(ClientState* cs)
+void dohThread(ClientState* clientState)
{
try {
- std::shared_ptr<DOHFrontend>& df = cs->dohFrontend;
- auto& dsc = df->d_dsc;
- dsc->cs = cs;
- dsc->df = cs->dohFrontend;
- dsc->h2o_config.server_name = h2o_iovec_init(df->d_serverTokens.c_str(), df->d_serverTokens.size());
+ std::shared_ptr<DOHFrontend>& dohFrontend = clientState->dohFrontend;
+ auto& dsc = dohFrontend->d_dsc;
+ dsc->clientState = clientState;
+ std::atomic_store_explicit(&dsc->dohFrontend, clientState->dohFrontend, std::memory_order_release);
+ dsc->h2o_config.server_name = h2o_iovec_init(dohFrontend->d_serverTokens.c_str(), dohFrontend->d_serverTokens.size());
#ifndef USE_SINGLE_ACCEPTOR_THREAD
- std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1]);
+ std::thread dnsdistThread(dnsdistclient, std::move(dsc->d_queryReceiver));
dnsdistThread.detach(); // gets us better error reporting
#endif
setThreadName("dnsdist/doh");
// I wonder if this registers an IP address.. I think it does
// this may mean we need to actually register a site "name" here and not the IP address
- h2o_hostconf_t *hostconf = h2o_config_register_host(&dsc->h2o_config, h2o_iovec_init(df->d_local.toString().c_str(), df->d_local.toString().size()), 65535);
+ h2o_hostconf_t *hostconf = h2o_config_register_host(&dsc->h2o_config, h2o_iovec_init(dohFrontend->d_tlsContext.d_addr.toString().c_str(), dohFrontend->d_tlsContext.d_addr.toString().size()), 65535);
- for(const auto& url : df->d_urls) {
+ dsc->paths = dohFrontend->d_urls;
+ for (const auto& url : dsc->paths) {
register_handler(hostconf, url.c_str(), doh_handler);
- dsc->paths.insert(url);
}
h2o_context_init(&dsc->h2o_ctx, h2o_evloop_create(), &dsc->h2o_config);
// in this complicated way we insert the DOHServerConfig pointer in there
h2o_vector_reserve(nullptr, &dsc->h2o_ctx.storage, 1);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
dsc->h2o_ctx.storage.entries[0].data = dsc.get();
++dsc->h2o_ctx.storage.size;
- auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, dsc->dohresponsepair[1], H2O_SOCKET_FLAG_DONT_READ);
+ auto* sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, dsc->d_responseReceiver.getDescriptor(), H2O_SOCKET_FLAG_DONT_READ);
sock->data = dsc.get();
// this listens to responses from dnsdist to turn into http responses
setupAcceptContext(*dsc->accept_ctx, *dsc, false);
- if (create_listener(dsc, cs->tcpFD) != 0) {
- throw std::runtime_error("DOH server failed to listen on " + df->d_local.toStringWithPort() + ": " + strerror(errno));
+ if (create_listener(dsc, clientState->tcpFD) != 0) {
+ throw std::runtime_error("DOH server failed to listen on " + dohFrontend->d_tlsContext.d_addr.toStringWithPort() + ": " + stringerror(errno));
}
- for (const auto& [addr, fd] : cs->d_additionalAddresses) {
- if (create_listener(dsc, fd) != 0) {
- throw std::runtime_error("DOH server failed to listen on additional address " + addr.toStringWithPort() + " for DOH local" + df->d_local.toStringWithPort() + ": " + strerror(errno));
+ for (const auto& [addr, descriptor] : clientState->d_additionalAddresses) {
+ if (create_listener(dsc, descriptor) != 0) {
+ throw std::runtime_error("DOH server failed to listen on additional address " + addr.toStringWithPort() + " for DOH local" + dohFrontend->d_tlsContext.d_addr.toStringWithPort() + ": " + stringerror(errno));
}
}
int result = h2o_evloop_run(dsc->h2o_ctx.loop, INT32_MAX);
if (result == -1) {
if (errno != EINTR) {
- errlog("Error in the DoH event loop: %s", strerror(errno));
+ errlog("Error in the DoH event loop: %s", stringerror(errno));
stop = true;
}
}
}
- while (stop == false);
+ while (!stop);
}
catch (const std::exception& e) {
}
}
-void handleUDPResponseForDoH(DOHUnitUniquePtr&& du, PacketBuffer&& udpResponse, InternalQueryState&& state)
+void DOHUnit::handleUDPResponse(PacketBuffer&& udpResponse, InternalQueryState&& state, [[maybe_unused]] const std::shared_ptr<DownstreamState>& downstream_)
{
- du->response = std::move(udpResponse);
- du->ids = std::move(state);
-
- const dnsheader* dh = reinterpret_cast<const struct dnsheader*>(du->response.data());
- if (!dh->tc) {
- static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
- static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
-
- DNSResponse dr(du->ids, du->response, du->downstream);
- dnsheader cleartextDH;
- memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
-
- dr.ids.du = std::move(du);
- if (!processResponse(dr.ids.du->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dr, false)) {
- if (dr.ids.du) {
- dr.ids.du->status_code = 503;
- sendDoHUnitToTheMainThread(std::move(dr.ids.du), "Response dropped by rules");
+ auto dohUnit = std::unique_ptr<DOHUnit>(this);
+ dohUnit->ids = std::move(state);
+
+ {
+ dnsheader_aligned dnsHeader(udpResponse.data());
+ if (dnsHeader.get()->tc) {
+ dohUnit->truncated = true;
+ }
+ }
+ if (!dohUnit->truncated) {
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+
+ DNSResponse dnsResponse(dohUnit->ids, udpResponse, dohUnit->downstream);
+ dnsheader cleartextDH{};
+ memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
+
+ dnsResponse.ids.du = std::move(dohUnit);
+ if (!processResponse(udpResponse, *localRespRuleActions, *localCacheInsertedRespRuleActions, dnsResponse, false)) {
+ if (dnsResponse.ids.du) {
+ dohUnit = getDUFromIDS(dnsResponse.ids);
+ dohUnit->status_code = 503;
+ sendDoHUnitToTheMainThread(std::move(dohUnit), "Response dropped by rules");
}
return;
}
- if (dr.isAsynchronous()) {
+ if (dnsResponse.isAsynchronous()) {
return;
}
- du = std::move(dr.ids.du);
- double udiff = du->ids.queryRealTime.udiff();
- vinfolog("Got answer from %s, relayed to %s (https), took %f us", du->downstream->d_config.remote.toStringWithPort(), du->ids.origRemote.toStringWithPort(), udiff);
+ dohUnit = getDUFromIDS(dnsResponse.ids);
+ dohUnit->response = std::move(udpResponse);
+ double udiff = dohUnit->ids.queryRealTime.udiff();
+ vinfolog("Got answer from %s, relayed to %s (https), took %f us", dohUnit->downstream->d_config.remote.toStringWithPort(), dohUnit->ids.origRemote.toStringWithPort(), udiff);
- handleResponseSent(du->ids, udiff, dr.ids.origRemote, du->downstream->d_config.remote, du->response.size(), cleartextDH, du->downstream->getProtocol(), true);
+ handleResponseSent(dohUnit->ids, udiff, dnsResponse.ids.origRemote, dohUnit->downstream->d_config.remote, dohUnit->response.size(), cleartextDH, dohUnit->downstream->getProtocol(), true);
- ++g_stats.responses;
- if (du->ids.cs) {
- ++du->ids.cs->responses;
+ ++dnsdist::metrics::g_stats.responses;
+ if (dohUnit->ids.cs != nullptr) {
+ ++dohUnit->ids.cs->responses;
}
}
- else {
- du->truncated = true;
+
+ sendDoHUnitToTheMainThread(std::move(dohUnit), "DoH response");
+}
+
+void H2ODOHFrontend::rotateTicketsKey(time_t now)
+{
+ if (d_dsc && d_dsc->accept_ctx) {
+ d_dsc->accept_ctx->rotateTicketsKey(now);
}
+}
- sendDoHUnitToTheMainThread(std::move(du), "DoH response");
+void H2ODOHFrontend::loadTicketsKeys(const std::string& keyFile)
+{
+ if (d_dsc && d_dsc->accept_ctx) {
+ d_dsc->accept_ctx->loadTicketsKeys(keyFile);
+ }
}
-#else /* HAVE_DNS_OVER_HTTPS */
+void H2ODOHFrontend::handleTicketsKeyRotation()
+{
+ if (d_dsc && d_dsc->accept_ctx) {
+ d_dsc->accept_ctx->handleTicketsKeyRotation();
+ }
+}
+
+std::string H2ODOHFrontend::getNextTicketsKeyRotation() const
+{
+ if (d_dsc && d_dsc->accept_ctx) {
+ return std::to_string(d_dsc->accept_ctx->getNextTicketsKeyRotation());
+ }
+ return {};
+}
+
+size_t H2ODOHFrontend::getTicketsKeysCount()
+{
+ size_t res = 0;
+ if (d_dsc && d_dsc->accept_ctx) {
+ res = d_dsc->accept_ctx->getTicketsKeysCount();
+ }
+ return res;
+}
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU)
+void H2ODOHFrontend::reloadCertificates()
{
+ auto newAcceptContext = std::make_shared<DOHAcceptContext>();
+ setupAcceptContext(*newAcceptContext, *d_dsc, true);
+ std::atomic_store_explicit(&d_dsc->accept_ctx, std::move(newAcceptContext), std::memory_order_release);
+}
+
+void H2ODOHFrontend::setup()
+{
+ registerOpenSSLUser();
+
+ d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout, d_internalPipeBufferSize);
+
+ if (isHTTPS()) {
+ try {
+ setupTLSContext(*d_dsc->accept_ctx,
+ d_tlsContext.d_tlsConfig,
+ d_tlsContext.d_tlsCounters);
+ }
+ catch (const std::runtime_error& e) {
+ throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_tlsContext.d_addr.toStringWithPort() + "': " + e.what());
+ }
+ }
}
+#endif /* HAVE_LIBH2OEVLOOP */
#endif /* HAVE_DNS_OVER_HTTPS */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "doh3.hh"
+
+#ifdef HAVE_DNS_OVER_HTTP3
+#include <quiche.h>
+
+#include "dolog.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "sstuff.hh"
+#include "threadname.hh"
+#include "base64.hh"
+
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-random.hh"
+
+#include "doq-common.hh"
+
+#if 0
+#define DEBUGLOG_ENABLED
+#define DEBUGLOG(x) std::cerr << x << std::endl;
+#else
+#define DEBUGLOG(x)
+#endif
+
+using namespace dnsdist::doq;
+
+using h3_headers_t = std::map<std::string, std::string>;
+
+class H3Connection
+{
+public:
+ H3Connection(const ComboAddress& peer, QuicheConfig config, QuicheConnection&& conn) :
+ d_peer(peer), d_conn(std::move(conn)), d_config(std::move(config))
+ {
+ }
+ H3Connection(const H3Connection&) = delete;
+ H3Connection(H3Connection&&) = default;
+ H3Connection& operator=(const H3Connection&) = delete;
+ H3Connection& operator=(H3Connection&&) = default;
+ ~H3Connection() = default;
+
+ ComboAddress d_peer;
+ QuicheConnection d_conn;
+ QuicheConfig d_config;
+ QuicheHTTP3Connection d_http3{nullptr, quiche_h3_conn_free};
+ // buffer request headers by streamID
+ std::unordered_map<uint64_t, h3_headers_t> d_headersBuffers;
+ std::unordered_map<uint64_t, PacketBuffer> d_streamBuffers;
+ std::unordered_map<uint64_t, PacketBuffer> d_streamOutBuffers;
+};
+
+static void sendBackDOH3Unit(DOH3UnitUniquePtr&& unit, const char* description);
+
+struct DOH3ServerConfig
+{
+ DOH3ServerConfig(QuicheConfig&& config_, QuicheHTTP3Config&& http3config_, uint32_t internalPipeBufferSize) :
+ config(std::move(config_)), http3config(std::move(http3config_))
+ {
+ {
+ auto [sender, receiver] = pdns::channel::createObjectQueue<DOH3Unit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, internalPipeBufferSize);
+ d_responseSender = std::move(sender);
+ d_responseReceiver = std::move(receiver);
+ }
+ }
+ DOH3ServerConfig(const DOH3ServerConfig&) = delete;
+ DOH3ServerConfig(DOH3ServerConfig&&) = default;
+ DOH3ServerConfig& operator=(const DOH3ServerConfig&) = delete;
+ DOH3ServerConfig& operator=(DOH3ServerConfig&&) = default;
+ ~DOH3ServerConfig() = default;
+
+ using ConnectionsMap = std::map<PacketBuffer, H3Connection>;
+
+ LocalHolders holders;
+ ConnectionsMap d_connections;
+ QuicheConfig config;
+ QuicheHTTP3Config http3config;
+ ClientState* clientState{nullptr};
+ std::shared_ptr<DOH3Frontend> df{nullptr};
+ pdns::channel::Sender<DOH3Unit> d_responseSender;
+ pdns::channel::Receiver<DOH3Unit> d_responseReceiver;
+};
+
+/* these might seem useless, but they are needed because
+ they need to be declared _after_ the definition of DOH3ServerConfig
+ so that we can use a unique_ptr in DOH3Frontend */
+DOH3Frontend::DOH3Frontend() = default;
+DOH3Frontend::~DOH3Frontend() = default;
+
+class DOH3TCPCrossQuerySender final : public TCPQuerySender
+{
+public:
+ DOH3TCPCrossQuerySender() = default;
+
+ [[nodiscard]] bool active() const override
+ {
+ return true;
+ }
+
+ void handleResponse([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
+ {
+ if (!response.d_idstate.doh3u) {
+ return;
+ }
+
+ auto unit = std::move(response.d_idstate.doh3u);
+ if (unit->dsc == nullptr) {
+ return;
+ }
+
+ unit->response = std::move(response.d_buffer);
+ unit->ids = std::move(response.d_idstate);
+ DNSResponse dnsResponse(unit->ids, unit->response, unit->downstream);
+
+ dnsheader cleartextDH{};
+ memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
+
+ if (!response.isAsync()) {
+
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+
+ dnsResponse.ids.doh3u = std::move(unit);
+
+ if (!processResponse(dnsResponse.ids.doh3u->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dnsResponse, false)) {
+ if (dnsResponse.ids.doh3u) {
+
+ sendBackDOH3Unit(std::move(dnsResponse.ids.doh3u), "Response dropped by rules");
+ }
+ return;
+ }
+
+ if (dnsResponse.isAsynchronous()) {
+ return;
+ }
+
+ unit = std::move(dnsResponse.ids.doh3u);
+ }
+
+ if (!unit->ids.selfGenerated) {
+ double udiff = unit->ids.queryRealTime.udiff();
+ vinfolog("Got answer from %s, relayed to %s (DoH3, %d bytes), took %f us", unit->downstream->d_config.remote.toStringWithPort(), unit->ids.origRemote.toStringWithPort(), unit->response.size(), udiff);
+
+ auto backendProtocol = unit->downstream->getProtocol();
+ if (backendProtocol == dnsdist::Protocol::DoUDP && unit->tcp) {
+ backendProtocol = dnsdist::Protocol::DoTCP;
+ }
+ handleResponseSent(unit->ids, udiff, unit->ids.origRemote, unit->downstream->d_config.remote, unit->response.size(), cleartextDH, backendProtocol, true);
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ if (unit->ids.cs != nullptr) {
+ ++unit->ids.cs->responses;
+ }
+
+ sendBackDOH3Unit(std::move(unit), "Cross-protocol response");
+ }
+
+ void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+ {
+ return handleResponse(now, std::move(response));
+ }
+
+ void notifyIOError([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
+ {
+ if (!response.d_idstate.doh3u) {
+ return;
+ }
+
+ auto unit = std::move(response.d_idstate.doh3u);
+ if (unit->dsc == nullptr) {
+ return;
+ }
+
+ /* this will signal an error */
+ unit->response.clear();
+ unit->ids = std::move(response.d_idstate);
+ sendBackDOH3Unit(std::move(unit), "Cross-protocol error");
+ }
+};
+
+class DOH3CrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+ DOH3CrossProtocolQuery(DOH3UnitUniquePtr&& unit, bool isResponse)
+ {
+ if (isResponse) {
+ /* happens when a response becomes async */
+ query = InternalQuery(std::move(unit->response), std::move(unit->ids));
+ }
+ else {
+ /* we need to duplicate the query here because we might need
+ the existing query later if we get a truncated answer */
+ query = InternalQuery(PacketBuffer(unit->query), std::move(unit->ids));
+ }
+
+ /* it might have been moved when we moved unit->ids */
+ if (unit) {
+ query.d_idstate.doh3u = std::move(unit);
+ }
+
+ /* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload,
+ clearing query.d_proxyProtocolPayloadAdded and unit->proxyProtocolPayloadSize.
+ Leave it for now because we know that the onky case where the payload has been
+ added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT,
+ and we know the TCP/DoT code can handle it. */
+ query.d_proxyProtocolPayloadAdded = query.d_idstate.doh3u->proxyProtocolPayloadSize > 0;
+ downstream = query.d_idstate.doh3u->downstream;
+ }
+
+ void handleInternalError()
+ {
+ sendBackDOH3Unit(std::move(query.d_idstate.doh3u), "DOH3 internal error");
+ }
+
+ std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+ {
+ query.d_idstate.doh3u->downstream = downstream;
+ return s_sender;
+ }
+
+ DNSQuestion getDQ() override
+ {
+ auto& ids = query.d_idstate;
+ DNSQuestion dnsQuestion(ids, query.d_buffer);
+ return dnsQuestion;
+ }
+
+ DNSResponse getDR() override
+ {
+ auto& ids = query.d_idstate;
+ DNSResponse dnsResponse(ids, query.d_buffer, downstream);
+ return dnsResponse;
+ }
+
+ DOH3UnitUniquePtr&& releaseDU()
+ {
+ return std::move(query.d_idstate.doh3u);
+ }
+
+private:
+ static std::shared_ptr<DOH3TCPCrossQuerySender> s_sender;
+};
+
+std::shared_ptr<DOH3TCPCrossQuerySender> DOH3CrossProtocolQuery::s_sender = std::make_shared<DOH3TCPCrossQuerySender>();
+
+static bool tryWriteResponse(H3Connection& conn, const uint64_t streamID, PacketBuffer& response)
+{
+ size_t pos = 0;
+ while (pos < response.size()) {
+ // send_body takes care of setting fin to false if it cannot send the entire content so we can try again.
+ auto res = quiche_h3_send_body(conn.d_http3.get(), conn.d_conn.get(),
+ streamID, &response.at(pos), response.size() - pos, true);
+ if (res == QUICHE_H3_ERR_DONE || res == QUICHE_H3_TRANSPORT_ERR_DONE) {
+ response.erase(response.begin(), response.begin() + static_cast<ssize_t>(pos));
+ return false;
+ }
+ if (res < 0) {
+ // Shutdown with internal error code
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(dnsdist::doq::DOQ_Error_Codes::DOQ_INTERNAL_ERROR));
+ return true;
+ }
+ pos += res;
+ }
+
+ return true;
+}
+
+static void h3_send_response(H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const uint8_t* body, size_t len)
+{
+ std::string status = std::to_string(statusCode);
+ std::string lenStr = std::to_string(len);
+ std::array<quiche_h3_header, 3> headers{
+ (quiche_h3_header){
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ .name = reinterpret_cast<const uint8_t*>(":status"),
+ .name_len = sizeof(":status") - 1,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ .value = reinterpret_cast<const uint8_t*>(status.data()),
+ .value_len = status.size(),
+ },
+ (quiche_h3_header){
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ .name = reinterpret_cast<const uint8_t*>("content-length"),
+ .name_len = sizeof("content-length") - 1,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ .value = reinterpret_cast<const uint8_t*>(lenStr.data()),
+ .value_len = lenStr.size(),
+ },
+ (quiche_h3_header){
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ .name = reinterpret_cast<const uint8_t*>("content-type"),
+ .name_len = sizeof("content-type") - 1,
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ .value = reinterpret_cast<const uint8_t*>("application/dns-message"),
+ .value_len = sizeof("application/dns-message") - 1,
+ },
+ };
+ auto returnValue = quiche_h3_send_response(conn.d_http3.get(), conn.d_conn.get(),
+ streamID, headers.data(),
+ // do not include content-type header info if there is no content
+ (len > 0 && statusCode == 200U ? headers.size() : headers.size() - 1),
+ len == 0);
+ if (returnValue != 0) {
+ /* in theory it could be QUICHE_H3_ERR_STREAM_BLOCKED if the stream is not writable / congested, but we are not going to handle this case */
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(dnsdist::doq::DOQ_Error_Codes::DOQ_INTERNAL_ERROR));
+ return;
+ }
+
+ if (len == 0) {
+ return;
+ }
+
+ size_t pos = 0;
+ while (pos < len) {
+ // send_body takes care of setting fin to false if it cannot send the entire content so we can try again.
+ auto res = quiche_h3_send_body(conn.d_http3.get(), conn.d_conn.get(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic): Quiche API
+ streamID, const_cast<uint8_t*>(body) + pos, len - pos, true);
+ if (res == QUICHE_H3_ERR_DONE || res == QUICHE_H3_TRANSPORT_ERR_DONE) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic): Quiche API
+ conn.d_streamOutBuffers[streamID] = PacketBuffer(body + pos, body + len);
+ return;
+ }
+ if (res < 0) {
+ // Shutdown with internal error code
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(1));
+ return;
+ }
+ pos += res;
+ }
+}
+
+static void h3_send_response(H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const std::string& content)
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ h3_send_response(conn, streamID, statusCode, reinterpret_cast<const uint8_t*>(content.data()), content.size());
+}
+
+static void handleResponse(DOH3Frontend& frontend, H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const PacketBuffer& response)
+{
+ if (statusCode == 200) {
+ ++frontend.d_validResponses;
+ }
+ else {
+ ++frontend.d_errorResponses;
+ }
+ if (response.empty()) {
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_UNSPECIFIED_ERROR));
+ }
+ else {
+ h3_send_response(conn, streamID, statusCode, &response.at(0), response.size());
+ }
+}
+
+void DOH3Frontend::setup()
+{
+ auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+ d_quicheParams.d_alpn = std::string(DOH3_ALPN.begin(), DOH3_ALPN.end());
+ configureQuiche(config, d_quicheParams, true);
+
+ auto http3config = QuicheHTTP3Config(quiche_h3_config_new(), quiche_h3_config_free);
+
+ d_server_config = std::make_unique<DOH3ServerConfig>(std::move(config), std::move(http3config), d_internalPipeBufferSize);
+}
+
+void DOH3Frontend::reloadCertificates()
+{
+ auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+ d_quicheParams.d_alpn = std::string(DOH3_ALPN.begin(), DOH3_ALPN.end());
+ configureQuiche(config, d_quicheParams, true);
+ std::atomic_store_explicit(&d_server_config->config, std::move(config), std::memory_order_release);
+}
+
+static std::optional<std::reference_wrapper<H3Connection>> getConnection(DOH3ServerConfig::ConnectionsMap& connMap, const PacketBuffer& connID)
+{
+ auto iter = connMap.find(connID);
+ if (iter == connMap.end()) {
+ return std::nullopt;
+ }
+ return iter->second;
+}
+
+static void sendBackDOH3Unit(DOH3UnitUniquePtr&& unit, const char* description)
+{
+ if (unit->dsc == nullptr) {
+ return;
+ }
+ try {
+ if (!unit->dsc->d_responseSender.send(std::move(unit))) {
+ ++dnsdist::metrics::g_stats.doh3ResponsePipeFull;
+ vinfolog("Unable to pass a %s to the DoH3 worker thread because the pipe is full", description);
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Unable to pass a %s to the DoH3 worker thread because we couldn't write to the pipe: %s", description, e.what());
+ }
+}
+
+static std::optional<std::reference_wrapper<H3Connection>> createConnection(DOH3ServerConfig& config, const PacketBuffer& serverSideID, const PacketBuffer& originalDestinationID, const ComboAddress& local, const ComboAddress& peer)
+{
+ auto quicheConfig = std::atomic_load_explicit(&config.config, std::memory_order_acquire);
+ auto quicheConn = QuicheConnection(quiche_accept(serverSideID.data(), serverSideID.size(),
+ originalDestinationID.data(), originalDestinationID.size(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const struct sockaddr*>(&local),
+ local.getSocklen(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const struct sockaddr*>(&peer),
+ peer.getSocklen(),
+ quicheConfig.get()),
+ quiche_conn_free);
+
+ if (config.df && !config.df->d_quicheParams.d_keyLogFile.empty()) {
+ quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_quicheParams.d_keyLogFile.c_str());
+ }
+
+ auto conn = H3Connection(peer, std::move(quicheConfig), std::move(quicheConn));
+ auto pair = config.d_connections.emplace(serverSideID, std::move(conn));
+ return pair.first->second;
+}
+
+std::unique_ptr<CrossProtocolQuery> getDOH3CrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
+{
+ if (!dnsQuestion.ids.doh3u) {
+ throw std::runtime_error("Trying to create a DoH3 cross protocol query without a valid DoH3 unit");
+ }
+
+ auto unit = std::move(dnsQuestion.ids.doh3u);
+ if (&dnsQuestion.ids != &unit->ids) {
+ unit->ids = std::move(dnsQuestion.ids);
+ }
+
+ unit->ids.origID = dnsQuestion.getHeader()->id;
+
+ if (!isResponse) {
+ if (unit->query.data() != dnsQuestion.getMutableData().data()) {
+ unit->query = std::move(dnsQuestion.getMutableData());
+ }
+ }
+ else {
+ if (unit->response.data() != dnsQuestion.getMutableData().data()) {
+ unit->response = std::move(dnsQuestion.getMutableData());
+ }
+ }
+
+ return std::make_unique<DOH3CrossProtocolQuery>(std::move(unit), isResponse);
+}
+
+static void processDOH3Query(DOH3UnitUniquePtr&& doh3Unit)
+{
+ const auto handleImmediateResponse = [](DOH3UnitUniquePtr&& unit, [[maybe_unused]] const char* reason) {
+ DEBUGLOG("handleImmediateResponse() reason=" << reason);
+ auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
+ handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->status_code, unit->response);
+ unit->ids.doh3u.reset();
+ };
+
+ auto& ids = doh3Unit->ids;
+ ids.doh3u = std::move(doh3Unit);
+ auto& unit = ids.doh3u;
+ uint16_t queryId = 0;
+ ComboAddress remote;
+
+ try {
+
+ remote = unit->ids.origRemote;
+ DOH3ServerConfig* dsc = unit->dsc;
+ auto& holders = dsc->holders;
+ ClientState& clientState = *dsc->clientState;
+
+ if (!holders.acl->match(remote)) {
+ vinfolog("Query from %s (DoH3) dropped because of ACL", remote.toStringWithPort());
+ ++dnsdist::metrics::g_stats.aclDrops;
+ unit->response.clear();
+
+ unit->status_code = 403;
+ handleImmediateResponse(std::move(unit), "DoH3 query dropped because of ACL");
+ return;
+ }
+
+ if (unit->query.size() < sizeof(dnsheader)) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ unit->response.clear();
+
+ unit->status_code = 400;
+ handleImmediateResponse(std::move(unit), "DoH3 non-compliant query");
+ return;
+ }
+
+ ++clientState.queries;
+ ++dnsdist::metrics::g_stats.queries;
+ unit->ids.queryRealTime.start();
+
+ {
+ /* don't keep that pointer around, it will be invalidated if the buffer is ever resized */
+ dnsheader_aligned dnsHeader(unit->query.data());
+
+ if (!checkQueryHeaders(*dnsHeader, clientState)) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+ header.rcode = RCode::ServFail;
+ header.qr = true;
+ return true;
+ });
+ unit->response = std::move(unit->query);
+
+ unit->status_code = 400;
+ handleImmediateResponse(std::move(unit), "DoH3 invalid headers");
+ return;
+ }
+
+ if (dnsHeader->qdcount == 0) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+ header.rcode = RCode::NotImp;
+ header.qr = true;
+ return true;
+ });
+ unit->response = std::move(unit->query);
+
+ unit->status_code = 400;
+ handleImmediateResponse(std::move(unit), "DoH3 empty query");
+ return;
+ }
+
+ queryId = ntohs(dnsHeader->id);
+ }
+
+ auto downstream = unit->downstream;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ unit->ids.qname = DNSName(reinterpret_cast<const char*>(unit->query.data()), static_cast<int>(unit->query.size()), sizeof(dnsheader), false, &unit->ids.qtype, &unit->ids.qclass);
+ DNSQuestion dnsQuestion(unit->ids, unit->query);
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&ids](dnsheader& header) {
+ const uint16_t* flags = getFlagsFromDNSHeader(&header);
+ ids.origFlags = *flags;
+ return true;
+ });
+ unit->ids.cs = &clientState;
+
+ auto result = processQuery(dnsQuestion, holders, downstream);
+ if (result == ProcessQueryResult::Drop) {
+ unit->status_code = 403;
+ handleImmediateResponse(std::move(unit), "DoH3 dropped query");
+ return;
+ }
+ if (result == ProcessQueryResult::Asynchronous) {
+ return;
+ }
+ if (result == ProcessQueryResult::SendAnswer) {
+ if (unit->response.empty()) {
+ unit->response = std::move(unit->query);
+ }
+ if (unit->response.size() >= sizeof(dnsheader)) {
+ const dnsheader_aligned dnsHeader(unit->response.data());
+
+ handleResponseSent(unit->ids.qname, QType(unit->ids.qtype), 0., unit->ids.origDest, ComboAddress(), unit->response.size(), *dnsHeader, dnsdist::Protocol::DoH3, dnsdist::Protocol::DoH3, false);
+ }
+ handleImmediateResponse(std::move(unit), "DoH3 self-answered response");
+ return;
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ if (unit->ids.cs != nullptr) {
+ ++unit->ids.cs->responses;
+ }
+
+ if (result != ProcessQueryResult::PassToBackend) {
+ unit->status_code = 500;
+ handleImmediateResponse(std::move(unit), "DoH3 no backend available");
+ return;
+ }
+
+ if (downstream == nullptr) {
+ unit->status_code = 502;
+ handleImmediateResponse(std::move(unit), "DoH3 no backend available");
+ return;
+ }
+
+ unit->downstream = downstream;
+
+ std::string proxyProtocolPayload;
+ /* we need to do this _before_ creating the cross protocol query because
+ after that the buffer will have been moved */
+ if (downstream->d_config.useProxyProtocol) {
+ proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+ }
+
+ unit->ids.origID = htons(queryId);
+ unit->tcp = true;
+
+ /* this moves unit->ids, careful! */
+ auto cpq = std::make_unique<DOH3CrossProtocolQuery>(std::move(unit), false);
+ cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+ if (downstream->passCrossProtocolQuery(std::move(cpq))) {
+ return;
+ }
+ // NOLINTNEXTLINE(bugprone-use-after-move): it was only moved if the call succeeded
+ unit = cpq->releaseDU();
+ unit->status_code = 500;
+ handleImmediateResponse(std::move(unit), "DoH3 internal error");
+ return;
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got an error in DOH3 question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
+ unit->status_code = 500;
+ handleImmediateResponse(std::move(unit), "DoH3 internal error");
+ return;
+ }
+}
+
+static void doh3_dispatch_query(DOH3ServerConfig& dsc, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, const PacketBuffer& serverConnID, const uint64_t streamID)
+{
+ try {
+ auto unit = std::make_unique<DOH3Unit>(std::move(query));
+ unit->dsc = &dsc;
+ unit->ids.origDest = local;
+ unit->ids.origRemote = remote;
+ unit->ids.protocol = dnsdist::Protocol::DoH3;
+ unit->serverConnID = serverConnID;
+ unit->streamID = streamID;
+
+ processDOH3Query(std::move(unit));
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Had error handling DoH3 DNS packet from %s: %s", remote.toStringWithPort(), exp.what());
+ }
+}
+
+static void flushResponses(pdns::channel::Receiver<DOH3Unit>& receiver)
+{
+ for (;;) {
+ try {
+ auto tmp = receiver.receive();
+ if (!tmp) {
+ return;
+ }
+
+ auto unit = std::move(*tmp);
+ auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
+ if (conn) {
+ handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->status_code, unit->response);
+ }
+ }
+ catch (const std::exception& e) {
+ errlog("Error while processing response received over DoH3: %s", e.what());
+ }
+ catch (...) {
+ errlog("Unspecified error while processing response received over DoH3");
+ }
+ }
+}
+
+static void flushStalledResponses(H3Connection& conn)
+{
+ for (auto streamIt = conn.d_streamOutBuffers.begin(); streamIt != conn.d_streamOutBuffers.end();) {
+ const auto streamID = streamIt->first;
+ auto& response = streamIt->second;
+ if (quiche_conn_stream_writable(conn.d_conn.get(), streamID, response.size()) == 1) {
+ if (tryWriteResponse(conn, streamID, response)) {
+ streamIt = conn.d_streamOutBuffers.erase(streamIt);
+ continue;
+ }
+ }
+ ++streamIt;
+ }
+}
+
+static void processH3HeaderEvent(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, const uint64_t streamID, quiche_h3_event* event)
+{
+ auto handleImmediateError = [&clientState, &frontend, &conn, streamID](const char* msg) {
+ DEBUGLOG(msg);
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ ++frontend.d_errorResponses;
+ h3_send_response(conn, streamID, 400, msg);
+ };
+
+ auto& headers = conn.d_headersBuffers.at(streamID);
+ // Callback result. Any value other than 0 will interrupt further header processing.
+ int cbresult = quiche_h3_event_for_each_header(
+ event,
+ [](uint8_t* name, size_t name_len, uint8_t* value, size_t value_len, void* argp) -> int {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ std::string_view key(reinterpret_cast<char*>(name), name_len);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ std::string_view content(reinterpret_cast<char*>(value), value_len);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+ auto* headersptr = reinterpret_cast<h3_headers_t*>(argp);
+ headersptr->emplace(key, content);
+ return 0;
+ },
+ &headers);
+
+#ifdef DEBUGLOG_ENABLED
+ DEBUGLOG("Processed headers of stream " << streamID);
+ for (const auto& [key, value] : headers) {
+ DEBUGLOG(" " << key << ": " << value);
+ }
+#endif
+ if (cbresult != 0 || headers.count(":method") == 0) {
+ handleImmediateError("Unable to process query headers");
+ return;
+ }
+
+ if (headers.at(":method") == "GET") {
+ if (headers.count(":path") == 0 || headers.at(":path").empty()) {
+ handleImmediateError("Path not found");
+ return;
+ }
+ const auto& path = headers.at(":path");
+ auto payload = dnsdist::doh::getPayloadFromPath(path);
+ if (!payload) {
+ handleImmediateError("Unable to find the DNS parameter");
+ return;
+ }
+ if (payload->size() < sizeof(dnsheader)) {
+ handleImmediateError("DoH3 non-compliant query");
+ return;
+ }
+ DEBUGLOG("Dispatching GET query");
+ doh3_dispatch_query(*(frontend.d_server_config), std::move(*payload), clientState.local, client, serverConnID, streamID);
+ conn.d_streamBuffers.erase(streamID);
+ conn.d_headersBuffers.erase(streamID);
+ return;
+ }
+
+ if (headers.at(":method") == "POST") {
+ if (!quiche_h3_event_headers_has_body(event)) {
+ handleImmediateError("Empty POST query");
+ }
+ return;
+ }
+
+ handleImmediateError("Unsupported HTTP method");
+}
+
+static void processH3DataEvent(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, const uint64_t streamID, quiche_h3_event* event, PacketBuffer& buffer)
+{
+ auto handleImmediateError = [&clientState, &frontend, &conn, streamID](const char* msg) {
+ DEBUGLOG(msg);
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ ++frontend.d_errorResponses;
+ h3_send_response(conn, streamID, 400, msg);
+ };
+ auto& headers = conn.d_headersBuffers.at(streamID);
+
+ if (headers.at(":method") != "POST") {
+ handleImmediateError("DATA frame for non-POST method");
+ return;
+ }
+
+ if (headers.count("content-type") == 0 || headers.at("content-type") != "application/dns-message") {
+ handleImmediateError("Unsupported content-type");
+ return;
+ }
+
+ buffer.resize(std::numeric_limits<uint16_t>::max());
+ auto& streamBuffer = conn.d_streamBuffers[streamID];
+
+ while (true) {
+ buffer.resize(std::numeric_limits<uint16_t>::max());
+ ssize_t len = quiche_h3_recv_body(conn.d_http3.get(),
+ conn.d_conn.get(), streamID,
+ buffer.data(), buffer.size());
+
+ if (len <= 0) {
+ break;
+ }
+
+ buffer.resize(static_cast<size_t>(len));
+ streamBuffer.insert(streamBuffer.end(), buffer.begin(), buffer.end());
+ }
+
+ if (!quiche_conn_stream_finished(conn.d_conn.get(), streamID)) {
+ return;
+ }
+
+ if (streamBuffer.size() < sizeof(dnsheader)) {
+ conn.d_streamBuffers.erase(streamID);
+ handleImmediateError("DoH3 non-compliant query");
+ return;
+ }
+
+ DEBUGLOG("Dispatching POST query");
+ doh3_dispatch_query(*(frontend.d_server_config), std::move(streamBuffer), clientState.local, client, serverConnID, streamID);
+ conn.d_headersBuffers.erase(streamID);
+ conn.d_streamBuffers.erase(streamID);
+}
+
+static void processH3Events(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, PacketBuffer& buffer)
+{
+ while (true) {
+ quiche_h3_event* event{nullptr};
+ // Processes HTTP/3 data received from the peer
+ const int64_t streamID = quiche_h3_conn_poll(conn.d_http3.get(),
+ conn.d_conn.get(),
+ &event);
+
+ if (streamID < 0) {
+ break;
+ }
+ conn.d_headersBuffers.try_emplace(streamID, h3_headers_t{});
+
+ switch (quiche_h3_event_type(event)) {
+ case QUICHE_H3_EVENT_HEADERS: {
+ processH3HeaderEvent(clientState, frontend, conn, client, serverConnID, streamID, event);
+ break;
+ }
+ case QUICHE_H3_EVENT_DATA: {
+ processH3DataEvent(clientState, frontend, conn, client, serverConnID, streamID, event, buffer);
+ break;
+ }
+ case QUICHE_H3_EVENT_FINISHED:
+ case QUICHE_H3_EVENT_RESET:
+ case QUICHE_H3_EVENT_PRIORITY_UPDATE:
+ case QUICHE_H3_EVENT_GOAWAY:
+ break;
+ }
+
+ quiche_h3_event_free(event);
+ }
+}
+
+static void handleSocketReadable(DOH3Frontend& frontend, ClientState& clientState, Socket& sock, PacketBuffer& buffer)
+{
+ // destination connection ID, will have to be sent as original destination connection ID
+ PacketBuffer serverConnID;
+ // source connection ID, will have to be sent as destination connection ID
+ PacketBuffer clientConnID;
+ PacketBuffer tokenBuf;
+ while (true) {
+ ComboAddress client;
+ buffer.resize(4096);
+ if (!sock.recvFromAsync(buffer, client) || buffer.empty()) {
+ return;
+ }
+ DEBUGLOG("Received DoH3 datagram of size " << buffer.size() << " from " << client.toStringWithPort());
+
+ uint32_t version{0};
+ uint8_t type{0};
+ std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> scid{};
+ size_t scid_len = scid.size();
+ std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> dcid{};
+ size_t dcid_len = dcid.size();
+ std::array<uint8_t, MAX_TOKEN_LEN> token{};
+ size_t token_len = token.size();
+
+ auto res = quiche_header_info(buffer.data(), buffer.size(), LOCAL_CONN_ID_LEN,
+ &version, &type,
+ scid.data(), &scid_len,
+ dcid.data(), &dcid_len,
+ token.data(), &token_len);
+ if (res != 0) {
+ DEBUGLOG("Error in quiche_header_info: " << res);
+ continue;
+ }
+
+ serverConnID.assign(dcid.begin(), dcid.begin() + dcid_len);
+ // source connection ID, will have to be sent as destination connection ID
+ clientConnID.assign(scid.begin(), scid.begin() + scid_len);
+ auto conn = getConnection(frontend.d_server_config->d_connections, serverConnID);
+
+ if (!conn) {
+ DEBUGLOG("Connection not found");
+ if (type != static_cast<uint8_t>(DOQ_Packet_Types::QUIC_PACKET_TYPE_INITIAL)) {
+ DEBUGLOG("Packet is not initial");
+ continue;
+ }
+
+ if (!quiche_version_is_supported(version)) {
+ DEBUGLOG("Unsupported version");
+ ++frontend.d_doh3UnsupportedVersionErrors;
+ handleVersionNegociation(sock, clientConnID, serverConnID, client, buffer);
+ continue;
+ }
+
+ if (token_len == 0) {
+ /* stateless retry */
+ DEBUGLOG("No token received");
+ handleStatelessRetry(sock, clientConnID, serverConnID, client, version, buffer);
+ continue;
+ }
+
+ tokenBuf.assign(token.begin(), token.begin() + token_len);
+ auto originalDestinationID = validateToken(tokenBuf, client);
+ if (!originalDestinationID) {
+ ++frontend.d_doh3InvalidTokensReceived;
+ DEBUGLOG("Discarding invalid token");
+ continue;
+ }
+
+ DEBUGLOG("Creating a new connection");
+ conn = createConnection(*frontend.d_server_config, serverConnID, *originalDestinationID, clientState.local, client);
+ if (!conn) {
+ continue;
+ }
+ }
+ DEBUGLOG("Connection found");
+ quiche_recv_info recv_info = {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<struct sockaddr*>(&client),
+ client.getSocklen(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<struct sockaddr*>(&clientState.local),
+ clientState.local.getSocklen(),
+ };
+
+ auto done = quiche_conn_recv(conn->get().d_conn.get(), buffer.data(), buffer.size(), &recv_info);
+ if (done < 0) {
+ continue;
+ }
+
+ if (quiche_conn_is_established(conn->get().d_conn.get()) || quiche_conn_is_in_early_data(conn->get().d_conn.get())) {
+ DEBUGLOG("Connection is established");
+
+ if (!conn->get().d_http3) {
+ conn->get().d_http3 = QuicheHTTP3Connection(quiche_h3_conn_new_with_transport(conn->get().d_conn.get(), frontend.d_server_config->http3config.get()),
+ quiche_h3_conn_free);
+ if (!conn->get().d_http3) {
+ continue;
+ }
+ DEBUGLOG("Successfully created HTTP/3 connection");
+ }
+
+ processH3Events(clientState, frontend, conn->get(), client, serverConnID, buffer);
+
+ flushEgress(sock, conn->get().d_conn, client, buffer);
+ }
+ else {
+ DEBUGLOG("Connection not established");
+ }
+ }
+}
+
+// this is the entrypoint from dnsdist.cc
+void doh3Thread(ClientState* clientState)
+{
+ try {
+ std::shared_ptr<DOH3Frontend>& frontend = clientState->doh3Frontend;
+
+ frontend->d_server_config->clientState = clientState;
+ frontend->d_server_config->df = clientState->doh3Frontend;
+
+ setThreadName("dnsdist/doh3");
+
+ Socket sock(clientState->udpFD);
+ sock.setNonBlocking();
+
+ auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+
+ auto responseReceiverFD = frontend->d_server_config->d_responseReceiver.getDescriptor();
+ mplexer->addReadFD(sock.getHandle(), [](int, FDMultiplexer::funcparam_t&) {});
+ mplexer->addReadFD(responseReceiverFD, [](int, FDMultiplexer::funcparam_t&) {});
+ std::vector<int> readyFDs;
+ PacketBuffer buffer(4096);
+ while (true) {
+ readyFDs.clear();
+ mplexer->getAvailableFDs(readyFDs, 500);
+
+ try {
+ if (std::find(readyFDs.begin(), readyFDs.end(), sock.getHandle()) != readyFDs.end()) {
+ handleSocketReadable(*frontend, *clientState, sock, buffer);
+ }
+
+ if (std::find(readyFDs.begin(), readyFDs.end(), responseReceiverFD) != readyFDs.end()) {
+ flushResponses(frontend->d_server_config->d_responseReceiver);
+ }
+
+ for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
+ quiche_conn_on_timeout(conn->second.d_conn.get());
+
+ flushEgress(sock, conn->second.d_conn, conn->second.d_peer, buffer);
+
+ if (quiche_conn_is_closed(conn->second.d_conn.get())) {
+#ifdef DEBUGLOG_ENABLED
+ quiche_stats stats;
+ quiche_path_stats path_stats;
+
+ quiche_conn_stats(conn->second.d_conn.get(), &stats);
+ quiche_conn_path_stats(conn->second.d_conn.get(), 0, &path_stats);
+
+ DEBUGLOG("Connection (DoH3) closed, recv=" << stats.recv << " sent=" << stats.sent << " lost=" << stats.lost << " rtt=" << path_stats.rtt << "ns cwnd=" << path_stats.cwnd);
+#endif
+ conn = frontend->d_server_config->d_connections.erase(conn);
+ }
+ else {
+ flushStalledResponses(conn->second);
+ ++conn;
+ }
+ }
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Caught exception in the main DoH3 thread: %s", exp.what());
+ }
+ catch (...) {
+ vinfolog("Unknown exception in the main DoH3 thread");
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ DEBUGLOG("Caught fatal error in the main DoH3 thread: " << e.what());
+ }
+}
+
+#endif /* HAVE_DNS_OVER_HTTP3 */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <memory>
+
+#include "config.h"
+#include "channel.hh"
+#include "iputils.hh"
+#include "libssl.hh"
+#include "noinitvector.hh"
+#include "stat_t.hh"
+#include "dnsdist-idstate.hh"
+
+struct DOH3ServerConfig;
+struct DownstreamState;
+
+#ifdef HAVE_DNS_OVER_HTTP3
+
+#include "doq-common.hh"
+
+struct DOH3Frontend
+{
+ DOH3Frontend();
+ DOH3Frontend(const DOH3Frontend&) = delete;
+ DOH3Frontend(DOH3Frontend&&) = delete;
+ DOH3Frontend& operator=(const DOH3Frontend&) = delete;
+ DOH3Frontend& operator=(DOH3Frontend&&) = delete;
+ ~DOH3Frontend();
+
+ void setup();
+ void reloadCertificates();
+
+ std::unique_ptr<DOH3ServerConfig> d_server_config;
+ ComboAddress d_local;
+
+#ifdef __linux__
+ // On Linux this gives us 128k pending queries (default is 8192 queries),
+ // which should be enough to deal with huge spikes
+ uint32_t d_internalPipeBufferSize{1024 * 1024};
+#else
+ uint32_t d_internalPipeBufferSize{0};
+#endif
+
+ dnsdist::doq::QuicheParams d_quicheParams;
+ pdns::stat_t d_doh3UnsupportedVersionErrors{0}; // Unsupported protocol version errors
+ pdns::stat_t d_doh3InvalidTokensReceived{0}; // Discarded received tokens
+ pdns::stat_t d_validResponses{0}; // Valid responses sent
+ pdns::stat_t d_errorResponses{0}; // Empty responses (no backend, drops, invalid queries, etc.)
+};
+
+struct DOH3Unit
+{
+ DOH3Unit(PacketBuffer&& query_) :
+ query(std::move(query_))
+ {
+ }
+
+ DOH3Unit(const DOH3Unit&) = delete;
+ DOH3Unit& operator=(const DOH3Unit&) = delete;
+
+ InternalQueryState ids;
+ PacketBuffer query;
+ PacketBuffer response;
+ PacketBuffer serverConnID;
+ std::shared_ptr<DownstreamState> downstream{nullptr};
+ DOH3ServerConfig* dsc{nullptr};
+ uint64_t streamID{0};
+ size_t proxyProtocolPayloadSize{0};
+ uint16_t status_code{200};
+ /* whether the query was re-sent to the backend over
+ TCP after receiving a truncated answer over UDP */
+ bool tcp{false};
+};
+
+using DOH3UnitUniquePtr = std::unique_ptr<DOH3Unit>;
+
+struct CrossProtocolQuery;
+struct DNSQuestion;
+std::unique_ptr<CrossProtocolQuery> getDOH3CrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse);
+
+void doh3Thread(ClientState* clientState);
+
+#else
+
+struct DOH3Unit
+{
+};
+
+struct DOH3Frontend
+{
+ DOH3Frontend()
+ {
+ }
+ void setup()
+ {
+ }
+};
+
+#endif
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <mutex>
+#include <sys/time.h>
+
+#include "dolog.hh"
+
+namespace dnsdist::logging
+{
+std::optional<std::ofstream> LoggingConfiguration::s_verboseStream{std::nullopt};
+std::string LoggingConfiguration::s_structuredLevelPrefix{"prio"};
+LoggingConfiguration::TimeFormat LoggingConfiguration::s_structuredTimeFormat{LoggingConfiguration::TimeFormat::Numeric};
+bool LoggingConfiguration::s_structuredLogging{false};
+bool LoggingConfiguration::s_logTimestamps{false};
+bool LoggingConfiguration::s_syslog{false};
+
+namespace
+{
+ const char* getTimeFormat()
+ {
+ if (!dnsdist::logging::LoggingConfiguration::getStructuredLogging()) {
+ return "%b %d %H:%M:%S ";
+ }
+
+ auto format = dnsdist::logging::LoggingConfiguration::getStructuredLoggingTimeFormat();
+ if (format == dnsdist::logging::LoggingConfiguration::TimeFormat::ISO8601) {
+ return "%FT%H:%M:%S%z";
+ }
+ return nullptr;
+ }
+}
+
+void logTime(std::ostream& stream)
+{
+ std::array<char, 50> buffer{""};
+
+ if (LoggingConfiguration::getStructuredLogging() && LoggingConfiguration::getStructuredLoggingTimeFormat() == LoggingConfiguration::TimeFormat::Numeric) {
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
+ snprintf(buffer.data(), buffer.size(), "%lld.%03ld", static_cast<long long>(now.tv_sec), static_cast<long>(now.tv_usec / 1000));
+ }
+ else {
+ const auto* timeFormat = getTimeFormat();
+ if (timeFormat == nullptr) {
+ return;
+ }
+
+ time_t now{0};
+ time(&now);
+ struct tm localNow
+ {
+ };
+ localtime_r(&now, &localNow);
+
+ {
+ // strftime is not thread safe, it can access locale information
+ static std::mutex mutex;
+ auto lock = std::lock_guard(mutex);
+
+ if (strftime(buffer.data(), buffer.size(), timeFormat, &localNow) == 0) {
+ buffer[0] = '\0';
+ }
+ }
+ }
+
+ if (dnsdist::logging::LoggingConfiguration::getStructuredLogging()) {
+ stream << "ts=" << std::quoted(buffer.data()) << " ";
+ }
+ else {
+ stream << buffer.data();
+ }
+}
+
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "doq-common.hh"
+#include "dnsdist-random.hh"
+#include "libssl.hh"
+
+#ifdef HAVE_DNS_OVER_QUIC
+
+#if 0
+#define DEBUGLOG_ENABLED
+#define DEBUGLOG(x) std::cerr << x << std::endl;
+#else
+#define DEBUGLOG(x)
+#endif
+
+namespace dnsdist::doq
+{
+
+static const std::string s_quicRetryTokenKey = dnsdist::crypto::authenticated::newKey(false);
+
+PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
+{
+ try {
+ dnsdist::crypto::authenticated::Nonce nonce;
+ nonce.init();
+
+ const auto addrBytes = peer.toByteString();
+ // this token will be valid for 60s
+ const uint64_t ttd = time(nullptr) + 60U;
+ PacketBuffer plainTextToken;
+ plainTextToken.reserve(sizeof(ttd) + addrBytes.size() + dcid.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ plainTextToken.insert(plainTextToken.end(), reinterpret_cast<const uint8_t*>(&ttd), reinterpret_cast<const uint8_t*>(&ttd) + sizeof(ttd));
+ plainTextToken.insert(plainTextToken.end(), addrBytes.begin(), addrBytes.end());
+ plainTextToken.insert(plainTextToken.end(), dcid.begin(), dcid.end());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ const auto encryptedToken = dnsdist::crypto::authenticated::encryptSym(std::string_view(reinterpret_cast<const char*>(plainTextToken.data()), plainTextToken.size()), s_quicRetryTokenKey, nonce, false);
+ // a bit sad, let's see if we can do better later
+ PacketBuffer encryptedTokenPacket;
+ encryptedTokenPacket.reserve(encryptedToken.size() + nonce.value.size());
+ encryptedTokenPacket.insert(encryptedTokenPacket.begin(), encryptedToken.begin(), encryptedToken.end());
+ encryptedTokenPacket.insert(encryptedTokenPacket.begin(), nonce.value.begin(), nonce.value.end());
+ return encryptedTokenPacket;
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Error while minting DoH3 token: %s", exp.what());
+ throw;
+ }
+}
+
+void fillRandom(PacketBuffer& buffer, size_t size)
+{
+ buffer.reserve(size);
+ while (size > 0) {
+ buffer.insert(buffer.end(), dnsdist::getRandomValue(std::numeric_limits<uint8_t>::max()));
+ --size;
+ }
+}
+
+std::optional<PacketBuffer> getCID()
+{
+ PacketBuffer buffer;
+
+ fillRandom(buffer, LOCAL_CONN_ID_LEN);
+
+ return buffer;
+}
+
+// returns the original destination ID if the token is valid, nothing otherwise
+std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer)
+{
+ try {
+ dnsdist::crypto::authenticated::Nonce nonce;
+ auto addrBytes = peer.toByteString();
+ const uint64_t now = time(nullptr);
+ const auto minimumSize = nonce.value.size() + sizeof(now) + addrBytes.size();
+ if (token.size() <= minimumSize) {
+ return std::nullopt;
+ }
+
+ memcpy(nonce.value.data(), token.data(), nonce.value.size());
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ auto cipher = std::string_view(reinterpret_cast<const char*>(&token.at(nonce.value.size())), token.size() - nonce.value.size());
+ auto plainText = dnsdist::crypto::authenticated::decryptSym(cipher, s_quicRetryTokenKey, nonce, false);
+
+ if (plainText.size() <= sizeof(now) + addrBytes.size()) {
+ return std::nullopt;
+ }
+
+ uint64_t ttd{0};
+ memcpy(&ttd, plainText.data(), sizeof(ttd));
+ if (ttd < now) {
+ return std::nullopt;
+ }
+
+ if (std::memcmp(&plainText.at(sizeof(ttd)), &*addrBytes.begin(), addrBytes.size()) != 0) {
+ return std::nullopt;
+ }
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+ return PacketBuffer(plainText.begin() + (sizeof(ttd) + addrBytes.size()), plainText.end());
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Error while validating DoH3 token: %s", exp.what());
+ return std::nullopt;
+ }
+}
+
+void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version, PacketBuffer& buffer)
+{
+ auto newServerConnID = getCID();
+ if (!newServerConnID) {
+ return;
+ }
+
+ auto token = mintToken(serverConnID, peer);
+
+ buffer.resize(MAX_DATAGRAM_SIZE);
+ auto written = quiche_retry(clientConnID.data(), clientConnID.size(),
+ serverConnID.data(), serverConnID.size(),
+ newServerConnID->data(), newServerConnID->size(),
+ token.data(), token.size(),
+ version,
+ buffer.data(), buffer.size());
+
+ if (written < 0) {
+ DEBUGLOG("failed to create retry packet " << written);
+ return;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ sock.sendTo(reinterpret_cast<const char*>(buffer.data()), static_cast<size_t>(written), peer);
+}
+
+void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, PacketBuffer& buffer)
+{
+ buffer.resize(MAX_DATAGRAM_SIZE);
+
+ auto written = quiche_negotiate_version(clientConnID.data(), clientConnID.size(),
+ serverConnID.data(), serverConnID.size(),
+ buffer.data(), buffer.size());
+
+ if (written < 0) {
+ DEBUGLOG("failed to create vneg packet " << written);
+ return;
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ sock.sendTo(reinterpret_cast<const char*>(buffer.data()), static_cast<size_t>(written), peer);
+}
+
+void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, PacketBuffer& buffer)
+{
+ buffer.resize(MAX_DATAGRAM_SIZE);
+ quiche_send_info send_info;
+
+ while (true) {
+ auto written = quiche_conn_send(conn.get(), buffer.data(), buffer.size(), &send_info);
+ if (written == QUICHE_ERR_DONE) {
+ return;
+ }
+
+ if (written < 0) {
+ return;
+ }
+ // FIXME pacing (as send_info.at should tell us when to send the packet) ?
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ sock.sendTo(reinterpret_cast<const char*>(buffer.data()), static_cast<size_t>(written), peer);
+ }
+}
+
+void configureQuiche(QuicheConfig& config, const QuicheParams& params, bool isHTTP)
+{
+ for (const auto& pair : params.d_tlsConfig.d_certKeyPairs) {
+ auto res = quiche_config_load_cert_chain_from_pem_file(config.get(), pair.d_cert.c_str());
+ if (res != 0) {
+ throw std::runtime_error("Error loading the server certificate: " + std::to_string(res));
+ }
+ if (pair.d_key) {
+ res = quiche_config_load_priv_key_from_pem_file(config.get(), pair.d_key->c_str());
+ if (res != 0) {
+ throw std::runtime_error("Error loading the server key: " + std::to_string(res));
+ }
+ }
+ }
+
+ {
+ auto res = quiche_config_set_application_protos(config.get(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const uint8_t*>(params.d_alpn.data()),
+ params.d_alpn.size());
+ if (res != 0) {
+ throw std::runtime_error("Error setting ALPN: " + std::to_string(res));
+ }
+ }
+
+ quiche_config_set_max_idle_timeout(config.get(), params.d_idleTimeout * 1000);
+ /* maximum size of an outgoing packet, which means the buffer we pass to quiche_conn_send() should be at least that big */
+ quiche_config_set_max_send_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
+ quiche_config_set_max_recv_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
+
+ // The number of concurrent remotely-initiated bidirectional streams to be open at any given time
+ // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi
+ // 0 means none will get accepted, that's why we have a default value of 65535
+ quiche_config_set_initial_max_streams_bidi(config.get(), params.d_maxInFlight);
+
+ // The number of bytes of incoming stream data to be buffered for each localy or remotely-initiated bidirectional stream
+ quiche_config_set_initial_max_stream_data_bidi_local(config.get(), 8192);
+ quiche_config_set_initial_max_stream_data_bidi_remote(config.get(), 8192);
+
+ if (isHTTP) {
+ /* see rfc9114 section 6.2. Unidirectional Streams:
+ Each endpoint needs to create at least one unidirectional stream for the HTTP control stream.
+ QPACK requires two additional unidirectional streams, and other extensions might require further streams.
+ Therefore, the transport parameters sent by both clients and servers MUST allow the peer to create at least three
+ unidirectional streams.
+ These transport parameters SHOULD also provide at least 1,024 bytes of flow-control credit to each unidirectional stream.
+ */
+ quiche_config_set_initial_max_streams_uni(config.get(), 3U);
+ quiche_config_set_initial_max_stream_data_uni(config.get(), 1024U);
+ }
+
+ // The number of total bytes of incoming stream data to be buffered for the whole connection
+ // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data
+ quiche_config_set_initial_max_data(config.get(), 8192 * params.d_maxInFlight);
+ if (!params.d_keyLogFile.empty()) {
+ quiche_config_log_keys(config.get());
+ }
+
+ auto algo = dnsdist::doq::s_available_cc_algorithms.find(params.d_ccAlgo);
+ if (algo != dnsdist::doq::s_available_cc_algorithms.end()) {
+ quiche_config_set_cc_algorithm(config.get(), static_cast<enum quiche_cc_algorithm>(algo->second));
+ }
+
+ {
+ PacketBuffer resetToken;
+ fillRandom(resetToken, 16);
+ quiche_config_set_stateless_reset_token(config.get(), resetToken.data());
+ }
+}
+
+};
+
+#endif
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <map>
+#include <memory>
+
+#include "config.h"
+
+#if defined(HAVE_DNS_OVER_QUIC) || defined(HAVE_DNS_OVER_HTTP3)
+
+#include <quiche.h>
+
+#include "dolog.hh"
+#include "noinitvector.hh"
+#include "sstuff.hh"
+#include "libssl.hh"
+#include "dnsdist-crypto.hh"
+
+namespace dnsdist::doq
+{
+
+static const std::map<const std::string, int> s_available_cc_algorithms = {
+ {"reno", QUICHE_CC_RENO},
+ {"cubic", QUICHE_CC_CUBIC},
+ {"bbr", QUICHE_CC_BBR},
+};
+
+using QuicheConnection = std::unique_ptr<quiche_conn, decltype(&quiche_conn_free)>;
+using QuicheHTTP3Connection = std::unique_ptr<quiche_h3_conn, decltype(&quiche_h3_conn_free)>;
+using QuicheConfig = std::shared_ptr<quiche_config>;
+using QuicheHTTP3Config = std::unique_ptr<quiche_h3_config, decltype(&quiche_h3_config_free)>;
+
+struct QuicheParams
+{
+ TLSConfig d_tlsConfig;
+ std::string d_keyLogFile;
+ uint64_t d_idleTimeout{5};
+ uint64_t d_maxInFlight{65535};
+ std::string d_ccAlgo{"reno"};
+ std::string d_alpn;
+};
+
+/* from rfc9250 section-4.3 */
+enum class DOQ_Error_Codes : uint64_t
+{
+ DOQ_NO_ERROR = 0,
+ DOQ_INTERNAL_ERROR = 1,
+ DOQ_PROTOCOL_ERROR = 2,
+ DOQ_REQUEST_CANCELLED = 3,
+ DOQ_EXCESSIVE_LOAD = 4,
+ DOQ_UNSPECIFIED_ERROR = 5
+};
+
+/* Quiche type values do not match rfc9000 */
+enum class DOQ_Packet_Types : uint8_t
+{
+ QUIC_PACKET_TYPE_INITIAL = 1,
+ QUIC_PACKET_TYPE_RETRY = 2,
+ QUIC_PACKET_TYPE_HANDSHAKE = 3,
+ QUIC_PACKET_TYPE_ZERO_RTT = 4,
+ QUIC_PACKET_TYPE_SHORT = 5,
+ QUIC_PACKET_TYPE_VERSION_NEGOTIATION = 6
+};
+
+static constexpr size_t MAX_TOKEN_LEN = dnsdist::crypto::authenticated::getEncryptedSize(std::tuple_size<decltype(dnsdist::crypto::authenticated::Nonce::value)>{} /* nonce */ + sizeof(uint64_t) /* TTD */ + 16 /* IPv6 */ + QUICHE_MAX_CONN_ID_LEN);
+static constexpr size_t MAX_DATAGRAM_SIZE = 1200;
+static constexpr size_t LOCAL_CONN_ID_LEN = 16;
+static constexpr std::array<uint8_t, 4> DOQ_ALPN{'\x03', 'd', 'o', 'q'};
+static constexpr std::array<uint8_t, 3> DOH3_ALPN{'\x02', 'h', '3'};
+
+void fillRandom(PacketBuffer& buffer, size_t size);
+std::optional<PacketBuffer> getCID();
+PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer);
+std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer);
+void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version, PacketBuffer& buffer);
+void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, PacketBuffer& buffer);
+void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, PacketBuffer& buffer);
+void configureQuiche(QuicheConfig& config, const QuicheParams& params, bool isHTTP);
+
+};
+
+#endif
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "doq.hh"
+
+#ifdef HAVE_DNS_OVER_QUIC
+#include <quiche.h>
+
+#include "dolog.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "sstuff.hh"
+#include "threadname.hh"
+
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-random.hh"
+
+#include "doq-common.hh"
+
+using namespace dnsdist::doq;
+
+#if 0
+#define DEBUGLOG_ENABLED
+#define DEBUGLOG(x) std::cerr << x << std::endl;
+#else
+#define DEBUGLOG(x)
+#endif
+
+class Connection
+{
+public:
+ Connection(const ComboAddress& peer, QuicheConfig config, QuicheConnection conn) :
+ d_peer(peer), d_conn(std::move(conn)), d_config(std::move(config))
+ {
+ }
+ Connection(const Connection&) = delete;
+ Connection(Connection&&) = default;
+ Connection& operator=(const Connection&) = delete;
+ Connection& operator=(Connection&&) = default;
+ ~Connection() = default;
+
+ ComboAddress d_peer;
+ QuicheConnection d_conn;
+ QuicheConfig d_config;
+
+ std::unordered_map<uint64_t, PacketBuffer> d_streamBuffers;
+ std::unordered_map<uint64_t, PacketBuffer> d_streamOutBuffers;
+};
+
+static void sendBackDOQUnit(DOQUnitUniquePtr&& unit, const char* description);
+
+struct DOQServerConfig
+{
+ DOQServerConfig(QuicheConfig&& config_, uint32_t internalPipeBufferSize) :
+ config(std::move(config_))
+ {
+ {
+ auto [sender, receiver] = pdns::channel::createObjectQueue<DOQUnit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, internalPipeBufferSize);
+ d_responseSender = std::move(sender);
+ d_responseReceiver = std::move(receiver);
+ }
+ }
+ DOQServerConfig(const DOQServerConfig&) = delete;
+ DOQServerConfig(DOQServerConfig&&) = default;
+ DOQServerConfig& operator=(const DOQServerConfig&) = delete;
+ DOQServerConfig& operator=(DOQServerConfig&&) = default;
+ ~DOQServerConfig() = default;
+
+ using ConnectionsMap = std::map<PacketBuffer, Connection>;
+
+ LocalHolders holders;
+ ConnectionsMap d_connections;
+ QuicheConfig config;
+ ClientState* clientState{nullptr};
+ std::shared_ptr<DOQFrontend> df{nullptr};
+ pdns::channel::Sender<DOQUnit> d_responseSender;
+ pdns::channel::Receiver<DOQUnit> d_responseReceiver;
+};
+
+/* these might seem useless, but they are needed because
+ they need to be declared _after_ the definition of DOQServerConfig
+ so that we can use a unique_ptr in DOQFrontend */
+DOQFrontend::DOQFrontend() = default;
+DOQFrontend::~DOQFrontend() = default;
+
+class DOQTCPCrossQuerySender final : public TCPQuerySender
+{
+public:
+ DOQTCPCrossQuerySender() = default;
+
+ [[nodiscard]] bool active() const override
+ {
+ return true;
+ }
+
+ void handleResponse([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
+ {
+ if (!response.d_idstate.doqu) {
+ return;
+ }
+
+ auto unit = std::move(response.d_idstate.doqu);
+ if (unit->dsc == nullptr) {
+ return;
+ }
+
+ unit->response = std::move(response.d_buffer);
+ unit->ids = std::move(response.d_idstate);
+ DNSResponse dnsResponse(unit->ids, unit->response, unit->downstream);
+
+ dnsheader cleartextDH{};
+ memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
+
+ if (!response.isAsync()) {
+
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+ static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+
+ dnsResponse.ids.doqu = std::move(unit);
+
+ if (!processResponse(dnsResponse.ids.doqu->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dnsResponse, false)) {
+ if (dnsResponse.ids.doqu) {
+
+ sendBackDOQUnit(std::move(dnsResponse.ids.doqu), "Response dropped by rules");
+ }
+ return;
+ }
+
+ if (dnsResponse.isAsynchronous()) {
+ return;
+ }
+
+ unit = std::move(dnsResponse.ids.doqu);
+ }
+
+ if (!unit->ids.selfGenerated) {
+ double udiff = unit->ids.queryRealTime.udiff();
+ vinfolog("Got answer from %s, relayed to %s (quic, %d bytes), took %f us", unit->downstream->d_config.remote.toStringWithPort(), unit->ids.origRemote.toStringWithPort(), unit->response.size(), udiff);
+
+ auto backendProtocol = unit->downstream->getProtocol();
+ if (backendProtocol == dnsdist::Protocol::DoUDP && unit->tcp) {
+ backendProtocol = dnsdist::Protocol::DoTCP;
+ }
+ handleResponseSent(unit->ids, udiff, unit->ids.origRemote, unit->downstream->d_config.remote, unit->response.size(), cleartextDH, backendProtocol, true);
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ if (unit->ids.cs != nullptr) {
+ ++unit->ids.cs->responses;
+ }
+
+ sendBackDOQUnit(std::move(unit), "Cross-protocol response");
+ }
+
+ void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+ {
+ return handleResponse(now, std::move(response));
+ }
+
+ void notifyIOError([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
+ {
+ if (!response.d_idstate.doqu) {
+ return;
+ }
+
+ auto unit = std::move(response.d_idstate.doqu);
+ if (unit->dsc == nullptr) {
+ return;
+ }
+
+ /* this will signal an error */
+ unit->response.clear();
+ unit->ids = std::move(response.d_idstate);
+ sendBackDOQUnit(std::move(unit), "Cross-protocol error");
+ }
+};
+
+class DOQCrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+ DOQCrossProtocolQuery(DOQUnitUniquePtr&& unit, bool isResponse)
+ {
+ if (isResponse) {
+ /* happens when a response becomes async */
+ query = InternalQuery(std::move(unit->response), std::move(unit->ids));
+ }
+ else {
+ /* we need to duplicate the query here because we might need
+ the existing query later if we get a truncated answer */
+ query = InternalQuery(PacketBuffer(unit->query), std::move(unit->ids));
+ }
+
+ /* it might have been moved when we moved unit->ids */
+ if (unit) {
+ query.d_idstate.doqu = std::move(unit);
+ }
+
+ /* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload,
+ clearing query.d_proxyProtocolPayloadAdded and unit->proxyProtocolPayloadSize.
+ Leave it for now because we know that the onky case where the payload has been
+ added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT,
+ and we know the TCP/DoT code can handle it. */
+ query.d_proxyProtocolPayloadAdded = query.d_idstate.doqu->proxyProtocolPayloadSize > 0;
+ downstream = query.d_idstate.doqu->downstream;
+ }
+
+ void handleInternalError()
+ {
+ sendBackDOQUnit(std::move(query.d_idstate.doqu), "DOQ internal error");
+ }
+
+ std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+ {
+ query.d_idstate.doqu->downstream = downstream;
+ return s_sender;
+ }
+
+ DNSQuestion getDQ() override
+ {
+ auto& ids = query.d_idstate;
+ DNSQuestion dnsQuestion(ids, query.d_buffer);
+ return dnsQuestion;
+ }
+
+ DNSResponse getDR() override
+ {
+ auto& ids = query.d_idstate;
+ DNSResponse dnsResponse(ids, query.d_buffer, downstream);
+ return dnsResponse;
+ }
+
+ DOQUnitUniquePtr&& releaseDU()
+ {
+ return std::move(query.d_idstate.doqu);
+ }
+
+private:
+ static std::shared_ptr<DOQTCPCrossQuerySender> s_sender;
+};
+
+std::shared_ptr<DOQTCPCrossQuerySender> DOQCrossProtocolQuery::s_sender = std::make_shared<DOQTCPCrossQuerySender>();
+
+static bool tryWriteResponse(Connection& conn, const uint64_t streamID, PacketBuffer& response)
+{
+ size_t pos = 0;
+ while (pos < response.size()) {
+ auto res = quiche_conn_stream_send(conn.d_conn.get(), streamID, &response.at(pos), response.size() - pos, true);
+ if (res == QUICHE_ERR_DONE) {
+ response.erase(response.begin(), response.begin() + static_cast<ssize_t>(pos));
+ return false;
+ }
+ if (res < 0) {
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_INTERNAL_ERROR));
+ return true;
+ }
+ pos += res;
+ }
+
+ return true;
+}
+
+static void handleResponse(DOQFrontend& frontend, Connection& conn, const uint64_t streamID, PacketBuffer& response)
+{
+ if (response.empty()) {
+ ++frontend.d_errorResponses;
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_UNSPECIFIED_ERROR));
+ return;
+ }
+ ++frontend.d_validResponses;
+ auto responseSize = static_cast<uint16_t>(response.size());
+ const std::array<uint8_t, 2> sizeBytes = {static_cast<uint8_t>(responseSize / 256), static_cast<uint8_t>(responseSize % 256)};
+ response.insert(response.begin(), sizeBytes.begin(), sizeBytes.end());
+ if (!tryWriteResponse(conn, streamID, response)) {
+ conn.d_streamOutBuffers[streamID] = std::move(response);
+ }
+}
+
+void DOQFrontend::setup()
+{
+ auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+ d_quicheParams.d_alpn = std::string(DOQ_ALPN.begin(), DOQ_ALPN.end());
+ configureQuiche(config, d_quicheParams, false);
+ d_server_config = std::make_unique<DOQServerConfig>(std::move(config), d_internalPipeBufferSize);
+}
+
+void DOQFrontend::reloadCertificates()
+{
+ auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+ d_quicheParams.d_alpn = std::string(DOQ_ALPN.begin(), DOQ_ALPN.end());
+ configureQuiche(config, d_quicheParams, false);
+ std::atomic_store_explicit(&d_server_config->config, std::move(config), std::memory_order_release);
+}
+
+static std::optional<std::reference_wrapper<Connection>> getConnection(DOQServerConfig::ConnectionsMap& connMap, const PacketBuffer& connID)
+{
+ auto iter = connMap.find(connID);
+ if (iter == connMap.end()) {
+ return std::nullopt;
+ }
+ return iter->second;
+}
+
+static void sendBackDOQUnit(DOQUnitUniquePtr&& unit, const char* description)
+{
+ if (unit->dsc == nullptr) {
+ return;
+ }
+ try {
+ if (!unit->dsc->d_responseSender.send(std::move(unit))) {
+ ++dnsdist::metrics::g_stats.doqResponsePipeFull;
+ vinfolog("Unable to pass a %s to the DoQ worker thread because the pipe is full", description);
+ }
+ }
+ catch (const std::exception& e) {
+ vinfolog("Unable to pass a %s to the DoQ worker thread because we couldn't write to the pipe: %s", description, e.what());
+ }
+}
+
+static std::optional<std::reference_wrapper<Connection>> createConnection(DOQServerConfig& config, const PacketBuffer& serverSideID, const PacketBuffer& originalDestinationID, const ComboAddress& local, const ComboAddress& peer)
+{
+ auto quicheConfig = std::atomic_load_explicit(&config.config, std::memory_order_acquire);
+ auto quicheConn = QuicheConnection(quiche_accept(serverSideID.data(), serverSideID.size(),
+ originalDestinationID.data(), originalDestinationID.size(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const struct sockaddr*>(&local),
+ local.getSocklen(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<const struct sockaddr*>(&peer),
+ peer.getSocklen(),
+ quicheConfig.get()),
+ quiche_conn_free);
+
+ if (config.df && !config.df->d_quicheParams.d_keyLogFile.empty()) {
+ quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_quicheParams.d_keyLogFile.c_str());
+ }
+
+ auto conn = Connection(peer, std::move(quicheConfig), std::move(quicheConn));
+ auto pair = config.d_connections.emplace(serverSideID, std::move(conn));
+ return pair.first->second;
+}
+
+std::unique_ptr<CrossProtocolQuery> getDOQCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
+{
+ if (!dnsQuestion.ids.doqu) {
+ throw std::runtime_error("Trying to create a DoQ cross protocol query without a valid DoQ unit");
+ }
+
+ auto unit = std::move(dnsQuestion.ids.doqu);
+ if (&dnsQuestion.ids != &unit->ids) {
+ unit->ids = std::move(dnsQuestion.ids);
+ }
+
+ unit->ids.origID = dnsQuestion.getHeader()->id;
+
+ if (!isResponse) {
+ if (unit->query.data() != dnsQuestion.getMutableData().data()) {
+ unit->query = std::move(dnsQuestion.getMutableData());
+ }
+ }
+ else {
+ if (unit->response.data() != dnsQuestion.getMutableData().data()) {
+ unit->response = std::move(dnsQuestion.getMutableData());
+ }
+ }
+
+ return std::make_unique<DOQCrossProtocolQuery>(std::move(unit), isResponse);
+}
+
+static void processDOQQuery(DOQUnitUniquePtr&& doqUnit)
+{
+ const auto handleImmediateResponse = [](DOQUnitUniquePtr&& unit, [[maybe_unused]] const char* reason) {
+ DEBUGLOG("handleImmediateResponse() reason=" << reason);
+ auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
+ handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->response);
+ unit->ids.doqu.reset();
+ };
+
+ auto& ids = doqUnit->ids;
+ ids.doqu = std::move(doqUnit);
+ auto& unit = ids.doqu;
+ uint16_t queryId = 0;
+ ComboAddress remote;
+
+ try {
+
+ remote = unit->ids.origRemote;
+ DOQServerConfig* dsc = unit->dsc;
+ auto& holders = dsc->holders;
+ ClientState& clientState = *dsc->clientState;
+
+ if (!holders.acl->match(remote)) {
+ vinfolog("Query from %s (DoQ) dropped because of ACL", remote.toStringWithPort());
+ ++dnsdist::metrics::g_stats.aclDrops;
+ unit->response.clear();
+
+ handleImmediateResponse(std::move(unit), "DoQ query dropped because of ACL");
+ return;
+ }
+
+ if (unit->query.size() < sizeof(dnsheader)) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ unit->response.clear();
+
+ handleImmediateResponse(std::move(unit), "DoQ non-compliant query");
+ return;
+ }
+
+ ++clientState.queries;
+ ++dnsdist::metrics::g_stats.queries;
+ unit->ids.queryRealTime.start();
+
+ {
+ /* don't keep that pointer around, it will be invalidated if the buffer is ever resized */
+ dnsheader_aligned dnsHeader(unit->query.data());
+
+ if (!checkQueryHeaders(*dnsHeader, clientState)) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+ header.rcode = RCode::ServFail;
+ header.qr = true;
+ return true;
+ });
+ unit->response = std::move(unit->query);
+
+ handleImmediateResponse(std::move(unit), "DoQ invalid headers");
+ return;
+ }
+
+ if (dnsHeader->qdcount == 0) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+ header.rcode = RCode::NotImp;
+ header.qr = true;
+ return true;
+ });
+ unit->response = std::move(unit->query);
+
+ handleImmediateResponse(std::move(unit), "DoQ empty query");
+ return;
+ }
+
+ queryId = ntohs(dnsHeader->id);
+ }
+
+ auto downstream = unit->downstream;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ unit->ids.qname = DNSName(reinterpret_cast<const char*>(unit->query.data()), static_cast<int>(unit->query.size()), sizeof(dnsheader), false, &unit->ids.qtype, &unit->ids.qclass);
+ DNSQuestion dnsQuestion(unit->ids, unit->query);
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&ids](dnsheader& header) {
+ const uint16_t* flags = getFlagsFromDNSHeader(&header);
+ ids.origFlags = *flags;
+ return true;
+ });
+ unit->ids.cs = &clientState;
+
+ auto result = processQuery(dnsQuestion, holders, downstream);
+ if (result == ProcessQueryResult::Drop) {
+ handleImmediateResponse(std::move(unit), "DoQ dropped query");
+ return;
+ }
+ if (result == ProcessQueryResult::Asynchronous) {
+ return;
+ }
+ if (result == ProcessQueryResult::SendAnswer) {
+ if (unit->response.empty()) {
+ unit->response = std::move(unit->query);
+ }
+ if (unit->response.size() >= sizeof(dnsheader)) {
+ const dnsheader_aligned dnsHeader(unit->response.data());
+
+ handleResponseSent(unit->ids.qname, QType(unit->ids.qtype), 0., unit->ids.origDest, ComboAddress(), unit->response.size(), *dnsHeader, dnsdist::Protocol::DoQ, dnsdist::Protocol::DoQ, false);
+ }
+ handleImmediateResponse(std::move(unit), "DoQ self-answered response");
+ return;
+ }
+
+ ++dnsdist::metrics::g_stats.responses;
+ if (unit->ids.cs != nullptr) {
+ ++unit->ids.cs->responses;
+ }
+
+ if (result != ProcessQueryResult::PassToBackend) {
+ handleImmediateResponse(std::move(unit), "DoQ no backend available");
+ return;
+ }
+
+ if (downstream == nullptr) {
+ handleImmediateResponse(std::move(unit), "DoQ no backend available");
+ return;
+ }
+
+ unit->downstream = downstream;
+
+ std::string proxyProtocolPayload;
+ /* we need to do this _before_ creating the cross protocol query because
+ after that the buffer will have been moved */
+ if (downstream->d_config.useProxyProtocol) {
+ proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+ }
+
+ unit->ids.origID = htons(queryId);
+ unit->tcp = true;
+
+ /* this moves unit->ids, careful! */
+ auto cpq = std::make_unique<DOQCrossProtocolQuery>(std::move(unit), false);
+ cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+ if (downstream->passCrossProtocolQuery(std::move(cpq))) {
+ return;
+ }
+ // NOLINTNEXTLINE(bugprone-use-after-move): it was only moved if the call succeeded
+ unit = cpq->releaseDU();
+ handleImmediateResponse(std::move(unit), "DoQ internal error");
+ return;
+ }
+ catch (const std::exception& e) {
+ vinfolog("Got an error in DOQ question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
+ handleImmediateResponse(std::move(unit), "DoQ internal error");
+ return;
+ }
+}
+
+static void doq_dispatch_query(DOQServerConfig& dsc, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, const PacketBuffer& serverConnID, const uint64_t streamID)
+{
+ try {
+ auto unit = std::make_unique<DOQUnit>(std::move(query));
+ unit->dsc = &dsc;
+ unit->ids.origDest = local;
+ unit->ids.origRemote = remote;
+ unit->ids.protocol = dnsdist::Protocol::DoQ;
+ unit->serverConnID = serverConnID;
+ unit->streamID = streamID;
+
+ processDOQQuery(std::move(unit));
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Had error handling DoQ DNS packet from %s: %s", remote.toStringWithPort(), exp.what());
+ }
+}
+
+static void flushResponses(pdns::channel::Receiver<DOQUnit>& receiver)
+{
+ for (;;) {
+ try {
+ auto tmp = receiver.receive();
+ if (!tmp) {
+ return;
+ }
+
+ auto unit = std::move(*tmp);
+ auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
+ if (conn) {
+ handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->response);
+ }
+ }
+ catch (const std::exception& e) {
+ errlog("Error while processing response received over DoQ: %s", e.what());
+ }
+ catch (...) {
+ errlog("Unspecified error while processing response received over DoQ");
+ }
+ }
+}
+
+static void flushStalledResponses(Connection& conn)
+{
+ for (auto streamIt = conn.d_streamOutBuffers.begin(); streamIt != conn.d_streamOutBuffers.end();) {
+ const auto& streamID = streamIt->first;
+ auto& response = streamIt->second;
+ if (quiche_conn_stream_writable(conn.d_conn.get(), streamID, response.size()) == 1) {
+ if (tryWriteResponse(conn, streamID, response)) {
+ streamIt = conn.d_streamOutBuffers.erase(streamIt);
+ continue;
+ }
+ }
+ ++streamIt;
+ }
+}
+
+static void handleReadableStream(DOQFrontend& frontend, ClientState& clientState, Connection& conn, uint64_t streamID, const ComboAddress& client, const PacketBuffer& serverConnID)
+{
+ auto& streamBuffer = conn.d_streamBuffers[streamID];
+ while (true) {
+ bool fin = false;
+ auto existingLength = streamBuffer.size();
+ streamBuffer.resize(existingLength + 512);
+ auto received = quiche_conn_stream_recv(conn.d_conn.get(), streamID,
+ &streamBuffer.at(existingLength), 512,
+ &fin);
+ if (received == 0 || received == QUICHE_ERR_DONE) {
+ streamBuffer.resize(existingLength);
+ return;
+ }
+ if (received < 0) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR));
+ return;
+ }
+
+ streamBuffer.resize(existingLength + received);
+ if (fin) {
+ break;
+ }
+ }
+
+ if (streamBuffer.size() < (sizeof(uint16_t) + sizeof(dnsheader))) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR));
+ return;
+ }
+
+ uint16_t payloadLength = streamBuffer.at(0) * 256 + streamBuffer.at(1);
+ streamBuffer.erase(streamBuffer.begin(), streamBuffer.begin() + 2);
+ if (payloadLength != streamBuffer.size()) {
+ ++dnsdist::metrics::g_stats.nonCompliantQueries;
+ ++clientState.nonCompliantQueries;
+ quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR));
+ return;
+ }
+ DEBUGLOG("Dispatching query");
+ doq_dispatch_query(*(frontend.d_server_config), std::move(streamBuffer), clientState.local, client, serverConnID, streamID);
+ conn.d_streamBuffers.erase(streamID);
+}
+
+static void handleSocketReadable(DOQFrontend& frontend, ClientState& clientState, Socket& sock, PacketBuffer& buffer)
+{
+ // destination connection ID, will have to be sent as original destination connection ID
+ PacketBuffer serverConnID;
+ // source connection ID, will have to be sent as destination connection ID
+ PacketBuffer clientConnID;
+ PacketBuffer tokenBuf;
+ while (true) {
+ ComboAddress client;
+ buffer.resize(4096);
+ if (!sock.recvFromAsync(buffer, client) || buffer.empty()) {
+ return;
+ }
+ DEBUGLOG("Received DoQ datagram of size " << buffer.size() << " from " << client.toStringWithPort());
+
+ uint32_t version{0};
+ uint8_t type{0};
+ std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> scid{};
+ size_t scid_len = scid.size();
+ std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> dcid{};
+ size_t dcid_len = dcid.size();
+ std::array<uint8_t, MAX_TOKEN_LEN> token{};
+ size_t token_len = token.size();
+
+ auto res = quiche_header_info(buffer.data(), buffer.size(), LOCAL_CONN_ID_LEN,
+ &version, &type,
+ scid.data(), &scid_len,
+ dcid.data(), &dcid_len,
+ token.data(), &token_len);
+ if (res != 0) {
+ DEBUGLOG("Error in quiche_header_info: " << res);
+ continue;
+ }
+
+ serverConnID.assign(dcid.begin(), dcid.begin() + dcid_len);
+ clientConnID.assign(scid.begin(), scid.begin() + scid_len);
+ auto conn = getConnection(frontend.d_server_config->d_connections, serverConnID);
+
+ if (!conn) {
+ DEBUGLOG("Connection not found");
+ if (type != static_cast<uint8_t>(DOQ_Packet_Types::QUIC_PACKET_TYPE_INITIAL)) {
+ DEBUGLOG("Packet is not initial");
+ continue;
+ }
+
+ if (!quiche_version_is_supported(version)) {
+ DEBUGLOG("Unsupported version");
+ ++frontend.d_doqUnsupportedVersionErrors;
+ handleVersionNegociation(sock, clientConnID, serverConnID, client, buffer);
+ continue;
+ }
+
+ if (token_len == 0) {
+ /* stateless retry */
+ DEBUGLOG("No token received");
+ handleStatelessRetry(sock, clientConnID, serverConnID, client, version, buffer);
+ continue;
+ }
+
+ tokenBuf.assign(token.begin(), token.begin() + token_len);
+ auto originalDestinationID = validateToken(tokenBuf, client);
+ if (!originalDestinationID) {
+ ++frontend.d_doqInvalidTokensReceived;
+ DEBUGLOG("Discarding invalid token");
+ continue;
+ }
+
+ DEBUGLOG("Creating a new connection");
+ conn = createConnection(*frontend.d_server_config, serverConnID, *originalDestinationID, clientState.local, client);
+ if (!conn) {
+ continue;
+ }
+ }
+ DEBUGLOG("Connection found");
+ quiche_recv_info recv_info = {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<struct sockaddr*>(&client),
+ client.getSocklen(),
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ reinterpret_cast<struct sockaddr*>(&clientState.local),
+ clientState.local.getSocklen(),
+ };
+
+ auto done = quiche_conn_recv(conn->get().d_conn.get(), buffer.data(), buffer.size(), &recv_info);
+ if (done < 0) {
+ continue;
+ }
+
+ if (quiche_conn_is_established(conn->get().d_conn.get()) || quiche_conn_is_in_early_data(conn->get().d_conn.get())) {
+ auto readable = std::unique_ptr<quiche_stream_iter, decltype(&quiche_stream_iter_free)>(quiche_conn_readable(conn->get().d_conn.get()), quiche_stream_iter_free);
+
+ uint64_t streamID = 0;
+ while (quiche_stream_iter_next(readable.get(), &streamID)) {
+ handleReadableStream(frontend, clientState, *conn, streamID, client, serverConnID);
+ }
+
+ flushEgress(sock, conn->get().d_conn, client, buffer);
+ }
+ else {
+ DEBUGLOG("Connection not established");
+ }
+ }
+}
+
+// this is the entrypoint from dnsdist.cc
+void doqThread(ClientState* clientState)
+{
+ try {
+ std::shared_ptr<DOQFrontend>& frontend = clientState->doqFrontend;
+
+ frontend->d_server_config->clientState = clientState;
+ frontend->d_server_config->df = clientState->doqFrontend;
+
+ setThreadName("dnsdist/doq");
+
+ Socket sock(clientState->udpFD);
+ sock.setNonBlocking();
+
+ auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+
+ auto responseReceiverFD = frontend->d_server_config->d_responseReceiver.getDescriptor();
+ mplexer->addReadFD(sock.getHandle(), [](int, FDMultiplexer::funcparam_t&) {});
+ mplexer->addReadFD(responseReceiverFD, [](int, FDMultiplexer::funcparam_t&) {});
+ std::vector<int> readyFDs;
+ PacketBuffer buffer(4096);
+ while (true) {
+ readyFDs.clear();
+ mplexer->getAvailableFDs(readyFDs, 500);
+
+ try {
+ if (std::find(readyFDs.begin(), readyFDs.end(), sock.getHandle()) != readyFDs.end()) {
+ handleSocketReadable(*frontend, *clientState, sock, buffer);
+ }
+
+ if (std::find(readyFDs.begin(), readyFDs.end(), responseReceiverFD) != readyFDs.end()) {
+ flushResponses(frontend->d_server_config->d_responseReceiver);
+ }
+
+ for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
+ quiche_conn_on_timeout(conn->second.d_conn.get());
+
+ flushEgress(sock, conn->second.d_conn, conn->second.d_peer, buffer);
+
+ if (quiche_conn_is_closed(conn->second.d_conn.get())) {
+#ifdef DEBUGLOG_ENABLED
+ quiche_stats stats;
+ quiche_path_stats path_stats;
+
+ quiche_conn_stats(conn->second.d_conn.get(), &stats);
+ quiche_conn_path_stats(conn->second.d_conn.get(), 0, &path_stats);
+
+ DEBUGLOG("Connection (DoQ) closed, recv=" << stats.recv << " sent=" << stats.sent << " lost=" << stats.lost << " rtt=" << path_stats.rtt << "ns cwnd=" << path_stats.cwnd);
+#endif
+ conn = frontend->d_server_config->d_connections.erase(conn);
+ }
+ else {
+ flushStalledResponses(conn->second);
+ ++conn;
+ }
+ }
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Caught exception in the main DoQ thread: %s", exp.what());
+ }
+ catch (...) {
+ vinfolog("Unknown exception in the main DoQ thread");
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ DEBUGLOG("Caught fatal error in the main DoQ thread: " << e.what());
+ }
+}
+
+#endif /* HAVE_DNS_OVER_QUIC */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <memory>
+
+#include "config.h"
+#include "channel.hh"
+#include "iputils.hh"
+#include "libssl.hh"
+#include "noinitvector.hh"
+#include "doq.hh"
+#include "stat_t.hh"
+#include "dnsdist-idstate.hh"
+
+struct DOQServerConfig;
+struct DownstreamState;
+
+#ifdef HAVE_DNS_OVER_QUIC
+
+#include "doq-common.hh"
+
+struct DOQFrontend
+{
+ DOQFrontend();
+ DOQFrontend(const DOQFrontend&) = delete;
+ DOQFrontend(DOQFrontend&&) = delete;
+ DOQFrontend& operator=(const DOQFrontend&) = delete;
+ DOQFrontend& operator=(DOQFrontend&&) = delete;
+ ~DOQFrontend();
+
+ void setup();
+ void reloadCertificates();
+
+ std::unique_ptr<DOQServerConfig> d_server_config;
+ dnsdist::doq::QuicheParams d_quicheParams;
+ ComboAddress d_local;
+
+#ifdef __linux__
+ // On Linux this gives us 128k pending queries (default is 8192 queries),
+ // which should be enough to deal with huge spikes
+ uint32_t d_internalPipeBufferSize{1024 * 1024};
+#else
+ uint32_t d_internalPipeBufferSize{0};
+#endif
+
+ pdns::stat_t d_doqUnsupportedVersionErrors{0}; // Unsupported protocol version errors
+ pdns::stat_t d_doqInvalidTokensReceived{0}; // Discarded received tokens
+ pdns::stat_t d_validResponses{0}; // Valid responses sent
+ pdns::stat_t d_errorResponses{0}; // Empty responses (no backend, drops, invalid queries, etc.)
+};
+
+struct DOQUnit
+{
+ DOQUnit(PacketBuffer&& query_) :
+ query(std::move(query_))
+ {
+ }
+
+ DOQUnit(const DOQUnit&) = delete;
+ DOQUnit& operator=(const DOQUnit&) = delete;
+
+ InternalQueryState ids;
+ PacketBuffer query;
+ PacketBuffer response;
+ PacketBuffer serverConnID;
+ std::shared_ptr<DownstreamState> downstream{nullptr};
+ DOQServerConfig* dsc{nullptr};
+ uint64_t streamID{0};
+ size_t proxyProtocolPayloadSize{0};
+ /* whether the query was re-sent to the backend over
+ TCP after receiving a truncated answer over UDP */
+ bool tcp{false};
+};
+
+using DOQUnitUniquePtr = std::unique_ptr<DOQUnit>;
+
+struct CrossProtocolQuery;
+struct DNSQuestion;
+std::unique_ptr<CrossProtocolQuery> getDOQCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse);
+
+void doqThread(ClientState* clientState);
+
+#else
+
+struct DOQUnit
+{
+};
+
+struct DOQFrontend
+{
+ DOQFrontend()
+ {
+ }
+ void setup()
+ {
+ }
+};
+
+#endif
--- /dev/null
+../ednsextendederror.cc
\ No newline at end of file
--- /dev/null
+../ednsextendederror.hh
\ No newline at end of file
--- /dev/null
+*.la
+*.lo
+*.o
+Makefile
+Makefile.in
--- /dev/null
+../../../../ext/arc4random/Makefile.am
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/arc4random.c
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/arc4random.h
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/arc4random.hh
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/arc4random_uniform.c
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/bsd-getentropy.c
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/chacha_private.h
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/explicit_bzero.c
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/includes.h
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/log.h
\ No newline at end of file
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
if (size > std::numeric_limits<uint16_t>::max()) {
return 0;
uint16_t qtype;
uint16_t qclass;
unsigned int consumed;
- PacketBuffer vect(data, data+size);
+ PacketBuffer vect(data, data + size);
const DNSName qname(reinterpret_cast<const char*>(data), size, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
pcSkipCookies.getKey(qname.getStorage(), consumed, vect, false);
pcHashCookies.getKey(qname.getStorage(), consumed, vect, false);
boost::optional<Netmask> subnet;
DNSDistPacketCache::getClientSubnet(vect, consumed, subnet);
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
}
- catch(const PDNSException& e) {
+ catch (const PDNSException& e) {
}
return 0;
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "xsk.hh"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+#ifdef HAVE_XSK
+ if (size > XskSocket::getFrameSize()) {
+ return 0;
+ }
+
+ try {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): packet data is usually mutable
+ XskPacket packet(const_cast<uint8_t*>(data), size, size);
+ if (packet.parse(false)) {
+ const auto& dest = packet.getToAddr();
+ const auto& orig = packet.getFromAddr();
+ const auto* payload = packet.getPayloadData();
+ auto capacity = packet.getCapacity();
+ auto length = packet.getDataLen();
+ auto frameLen = packet.getFrameLen();
+ auto header = packet.cloneHeaderToPacketBuffer();
+ auto buffer = packet.clonePacketBuffer();
+ (void)dest;
+ (void)orig;
+ (void)payload;
+ (void)capacity;
+ (void)length;
+ (void)frameLen;
+ }
+ }
+ catch (const std::exception& e) {
+ }
+#endif /* HAVE_XSK */
+ return 0;
+}
</tr></table>
<p>
Uptime: <span id="uptime"></span>, Number of queries: <span id="questions"></span> (<span id="qps"></span> qps), ACL drops: <span id="acl-drops"></span>, Dynamic drops: <span id="dyn-drops"></span>, Rule drops: <span id="rule-drops"></span><br/>
- Average response time: UDP <span id="latency"></span> ms, TCP <span id="latency-tcp"></span> ms, DoT <span id="latency-dot"></span> ms, DoH <span id="latency-doh"></span> ms <br/>
+ Average response time: UDP <span id="latency"></span> ms, TCP <span id="latency-tcp"></span> ms, DoT <span id="latency-dot"></span> ms, DoH <span id="latency-doh"></span> ms, DoQ <span id="latency-doq"></span> ms <br/>
CPU Usage: <span id="cpu"></span>%, Cache hitrate: <span id="phitrate"></span>%, Server selection policy: <span id="server-policy"></span><br/>
Listening on: <span id="local"></span>, ACL: <span id="acl"></span>
</p>
$("#latency-tcp").text((data["latency-tcp-avg10000"]/1000.0).toFixed(2));
$("#latency-dot").text((data["latency-dot-avg10000"]/1000.0).toFixed(2));
$("#latency-doh").text((data["latency-doh-avg10000"]/1000.0).toFixed(2));
+ $("#latency-doq").text((data["latency-doq-avg10000"]/1000.0).toFixed(2));
if(!gdata["cpu-sys-msec"])
gdata=data;
bouw = bouw + "</table>";
$("#downstreams").html(bouw);
- bouw='<table width="100%"><tr align=left><th>#</th><th align=left>Rule</th><th>Action</th><th>Matches</th></tr>';
+ bouw='<table width="100%"><tr align=left><th>#</th><th align=left>Name</th><th align=left>Rule</th><th>Action</th><th>Matches</th></tr>';
if(data["rules"].length) {
$.each(data["rules"], function(a,b) {
- bouw = bouw + ("<tr align=left><td>"+b["id"]+"</td><td align=left>"+b["rule"]+"</td><td>"+b["action"]+"</td>");
+ bouw = bouw + ("<tr align=left><td>"+b["id"]+"</td><td align=left>"+b["name"]+"</td><td align=left>"+b["rule"]+"</td><td>"+b["action"]+"</td>");
bouw = bouw + ("<td>"+b["matches"]+"</td></tr>");
});
}
bouw = bouw + "</table>";
$("#rules").html(bouw);
- bouw='<table width="100%"><tr align=left><th>#</th><th align=left>Response Rule</th><th>Action</th><th>Matches</th></tr>';
+ bouw='<table width="100%"><tr align=left><th>#</th><th align=left>Name</th><th align=left>Response Rule</th><th>Action</th><th>Matches</th></tr>';
if(data["response-rules"].length) {
$.each(data["response-rules"], function(a,b) {
- bouw = bouw + ("<tr align=left><td>"+b["id"]+"</td><td align=left>"+b["rule"]+"</td><td>"+b["action"]+"</td>");
+ bouw = bouw + ("<tr align=left><td>"+b["id"]+"</td><td align=left>"+b["name"]+"</td><td align=left>"+b["rule"]+"</td><td>"+b["action"]+"</td>");
bouw = bouw + ("<td>"+b["matches"]+"</td></tr>");
});
}
$.ajax({ url: 'jsonstat?command=dynblocklist', type: 'GET', dataType: 'json', jsonp: false,
success: function(data) {
- var bouw='<table width="100%"><tr align=left><th>Dyn blocked netmask</th><th>Seconds</th><th>Blocks</th><th align=left>Reason</th></tr>';
+ var bouw='<table width="100%"><tr align=left><th>Dyn blocked netmask</th><th>Seconds</th><th>Blocks</th><th>eBPF</th><th align=left>Reason</th></tr>';
var gotsome=false;
$.each(data, function(a,b) {
- bouw=bouw+("<tr><td>"+a+"</td><td>"+b.seconds+"</td><td>"+b.blocks+"</td><td>"+b.reason+"</td></tr>");
+ bouw=bouw+("<tr><td>"+a+"</td><td>"+b.seconds+"</td><td>"+b.blocks+"</td><td>"+b.ebpf+"</td><td>"+b.reason+"</td></tr>");
gotsome=true;
});
echo "};"
done
-echo "static const map<string,string> s_urlmap={"
+echo "static const map<string,string,std::less<>> s_urlmap={"
for a in $(find ${DIR}html -type f | grep -v \~ | sort)
do
b=$(echo $a | sed s:${DIR}html/::g)
AC_DEFUN([DNSDIST_ENABLE_DNS_OVER_HTTPS], [
AC_MSG_CHECKING([whether to enable incoming DNS over HTTPS (DoH) support])
AC_ARG_ENABLE([dns-over-https],
- AS_HELP_STRING([--enable-dns-over-https], [enable incoming DNS over HTTPS (DoH) support (requires libh2o) @<:@default=no@:>@]),
+ AS_HELP_STRING([--enable-dns-over-https], [enable incoming DNS over HTTPS (DoH) support (requires libh2o or nghttp2) @<:@default=no@:>@]),
[enable_dns_over_https=$enableval],
[enable_dns_over_https=no]
)
--- /dev/null
+AC_DEFUN([DNSDIST_ENABLE_DNS_OVER_HTTP3], [
+ AC_MSG_CHECKING([whether to enable incoming DNS over HTTP3 (DoH3) support])
+ AC_ARG_ENABLE([dns-over-http3],
+ AS_HELP_STRING([--enable-dns-over-http3], [enable incoming DNS over HTTP3 (DoH3) support (requires quiche) @<:@default=no@:>@]),
+ [enable_dns_over_http3=$enableval],
+ [enable_dns_over_http3=no]
+ )
+ AC_MSG_RESULT([$enable_dns_over_http3])
+ AM_CONDITIONAL([HAVE_DNS_OVER_HTTP3], [test "x$enable_dns_over_http3" != "xno"])
+
+ AM_COND_IF([HAVE_DNS_OVER_HTTP3], [
+ AC_DEFINE([HAVE_DNS_OVER_HTTP3], [1], [Define to 1 if you enable DNS over HTTP/3 support])
+ ])
+])
--- /dev/null
+AC_DEFUN([DNSDIST_ENABLE_DNS_OVER_QUIC], [
+ AC_MSG_CHECKING([whether to enable incoming DNS over QUIC (DoQ) support])
+ AC_ARG_ENABLE([dns-over-quic],
+ AS_HELP_STRING([--enable-dns-over-quic], [enable incoming DNS over QUIC (DoQ) support (requires quiche) @<:@default=no@:>@]),
+ [enable_dns_over_quic=$enableval],
+ [enable_dns_over_quic=no]
+ )
+ AC_MSG_RESULT([$enable_dns_over_quic])
+ AM_CONDITIONAL([HAVE_DNS_OVER_QUIC], [test "x$enable_dns_over_quic" != "xno"])
+
+ AM_COND_IF([HAVE_DNS_OVER_QUIC], [
+ AC_DEFINE([HAVE_DNS_OVER_QUIC], [1], [Define to 1 if you enable DNS over QUIC support])
+ ])
+])
-AC_DEFUN([PDNS_CHECK_LIBH2OEVLOOP], [
+AC_DEFUN([PDNS_WITH_LIBH2OEVLOOP], [
+ AC_MSG_CHECKING([whether we will be linking in libh2o-evloop])
HAVE_LIBH2OEVLOOP=0
- PKG_CHECK_MODULES([LIBH2OEVLOOP], [libh2o-evloop], [
- [HAVE_LIBH2OEVLOOP=1]
- AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you have libh2o-evloop])
- save_CFLAGS=$CFLAGS
- save_LIBS=$LIBS
- CFLAGS="$LIBH2OEVLOOP_CFLAGS $CFLAGS"
- LIBS="$LIBH2OEVLOOP_LIBS $LIBS"
- AC_CHECK_DECLS([h2o_socket_get_ssl_server_name], [
+ AC_ARG_WITH([h2o],
+ AS_HELP_STRING([--with-h2o],[use libh2o-evloop @<:@default=no@:>@]),
+ [with_h2o=$withval],
+ [with_h2o=no],
+ )
+ AC_MSG_RESULT([$with_h2o])
+
+ AS_IF([test "x$with_h2o" = "xyes" -o "x$with_h2o" = "xauto"], [
+ PKG_CHECK_MODULES([LIBH2OEVLOOP], [libh2o-evloop], [
+ [HAVE_LIBH2OEVLOOP=1]
+ AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you have libh2o-evloop])
+ save_CFLAGS=$CFLAGS
+ save_LIBS=$LIBS
+ CFLAGS="$LIBH2OEVLOOP_CFLAGS $CFLAGS"
+ LIBS="$LIBH2OEVLOOP_LIBS $LIBS"
+ AC_CHECK_DECLS([h2o_socket_get_ssl_server_name], [
AC_DEFINE([HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME], [1], [define to 1 if h2o_socket_get_ssl_server_name is available.])
],
[ : ],
[AC_INCLUDES_DEFAULT
#include <h2o/socket.h>
])
- CFLAGS=$save_CFLAGS
- LIBS=$save_LIBS
- ], [ : ])
+ CFLAGS=$save_CFLAGS
+ LIBS=$save_LIBS
+ ], [ : ])
+ ])
AM_CONDITIONAL([HAVE_LIBH2OEVLOOP], [test "x$LIBH2OEVLOOP_LIBS" != "x"])
+ AM_COND_IF([HAVE_LIBH2OEVLOOP], [
+ AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you enable h2o-evloop support])
+ ])
+
+ AS_IF([test "x$with_h2o" = "xyes"], [
+ AS_IF([test x"LIBH2OEVLOOP_LIBS" = "x"], [
+ AC_MSG_ERROR([h2o-evloop requested but libraries were not found])
+ ])
+ ])
])
--- /dev/null
+../../../m4/pdns_enable_coverage.m4
\ No newline at end of file
--- /dev/null
+../../../m4/pdns_enable_fuzz_targets.m4
\ No newline at end of file
PKG_CHECK_MODULES([NGHTTP2], [libnghttp2], [
[HAVE_NGHTTP2=1]
AC_DEFINE([HAVE_NGHTTP2], [1], [Define to 1 if you have nghttp2])
+ save_CFLAGS=$CFLAGS
+ save_LIBS=$LIBS
+ CFLAGS="$NGHTTP2_CFLAGS $CFLAGS"
+ LIBS="$NGHTTP2_LIBS $LIBS"
+ AC_CHECK_FUNCS([nghttp2_check_header_value_rfc9113 nghttp2_check_method nghttp2_check_path])
+ CFLAGS=$save_CFLAGS
+ LIBS=$save_LIBS
], [ : ])
])
])
--- /dev/null
+AC_DEFUN([PDNS_WITH_QUICHE], [
+ AC_MSG_CHECKING([whether we will be linking in quiche])
+ HAVE_QUICHE=0
+ AC_ARG_WITH([quiche],
+ AS_HELP_STRING([--with-quiche],[use quiche @<:@default=auto@:>@]),
+ [with_quiche=$withval],
+ [with_quiche=auto],
+ )
+ AC_MSG_RESULT([$with_quiche])
+
+ AS_IF([test "x$with_quiche" != "xno"], [
+ AS_IF([test "x$with_quiche" = "xyes" -o "x$with_quiche" = "xauto"], [
+ PKG_CHECK_MODULES([QUICHE], [quiche >= 0.15.0], [
+ [HAVE_QUICHE=1]
+ AC_DEFINE([HAVE_QUICHE], [1], [Define to 1 if you have quiche])
+ ], [ : ])
+ ])
+ ])
+ AM_CONDITIONAL([HAVE_QUICHE], [test "x$QUICHE_LIBS" != "x"])
+ AS_IF([test "x$with_quiche" = "xyes"], [
+ AS_IF([test x"$QUICHE_LIBS" = "x"], [
+ AC_MSG_ERROR([quiche requested but libraries were not found])
+ ])
+ ])
+])
--- /dev/null
+AC_DEFUN([PDNS_WITH_XSK],[
+ AC_MSG_CHECKING([if we have AF_XDP (XSK) support])
+ AC_ARG_WITH([xsk],
+ AS_HELP_STRING([--with-xsk],[enable AF_XDP (XDK) support @<:@default=auto@:>@]),
+ [with_xsk=$withval],
+ [with_xsk=auto],
+ )
+ AC_MSG_RESULT([$with_xsk])
+
+ AS_IF([test "x$with_xsk" != "xno"], [
+ AS_IF([test "x$with_xsk" = "xyes" -o "x$with_xsk" = "xauto"], [
+ PKG_CHECK_MODULES([XDP], [libxdp], [
+ AC_DEFINE([HAVE_XDP], [1], [Define to 1 if you have the XDP library])
+ ], [:])
+ PKG_CHECK_MODULES([BPF], [libbpf], [
+ AC_DEFINE([HAVE_BPF], [1], [Define to 1 if you have the BPF library])
+ save_CFLAGS=$CFLAGS
+ save_LIBS=$LIBS
+ CFLAGS="$BPF_CFLAGS $CFLAGS"
+ LIBS="$BPF_LIBS $LIBS"
+ AC_CHECK_FUNCS([bpf_xdp_query])
+ CFLAGS=$save_CFLAGS
+ LIBS=$save_LIBS
+ ], [:])
+ ])
+ ])
+
+ AM_CONDITIONAL([HAVE_XSK], [test x"$BPF_LIBS" != "x" -a x"$XDP_LIBS" != "x"])
+ AM_COND_IF([HAVE_XSK], [
+ AC_DEFINE([HAVE_XSK], [1], [Define to 1 if you have AF_XDP (XSK) support enabled])
+ ])
+
+ AS_IF([test "x$with_xsk" = "xyes"], [
+ AS_IF([test x"$BPF_LIBS" = "x" -o x"$XDP_LIBS" = "x" ], [
+ AC_MSG_ERROR([AF_XDP (XSK) support requested but required libbpf and/or libxdp were not found])
+ ])
+ ])
+])
+++ /dev/null
-../sodcrypto.cc
\ No newline at end of file
+++ /dev/null
-../sodcrypto.hh
\ No newline at end of file
--- /dev/null
+../standalone_fuzz_target_runner.cc
\ No newline at end of file
if (isWaitingForWrite()) {
d_isWaitingForWrite = false;
- d_mplexer.alterFDToRead(d_fd, callback, callbackData, ttd ? &*ttd : nullptr);
+ d_mplexer.alterFDToRead(d_fd, std::move(callback), callbackData, ttd ? &*ttd : nullptr);
DEBUGLOG(__PRETTY_FUNCTION__<<": alter from write to read FD "<<d_fd);
}
else {
- d_mplexer.addReadFD(d_fd, callback, callbackData, ttd ? &*ttd : nullptr);
+ d_mplexer.addReadFD(d_fd, std::move(callback), callbackData, ttd ? &*ttd : nullptr);
DEBUGLOG(__PRETTY_FUNCTION__<<": add read FD "<<d_fd);
}
if (isWaitingForRead()) {
d_isWaitingForRead = false;
- d_mplexer.alterFDToWrite(d_fd, callback, callbackData, ttd ? &*ttd : nullptr);
+ d_mplexer.alterFDToWrite(d_fd, std::move(callback), callbackData, ttd ? &*ttd : nullptr);
DEBUGLOG(__PRETTY_FUNCTION__<<": alter from read to write FD "<<d_fd);
}
else {
- d_mplexer.addWriteFD(d_fd, callback, callbackData, ttd ? &*ttd : nullptr);
+ d_mplexer.addWriteFD(d_fd, std::move(callback), callbackData, ttd ? &*ttd : nullptr);
DEBUGLOG(__PRETTY_FUNCTION__<<": add write FD "<<d_fd);
}
--- /dev/null
+../test-channel.cc
\ No newline at end of file
-
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
op.close();
BOOST_CHECK_EQUAL(op.readTimeout(&i, 1), 0);
-
-};
+}
std::atomic<int> done = 0;
BOOST_AUTO_TEST_CASE(test_delay_pipe_small) {
sleep(1);
BOOST_CHECK_EQUAL(done, n);
-};
+}
-BOOST_AUTO_TEST_CASE(test_delay_pipe_big) {
+BOOST_AUTO_TEST_CASE(test_delay_pipe_big) {
done=0;
struct Work
{
sleep(1);
BOOST_CHECK_EQUAL(done, n);
-};
-
+}
BOOST_AUTO_TEST_SUITE_END();
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
BOOST_CHECK_EQUAL(record.d_name, target);
BOOST_CHECK_EQUAL(record.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(record.d_ttl, 7200);
+ BOOST_CHECK_EQUAL(record.d_ttl, 7200U);
BOOST_CHECK_EQUAL(record.d_place, 1U);
BOOST_CHECK_GE(record.d_contentOffset, lastOffset);
lastOffset = record.d_contentOffset + record.d_contentLength;
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
#include "dnsdist-lua-ffi.hh"
#include "dnsdist-rings.hh"
+#include "dnsdist-web.hh"
#include "dnsparser.hh"
#include "dnswriter.hh"
+bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def)
+{
+ return true;
+}
+
BOOST_AUTO_TEST_SUITE(test_dnsdist_lua_ffi)
BOOST_AUTO_TEST_CASE(test_Query)
BOOST_REQUIRE_EQUAL(bufferSize, sizeof(ids.origRemote.sin4.sin_addr.s_addr));
BOOST_CHECK(memcmp(buffer, &ids.origRemote.sin4.sin_addr.s_addr, sizeof(ids.origRemote.sin4.sin_addr.s_addr)) == 0);
BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_remote_port(&lightDQ), 4242U);
+ BOOST_CHECK(!dnsdist_ffi_dnsquestion_is_remote_v6(nullptr));
+ BOOST_CHECK(!dnsdist_ffi_dnsquestion_is_remote_v6(&lightDQ));
}
{
BOOST_CHECK_EQUAL(dnsdist_ffi_server_is_up(&server), false);
BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_name(&server), "");
BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_name_with_addr(&server), dsAddr.toStringWithPort());
- BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_weight(&server), 1U);
- BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_order(&server), 1U);
+ BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_weight(&server), 1);
+ BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_order(&server), 1);
BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_latency(&server), 0.0);
}
ids.queryRealTime.start();
DNSQuestion dq(ids, query);
packetCache->get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- packetCache->insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+ packetCache->insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
std::string poolName("test-pool");
auto testPool = std::make_shared<ServerPool>();
{
dnsheader dh;
memset(&dh, 0, sizeof(dh));
+ dh.id = htons(42);
+ dh.rd = 1;
+ dh.ancount = htons(1);
+ dh.nscount = htons(1);
+ dh.arcount = htons(1);
+ dh.rcode = RCode::NXDomain;
DNSName qname("rings.luaffi.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress backend("192.0.2.42");
BOOST_CHECK_EQUAL(dnsdist_ffi_ring_get_entries_by_mac(nullptr, nullptr), 0U);
BOOST_CHECK(list == nullptr);
BOOST_CHECK(!dnsdist_ffi_ring_entry_is_response(nullptr, 0));
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_age(nullptr, 0) == 0.0);
BOOST_CHECK(dnsdist_ffi_ring_entry_get_name(nullptr, 0) == nullptr);
BOOST_CHECK(dnsdist_ffi_ring_entry_get_type(nullptr, 0) == 0);
BOOST_CHECK(dnsdist_ffi_ring_entry_get_requestor(nullptr, 0) == nullptr);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_backend(nullptr, 0) == nullptr);
BOOST_CHECK(dnsdist_ffi_ring_entry_get_protocol(nullptr, 0) == 0);
BOOST_CHECK(dnsdist_ffi_ring_entry_get_size(nullptr, 0) == 0);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_latency(nullptr, 0) == 0);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_id(nullptr, 0) == 0);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_rcode(nullptr, 0) == 0);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_aa(nullptr, 0) == false);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_rd(nullptr, 0) == false);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_tc(nullptr, 0) == false);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_ancount(nullptr, 0) == 0);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_nscount(nullptr, 0) == 0);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_arcount(nullptr, 0) == 0);
BOOST_CHECK(!dnsdist_ffi_ring_entry_has_mac_address(nullptr, 0));
BOOST_CHECK(dnsdist_ffi_ring_entry_get_mac_address(nullptr, 0) == nullptr);
}
BOOST_CHECK(dnsdist_ffi_ring_entry_is_response(list, 1));
for (size_t idx = 0; idx < 2; idx++) {
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_age(list, idx) >= 0.0);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_age(list, idx) < 2.0);
BOOST_CHECK(dnsdist_ffi_ring_entry_get_name(list, idx) == qname.toString());
BOOST_CHECK(dnsdist_ffi_ring_entry_get_type(list, idx) == qtype);
- BOOST_CHECK(dnsdist_ffi_ring_entry_get_requestor(list, idx) == requestor1.toString());
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_requestor(list, idx) == requestor1.toStringWithPort());
BOOST_CHECK(dnsdist_ffi_ring_entry_get_protocol(list, idx) == protocol.toNumber());
BOOST_CHECK_EQUAL(dnsdist_ffi_ring_entry_get_size(list, idx), size);
+ BOOST_CHECK_EQUAL(dnsdist_ffi_ring_entry_get_id(list, idx), 42U);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_aa(list, idx) == false);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_rd(list, idx) == true);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_tc(list, idx) == false);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_ancount(list, idx) == 1);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_nscount(list, idx) == 1);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_arcount(list, idx) == 1);
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_rcode(list, idx) == RCode::NXDomain);
+ if (dnsdist_ffi_ring_entry_is_response(list, idx)) {
+ BOOST_CHECK(dnsdist_ffi_ring_entry_get_backend(list, idx) == backend.toStringWithPort());
+ BOOST_CHECK_EQUAL(dnsdist_ffi_ring_entry_get_latency(list, idx), responseTime);
+ }
BOOST_CHECK(!dnsdist_ffi_ring_entry_has_mac_address(list, idx));
BOOST_CHECK(dnsdist_ffi_ring_entry_get_mac_address(list, idx) == std::string());
}
dnsdist_ffi_ring_entry_list_free(list);
list = nullptr;
- // no the right requestor
+ // not the right requestor
BOOST_REQUIRE_EQUAL(dnsdist_ffi_ring_get_entries_by_addr("192.0.2.2", &list), 0U);
BOOST_CHECK(list == nullptr);
}
}
+BOOST_AUTO_TEST_CASE(test_hash)
+{
+ const uint32_t seed = 0x42;
+ const std::array<unsigned char, 10> data{{'0', 'x', 'd', 'e', 'a', 'd', 'b', 'E', 'e', 'F'}};
+ const std::array<unsigned char, 10> capitalizedData{{'0', 'X', 'D', 'E', 'A', 'D', 'B', 'E', 'E', 'F'}};
+
+ {
+ /* invalid */
+ BOOST_CHECK_EQUAL(dnsdist_ffi_hash(0, nullptr, 0, false), 0U);
+ BOOST_CHECK_EQUAL(dnsdist_ffi_hash(seed, nullptr, 0, false), seed);
+ }
+ {
+ /* case sensitive */
+ auto hash = dnsdist_ffi_hash(seed, data.data(), data.size(), false);
+ BOOST_CHECK_EQUAL(hash, burtle(data.data(), data.size(), seed));
+ BOOST_CHECK_NE(hash, burtle(capitalizedData.data(), capitalizedData.size(), seed));
+ BOOST_CHECK_NE(hash, burtleCI(capitalizedData.data(), capitalizedData.size(), seed));
+ }
+ {
+ /* case insensitive */
+ auto hash = dnsdist_ffi_hash(seed, data.data(), data.size(), true);
+ BOOST_CHECK_EQUAL(hash, burtleCI(data.data(), data.size(), seed));
+ BOOST_CHECK_NE(hash, burtle(capitalizedData.data(), capitalizedData.size(), seed));
+ BOOST_CHECK_EQUAL(hash, burtleCI(capitalizedData.data(), capitalizedData.size(), seed));
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END();
+++ /dev/null
-../test-dnsdist_cc.cc
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+#include <unistd.h>
+
+#include "dnsdist.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-internal-queries.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-xpf.hh"
+#include "dnsdist-xsk.hh"
+
+#include "dolog.hh"
+#include "dnsname.hh"
+#include "dnsparser.hh"
+#include "dnswriter.hh"
+#include "ednsoptions.hh"
+#include "ednscookies.hh"
+#include "ednssubnet.hh"
+
+ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+{
+ return ProcessQueryResult::Drop;
+}
+
+bool processResponseAfterRules(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted)
+{
+ return false;
+}
+
+bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
+{
+ return false;
+}
+
+bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend)
+{
+ return true;
+}
+
+namespace dnsdist
+{
+std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
+{
+ return nullptr;
+}
+}
+
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static): only a stub
+bool DNSDistSNMPAgent::sendBackendStatusChangeTrap([[maybe_unused]] DownstreamState const& backend)
+{
+ return false;
+}
+namespace dnsdist::xsk
+{
+bool XskProcessQuery(ClientState& clientState, LocalHolders& holders, XskPacket& packet)
+{
+ return false;
+}
+}
+
+bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& localRespRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids)
+{
+ return false;
+}
+
+BOOST_AUTO_TEST_SUITE(test_dnsdist_cc)
+
+static const uint16_t ECSSourcePrefixV4 = 24;
+static const uint16_t ECSSourcePrefixV6 = 56;
+
+static void validateQuery(const PacketBuffer& packet, bool hasEdns = true, bool hasXPF = false, uint16_t additionals = 0, uint16_t answers = 0, uint16_t authorities = 0)
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, answers);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, authorities);
+ uint16_t expectedARCount = additionals + (hasEdns ? 1U : 0U) + (hasXPF ? 1U : 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, expectedARCount);
+}
+
+static void validateECS(const PacketBuffer& packet, const ComboAddress& expected)
+{
+ InternalQueryState ids;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.origRemote = ComboAddress("::1");
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+ DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet));
+ BOOST_CHECK(parseEDNSOptions(dnsQuestion));
+ BOOST_REQUIRE(dnsQuestion.ednsOptions != nullptr);
+ BOOST_CHECK_EQUAL(dnsQuestion.ednsOptions->size(), 1U);
+ const auto& ecsOption = dnsQuestion.ednsOptions->find(EDNSOptionCode::ECS);
+ BOOST_REQUIRE(ecsOption != dnsQuestion.ednsOptions->cend());
+
+ string expectedOption;
+ generateECSOption(expected, expectedOption, expected.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+ /* we need to skip the option code and length, which are not included */
+ BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U);
+ BOOST_CHECK_EQUAL(expectedOption.substr(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
+}
+
+static void validateResponse(const PacketBuffer& packet, bool hasEdns, uint8_t additionalCount = 0)
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(false, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+
+ BOOST_CHECK_EQUAL(mdp.d_header.qr, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, (hasEdns ? 1U : 0U) + additionalCount);
+}
+
+BOOST_AUTO_TEST_CASE(test_addXPF)
+{
+ static const uint16_t xpfOptionCode = 65422;
+
+ DNSName name("www.powerdns.com.");
+ InternalQueryState ids;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.origRemote = ComboAddress("::1");
+ ids.origDest = ComboAddress("::1");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ PacketBuffer queryWithXPF;
+
+ {
+ PacketBuffer packet = query;
+
+ /* large enough packet */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+ DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet));
+ BOOST_CHECK_EQUAL(ids.qname, name);
+ BOOST_CHECK(ids.qtype == QType::A);
+
+ BOOST_CHECK(addXPF(dnsQuestion, xpfOptionCode));
+ BOOST_CHECK(packet.size() > query.size());
+ validateQuery(packet, false, true);
+ queryWithXPF = packet;
+ }
+
+ {
+ PacketBuffer packet = query;
+
+ /* packet is already too large for the 4096 limit over UDP */
+ packet.resize(4096);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+ DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet));
+ BOOST_CHECK_EQUAL(ids.qname, name);
+ BOOST_CHECK(ids.qtype == QType::A);
+
+ BOOST_REQUIRE(!addXPF(dnsQuestion, xpfOptionCode));
+ BOOST_CHECK_EQUAL(packet.size(), 4096U);
+ packet.resize(query.size());
+ validateQuery(packet, false, false);
+ }
+
+ {
+ PacketBuffer packet = query;
+
+ /* packet with trailing data (overriding it) */
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+ DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet));
+ BOOST_CHECK_EQUAL(ids.qname, name);
+ BOOST_CHECK(ids.qtype == QType::A);
+
+ /* add trailing data */
+ const size_t trailingDataSize = 10;
+ /* Making sure we have enough room to allow for fake trailing data */
+ packet.resize(packet.size() + trailingDataSize);
+ for (size_t idx = 0; idx < trailingDataSize; idx++) {
+ packet.push_back('A');
+ }
+
+ BOOST_CHECK(addXPF(dnsQuestion, xpfOptionCode));
+ BOOST_CHECK_EQUAL(packet.size(), queryWithXPF.size());
+ BOOST_CHECK_EQUAL(memcmp(queryWithXPF.data(), packet.data(), queryWithXPF.size()), 0);
+ validateQuery(packet, false, true);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithoutEDNS)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.0.2.1");
+ DNSName name("www.powerdns.com.");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ uint16_t len = query.size();
+
+ /* large enough packet */
+ PacketBuffer packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, true);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet);
+ validateECS(packet, remote);
+ PacketBuffer queryWithEDNS = packet;
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ packet.resize(query.size());
+ validateQuery(packet, false);
+
+ /* packet with trailing data (overriding it) */
+ packet = query;
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+ /* add trailing data */
+ const size_t trailingDataSize = 10;
+ /* Making sure we have enough room to allow for fake trailing data */
+ packet.resize(packet.size() + trailingDataSize);
+ for (size_t idx = 0; idx < trailingDataSize; idx++) {
+ packet[len + idx] = 'A';
+ }
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+ BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size());
+ BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0);
+ BOOST_CHECK_EQUAL(ednsAdded, true);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet);
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithoutEDNSButWithAnswer)
+{
+ /* this might happen for NOTIFY queries where, according to rfc1996:
+ "If ANCOUNT>0, then the answer section represents an
+ unsecure hint at the new RRset for this <QNAME,QCLASS,QTYPE>".
+ */
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.0.2.1");
+ DNSName name("www.powerdns.com.");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER, false);
+ packetWriter.xfrIP(remote.sin4.sin_addr.s_addr);
+ packetWriter.commit();
+ uint16_t len = query.size();
+
+ /* large enough packet */
+ PacketBuffer packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, true);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet, true, false, 0, 1);
+ validateECS(packet, remote);
+ PacketBuffer queryWithEDNS = packet;
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ packet.resize(query.size());
+ validateQuery(packet, false, false, 0, 1);
+
+ /* packet with trailing data (overriding it) */
+ packet = query;
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+ /* add trailing data */
+ const size_t trailingDataSize = 10;
+ /* Making sure we have enough room to allow for fake trailing data */
+ packet.resize(packet.size() + trailingDataSize);
+ for (size_t idx = 0; idx < trailingDataSize; idx++) {
+ packet[len + idx] = 'A';
+ }
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+ BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size());
+ BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0);
+ BOOST_CHECK_EQUAL(ednsAdded, true);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet, true, false, 0, 1);
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
+{
+ InternalQueryState ids;
+ ids.origRemote = ComboAddress("192.0.2.1");
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ DNSName name("www.powerdns.com.");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+
+ auto packet = query;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ BOOST_CHECK_EQUAL(ids.qname, name);
+ BOOST_CHECK(ids.qtype == QType::A);
+ BOOST_CHECK(ids.qclass == QClass::IN);
+
+ DNSQuestion dnsQuestion(ids, packet);
+ /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
+ BOOST_CHECK(!parseEDNSOptions(dnsQuestion));
+
+ /* And now we add our own ECS */
+ BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion, ednsAdded, ecsAdded));
+ BOOST_CHECK_GT(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, true);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet);
+ validateECS(packet, ids.origRemote);
+
+ /* trailing data */
+ packet = query;
+ packet.resize(2048);
+
+ ednsAdded = false;
+ ecsAdded = false;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ BOOST_CHECK_EQUAL(ids.qname, name);
+ BOOST_CHECK(ids.qtype == QType::A);
+ BOOST_CHECK(ids.qclass == QClass::IN);
+ DNSQuestion dnsQuestion2(ids, packet);
+
+ BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion2, ednsAdded, ecsAdded));
+ BOOST_CHECK_GT(packet.size(), query.size());
+ BOOST_CHECK_LT(packet.size(), 2048U);
+ BOOST_CHECK_EQUAL(ednsAdded, true);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet);
+ validateECS(packet, ids.origRemote);
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote;
+ DNSName name("www.powerdns.com.");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet);
+ validateECS(packet, remote);
+
+ /* not large enough packet */
+ consumed = 0;
+ ednsAdded = false;
+ ecsAdded = false;
+ packet = query;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet);
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed)
+{
+ InternalQueryState ids;
+ ids.origRemote = ComboAddress("2001:DB8::1");
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ DNSName name("www.powerdns.com.");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+
+ auto packet = query;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ BOOST_CHECK_EQUAL(ids.qname, name);
+ BOOST_CHECK(ids.qtype == QType::A);
+ BOOST_CHECK(ids.qclass == QClass::IN);
+
+ DNSQuestion dnsQuestion(ids, packet);
+ /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
+ BOOST_CHECK(parseEDNSOptions(dnsQuestion));
+
+ /* And now we add our own ECS */
+ BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion, ednsAdded, ecsAdded));
+ BOOST_CHECK_GT(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet);
+ validateECS(packet, ids.origRemote);
+
+ /* trailing data */
+ packet = query;
+ packet.resize(2048);
+ ednsAdded = false;
+ ecsAdded = false;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+ BOOST_CHECK_EQUAL(ids.qname, name);
+ BOOST_CHECK(ids.qtype == QType::A);
+ BOOST_CHECK(ids.qclass == QClass::IN);
+ DNSQuestion dnsQuestion2(ids, packet);
+
+ BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion2, ednsAdded, ecsAdded));
+ BOOST_CHECK_GT(packet.size(), query.size());
+ BOOST_CHECK_LT(packet.size(), 2048U);
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet);
+ validateECS(packet, ids.origRemote);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSWithSameSize)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet);
+ validateECS(packet, remote);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ ComboAddress origRemote("127.0.0.1");
+ InternalQueryState ids;
+ ids.origRemote = remote;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.qname = DNSName("www.powerdns.com.");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ uint16_t qclass = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+ BOOST_CHECK_EQUAL(qname, ids.qname);
+ BOOST_CHECK(qtype == QType::A);
+ BOOST_CHECK(qclass == QClass::IN);
+
+ DNSQuestion dnsQuestion(ids, packet);
+ dnsQuestion.ecsOverride = true;
+
+ /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
+ BOOST_CHECK(parseEDNSOptions(dnsQuestion));
+
+ /* And now we add our own ECS */
+ BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion, ednsAdded, ecsAdded));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet);
+ validateECS(packet, remote);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSWithSmaller)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, 32);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK(packet.size() < query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet);
+ validateECS(packet, remote);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSWithLarger)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ EDNSSubnetOpts ecsOpts;
+ // smaller (less specific so less bits) option
+ static_assert(8 < ECSSourcePrefixV4, "The ECS scope should be smaller");
+ ecsOpts.source = Netmask(origRemote, 8);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet);
+ validateECS(packet, remote);
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSFollowedByTSIG)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, 8);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 1);
+ validateECS(packet, remote);
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 1);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSAfterAN)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.commit();
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, 8);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 0, 1, 0);
+ validateECS(packet, remote);
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 0, 1, 0);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSAfterAuth)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::AUTHORITY, true);
+ packetWriter.commit();
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, 8);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 0, 0, 1);
+ validateECS(packet, remote);
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 0, 0, 1);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSBetweenTwoRecords)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, 8);
+ string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+ packetWriter.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 2);
+ validateECS(packet, remote);
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 2);
+}
+
+BOOST_AUTO_TEST_CASE(insertECSInEDNSBetweenTwoRecords)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ validateQuery(packet, true, false, 2);
+ validateECS(packet, remote);
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(query, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false, 2);
+}
+
+BOOST_AUTO_TEST_CASE(insertECSAfterTSIG)
+{
+ bool ednsAdded = false;
+ bool ecsAdded = false;
+ ComboAddress remote("192.168.1.25");
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+ string newECSOption;
+ generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK(packet.size() > query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, true);
+ BOOST_CHECK_EQUAL(ecsAdded, true);
+ /* the MOADNSParser does not allow anything except XPF after a TSIG */
+ BOOST_CHECK_THROW(validateQuery(packet, true, false, 1), MOADNSException);
+ validateECS(packet, remote);
+
+ /* not large enough packet */
+ packet = query;
+
+ ednsAdded = false;
+ ecsAdded = false;
+ consumed = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+ BOOST_CHECK_EQUAL(packet.size(), query.size());
+ BOOST_CHECK_EQUAL(ednsAdded, false);
+ BOOST_CHECK_EQUAL(ecsAdded, false);
+ validateQuery(packet, true, false);
+}
+
+BOOST_AUTO_TEST_CASE(removeEDNSWhenFirst)
+{
+ DNSName name("www.powerdns.com.");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ PacketBuffer newResponse;
+ int res = rewriteResponseWithoutEDNS(response, newResponse);
+ BOOST_CHECK_EQUAL(res, 0);
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+ size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
+ BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
+
+ validateResponse(newResponse, false, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeEDNSWhenIntermediary)
+{
+ DNSName name("www.powerdns.com.");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+ packetWriter.startRecord(DNSName("yetanother.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ PacketBuffer newResponse;
+ int res = rewriteResponseWithoutEDNS(response, newResponse);
+ BOOST_CHECK_EQUAL(res, 0);
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+ size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
+ BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
+
+ validateResponse(newResponse, false, 2);
+}
+
+BOOST_AUTO_TEST_CASE(removeEDNSWhenLast)
+{
+ DNSName name("www.powerdns.com.");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+ packetWriter.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+
+ PacketBuffer newResponse;
+ int res = rewriteResponseWithoutEDNS(response, newResponse);
+
+ BOOST_CHECK_EQUAL(res, 0);
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+ size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
+ BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
+
+ validateResponse(newResponse, false, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeECSWhenOnlyOption)
+{
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ uint16_t optStart = 0;
+ size_t optLen = 0;
+ bool last = false;
+
+ int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(last, true);
+
+ size_t responseLen = response.size();
+ size_t existingOptLen = optLen;
+ BOOST_CHECK(existingOptLen < responseLen);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ res = removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
+ responseLen -= (existingOptLen - optLen);
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(response.data()), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ validateResponse(response, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeECSWhenFirstOption)
+{
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV6);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr = cookiesOpt.makeOptString();
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ uint16_t optStart = 0;
+ size_t optLen = 0;
+ bool last = false;
+
+ int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(last, true);
+
+ size_t responseLen = response.size();
+ size_t existingOptLen = optLen;
+ BOOST_CHECK(existingOptLen < responseLen);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ res = removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
+ responseLen -= (existingOptLen - optLen);
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(response.data()), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ validateResponse(response, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeECSWhenIntermediaryOption)
+{
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr1 = cookiesOpt.makeOptString();
+ string cookiesOptionStr2 = cookiesOpt.makeOptString();
+
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1);
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ uint16_t optStart = 0;
+ size_t optLen = 0;
+ bool last = false;
+
+ int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(last, true);
+
+ size_t responseLen = response.size();
+ size_t existingOptLen = optLen;
+ BOOST_CHECK(existingOptLen < responseLen);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ res = removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
+ responseLen -= (existingOptLen - optLen);
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(response.data()), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ validateResponse(response, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeECSWhenLastOption)
+{
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr = cookiesOpt.makeOptString();
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ uint16_t optStart = 0;
+ size_t optLen = 0;
+ bool last = false;
+
+ int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(last, true);
+
+ size_t responseLen = response.size();
+ size_t existingOptLen = optLen;
+ BOOST_CHECK(existingOptLen < responseLen);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ res = removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
+ responseLen -= (existingOptLen - optLen);
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(response.data()), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ validateResponse(response, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenOnlyOption)
+{
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ PacketBuffer newResponse;
+ int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
+ BOOST_CHECK_EQUAL(res, 0);
+
+ BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ validateResponse(newResponse, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenFirstOption)
+{
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr = cookiesOpt.makeOptString();
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ PacketBuffer newResponse;
+ int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
+ BOOST_CHECK_EQUAL(res, 0);
+
+ BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ validateResponse(newResponse, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenIntermediaryOption)
+{
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr1 = cookiesOpt.makeOptString();
+ string cookiesOptionStr2 = cookiesOpt.makeOptString();
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1);
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ PacketBuffer newResponse;
+ int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
+ BOOST_CHECK_EQUAL(res, 0);
+
+ BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ validateResponse(newResponse, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenLastOption)
+{
+ DNSName name("www.powerdns.com.");
+ ComboAddress origRemote("127.0.0.1");
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+ packetWriter.xfr32BitInt(0x01020304);
+
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr = cookiesOpt.makeOptString();
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+ packetWriter.xfr32BitInt(0x01020304);
+ packetWriter.commit();
+
+ PacketBuffer newResponse;
+ int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
+ BOOST_CHECK_EQUAL(res, 0);
+
+ BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+ BOOST_CHECK_EQUAL(qname, name);
+ BOOST_CHECK(qtype == QType::A);
+
+ validateResponse(newResponse, true, 1);
+}
+
+static DNSQuestion turnIntoResponse(InternalQueryState& ids, PacketBuffer& query, bool resizeBuffer = true)
+{
+ if (resizeBuffer) {
+ query.resize(4096);
+ }
+
+ auto dnsQuestion = DNSQuestion(ids, query);
+
+ BOOST_CHECK(addEDNSToQueryTurnedResponse(dnsQuestion));
+
+ return dnsQuestion;
+}
+
+static int getZ(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, PacketBuffer& query)
+{
+ InternalQueryState ids;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.qname = qname;
+ ids.qtype = qtype;
+ ids.qclass = qclass;
+ ids.origDest = ComboAddress("127.0.0.1");
+ ids.origRemote = ComboAddress("127.0.0.1");
+ ids.queryRealTime.start();
+
+ auto dnsQuestion = DNSQuestion(ids, query);
+
+ return getEDNSZ(dnsQuestion);
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSZ)
+{
+ uint16_t zValue = 0;
+ uint16_t udpPayloadSize = 0;
+ DNSName qname("www.powerdns.com.");
+ uint16_t qtype = QType::A;
+ uint16_t qclass = QClass::IN;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr = cookiesOpt.makeOptString();
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+
+ {
+ /* no EDNS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.commit();
+
+ BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), false);
+ BOOST_CHECK_EQUAL(zValue, 0);
+ BOOST_CHECK_EQUAL(udpPayloadSize, 0);
+ }
+
+ {
+ /* truncated EDNS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+ packetWriter.commit();
+
+ query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1));
+ BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), false);
+ BOOST_CHECK_EQUAL(zValue, 0);
+ BOOST_CHECK_EQUAL(udpPayloadSize, 0);
+ }
+
+ {
+ /* valid EDNS, no options, DO not set */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+
+ BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), true);
+ BOOST_CHECK_EQUAL(zValue, 0);
+ BOOST_CHECK_EQUAL(udpPayloadSize, 512);
+ }
+
+ {
+ /* valid EDNS, no options, DO set */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+ packetWriter.commit();
+
+ BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), true);
+ BOOST_CHECK_EQUAL(zValue, EDNS_HEADER_FLAG_DO);
+ BOOST_CHECK_EQUAL(udpPayloadSize, 512);
+ }
+
+ {
+ /* valid EDNS, options, DO not set */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), true);
+ BOOST_CHECK_EQUAL(zValue, 0);
+ BOOST_CHECK_EQUAL(udpPayloadSize, 512);
+ }
+
+ {
+ /* valid EDNS, options, DO set */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts);
+ packetWriter.commit();
+
+ BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), true);
+ BOOST_CHECK_EQUAL(zValue, EDNS_HEADER_FLAG_DO);
+ BOOST_CHECK_EQUAL(udpPayloadSize, 512);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse)
+{
+ InternalQueryState ids;
+ ids.qname = DNSName("www.powerdns.com.");
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.origDest = ComboAddress("127.0.0.1");
+ ids.origRemote = ComboAddress("127.0.0.1");
+ ids.queryRealTime.start();
+ uint16_t zValue = 0;
+ uint16_t udpPayloadSize = 0;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr = cookiesOpt.makeOptString();
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+
+ {
+ /* no EDNS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.getHeader()->rcode = RCode::NXDomain;
+ packetWriter.commit();
+
+ auto dnsQuestion = turnIntoResponse(ids, query);
+ BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), 0);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), false);
+ BOOST_CHECK_EQUAL(zValue, 0);
+ BOOST_CHECK_EQUAL(udpPayloadSize, 0);
+ }
+
+ {
+ /* truncated EDNS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+ packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+ packetWriter.commit();
+
+ query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1));
+ auto dnsQuestion = turnIntoResponse(ids, query, false);
+ BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), 0);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), false);
+ BOOST_CHECK_EQUAL(zValue, 0);
+ BOOST_CHECK_EQUAL(udpPayloadSize, 0);
+ }
+
+ {
+ /* valid EDNS, no options, DO not set */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+
+ auto dnsQuestion = turnIntoResponse(ids, query);
+ BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), 0);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), true);
+ BOOST_CHECK_EQUAL(zValue, 0);
+ BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
+ }
+
+ {
+ /* valid EDNS, no options, DO set */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+ packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+ packetWriter.commit();
+
+ auto dnsQuestion = turnIntoResponse(ids, query);
+ BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), EDNS_HEADER_FLAG_DO);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), true);
+ BOOST_CHECK_EQUAL(zValue, EDNS_HEADER_FLAG_DO);
+ BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
+ }
+
+ {
+ /* valid EDNS, options, DO not set */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ auto dnsQuestion = turnIntoResponse(ids, query);
+ BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), 0);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), true);
+ BOOST_CHECK_EQUAL(zValue, 0);
+ BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
+ }
+
+ {
+ /* valid EDNS, options, DO set */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+ packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts);
+ packetWriter.commit();
+
+ auto dnsQuestion = turnIntoResponse(ids, query);
+ BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), EDNS_HEADER_FLAG_DO);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), true);
+ BOOST_CHECK_EQUAL(zValue, EDNS_HEADER_FLAG_DO);
+ BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSOptionsStart)
+{
+ const DNSName qname("www.powerdns.com.");
+ const uint16_t qtype = QType::A;
+ const uint16_t qclass = QClass::IN;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
+ const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+ const ComboAddress rem("127.0.0.1");
+ uint16_t optRDPosition = 0;
+ size_t remaining = 0;
+
+ const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE;
+
+ {
+ /* no EDNS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.getHeader()->rcode = RCode::NXDomain;
+ packetWriter.commit();
+
+ int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+
+ BOOST_CHECK_EQUAL(res, ENOENT);
+
+ /* truncated packet (should not matter) */
+ query.resize(query.size() - 1);
+ res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+
+ BOOST_CHECK_EQUAL(res, ENOENT);
+ }
+
+ {
+ /* valid EDNS, no options */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+
+ int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
+ BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
+
+ /* truncated packet */
+ query.resize(query.size() - 1);
+
+ res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+ BOOST_CHECK_EQUAL(res, ENOENT);
+ }
+
+ {
+ /* valid EDNS, options */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
+ BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
+
+ /* truncated options (should not matter for this test) */
+ query.resize(query.size() - 1);
+ res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
+ BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt)
+{
+
+ auto locateEDNSOption = [](const PacketBuffer& query, uint16_t code, size_t* optContentStart, uint16_t* optContentLen) {
+ uint16_t optStart = 0;
+ size_t optLen = 0;
+ bool last = false;
+ int res = locateEDNSOptRR(query, &optStart, &optLen, &last);
+ if (res != 0) {
+ // no EDNS OPT RR
+ return false;
+ }
+
+ if (optLen < optRecordMinimumSize) {
+ return false;
+ }
+
+ if (optStart < query.size() && query.at(optStart) != 0) {
+ // OPT RR Name != '.'
+ return false;
+ }
+
+ return isEDNSOptionInOpt(query, optStart, optLen, code, optContentStart, optContentLen);
+ };
+
+ const DNSName qname("www.powerdns.com.");
+ const uint16_t qtype = QType::A;
+ const uint16_t qclass = QClass::IN;
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
+ const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ const size_t sizeOfECSContent = ecsOptionStr.size();
+ const size_t sizeOfECSOption = /* option code */ 2 + /* option length */ 2 + sizeOfECSContent;
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr = cookiesOpt.makeOptString();
+ const size_t sizeOfCookieOption = /* option code */ 2 + /* option length */ 2 + cookiesOpt.size();
+ /*
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ */
+ const ComboAddress rem("127.0.0.1");
+ size_t optContentStart{std::numeric_limits<size_t>::max()};
+ uint16_t optContentLen{0};
+
+ const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE;
+
+ {
+ /* no EDNS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.getHeader()->rcode = RCode::NXDomain;
+ packetWriter.commit();
+
+ bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+ BOOST_CHECK_EQUAL(found, false);
+
+ /* truncated packet (should not matter here) */
+ query.resize(query.size() - 1);
+ found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+ BOOST_CHECK_EQUAL(found, false);
+ }
+
+ {
+ /* valid EDNS, no options */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
+
+ bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+ BOOST_CHECK_EQUAL(found, false);
+
+ /* truncated packet */
+ query.resize(query.size() - 1);
+ BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::out_of_range);
+ }
+
+ {
+ /* valid EDNS, two cookie options but no ECS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+ BOOST_CHECK_EQUAL(found, false);
+
+ /* truncated packet */
+ query.resize(query.size() - 1);
+ BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
+ }
+
+ {
+ /* valid EDNS, two ECS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+ opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+ BOOST_CHECK_EQUAL(found, true);
+ if (found) {
+ BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + /* option code */ 2 + /* option length */ 2);
+ BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
+ }
+
+ /* truncated packet */
+ query.resize(query.size() - 1);
+ BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
+ }
+
+ {
+ /* valid EDNS, one ECS between two cookies */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+ BOOST_CHECK_EQUAL(found, true);
+ if (found) {
+ BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfCookieOption + /* option code */ 2 + /* option length */ 2);
+ BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
+ }
+
+ /* truncated packet */
+ query.resize(query.size() - 1);
+ BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
+ }
+
+ {
+ /* valid EDNS, one 65002 after an ECS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+ opts.emplace_back(65535, cookiesOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+
+ bool found = locateEDNSOption(query, 65535, &optContentStart, &optContentLen);
+ BOOST_CHECK_EQUAL(found, true);
+ if (found) {
+ BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfECSOption + /* option code */ 2 + /* option length */ 2);
+ BOOST_CHECK_EQUAL(optContentLen, cookiesOptionStr.size());
+ }
+
+ /* truncated packet */
+ query.resize(query.size() - 1);
+ BOOST_CHECK_THROW(locateEDNSOption(query, 65002, &optContentStart, &optContentLen), std::range_error);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA)
+{
+ InternalQueryState ids;
+ ids.origRemote = ComboAddress("192.0.2.1");
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ ComboAddress remote;
+ DNSName name("www.powerdns.com.");
+
+ PacketBuffer query;
+ PacketBuffer queryWithEDNS;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ GenericDNSPacketWriter<PacketBuffer> packetWriterEDNS(queryWithEDNS, name, QType::A, QClass::IN, 0);
+ packetWriterEDNS.getHeader()->rd = 1;
+ packetWriterEDNS.addOpt(1232, 0, 0);
+ packetWriterEDNS.commit();
+
+ /* test NXD */
+ {
+ /* no incoming EDNS */
+ auto packet = query;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, false));
+ BOOST_CHECK(packet.size() > query.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+ BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+ BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+ }
+ {
+ /* now with incoming EDNS */
+ auto packet = queryWithEDNS;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, false));
+ BOOST_CHECK(packet.size() > queryWithEDNS.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+ BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U);
+ BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+ }
+
+ /* test No Data */
+ {
+ /* no incoming EDNS */
+ auto packet = query;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, false));
+ BOOST_CHECK(packet.size() > query.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+ BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+ BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+ }
+ {
+ /* now with incoming EDNS */
+ auto packet = queryWithEDNS;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, false));
+ BOOST_CHECK(packet.size() > queryWithEDNS.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+ BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U);
+ BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+ }
+
+ /* SOA in the authority section*/
+
+ /* test NXD */
+ {
+ /* no incoming EDNS */
+ auto packet = query;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4,
+ 5, true));
+ BOOST_CHECK(packet.size() > query.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+ BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U);
+ BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+ }
+ {
+ /* now with incoming EDNS */
+ auto packet = queryWithEDNS;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, true));
+ BOOST_CHECK(packet.size() > queryWithEDNS.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+ BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+ BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+ }
+
+ /* test No Data */
+ {
+ /* no incoming EDNS */
+ auto packet = query;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, true));
+ BOOST_CHECK(packet.size() > query.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+ BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U);
+ BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+ }
+ {
+ /* now with incoming EDNS */
+ auto packet = queryWithEDNS;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, true));
+ BOOST_CHECK(packet.size() > queryWithEDNS.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+ BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+ BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS)
+{
+ InternalQueryState ids;
+ ids.origRemote = ComboAddress("192.168.1.25");
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ const DNSName name("www.powerdns.com.");
+ const ComboAddress v4Addr("192.0.2.1");
+
+ {
+ /* no EDNS and no other additional record */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ uint16_t qclass = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(!parseEDNSOptions(dnsQuestion));
+ }
+
+ {
+ /* nothing in additional (so no EDNS) but a record in ANSWER */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER);
+ packetWriter.xfrIP(v4Addr.sin4.sin_addr.s_addr);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ uint16_t qclass = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(!parseEDNSOptions(dnsQuestion));
+ }
+
+ {
+ /* nothing in additional (so no EDNS) but a record in AUTHORITY */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+ packetWriter.getHeader()->rd = 1;
+ packetWriter.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::AUTHORITY);
+ packetWriter.xfrIP(v4Addr.sin4.sin_addr.s_addr);
+ packetWriter.commit();
+
+ /* large enough packet */
+ auto packet = query;
+
+ unsigned int consumed = 0;
+ uint16_t qtype = 0;
+ uint16_t qclass = 0;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+ DNSQuestion dnsQuestion(ids, packet);
+
+ BOOST_CHECK(!parseEDNSOptions(dnsQuestion));
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_setEDNSOption)
+{
+ InternalQueryState ids;
+ ids.origRemote = ComboAddress("192.0.2.1:42");
+ ids.origDest = ComboAddress("127.0.0.1:53");
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.qname = DNSName("powerdns.com.");
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.queryRealTime.start();
+
+ timespec expiredTime{};
+ /* the internal QPS limiter does not use the real time */
+ gettime(&expiredTime);
+
+ PacketBuffer packet;
+ GenericDNSPacketWriter<PacketBuffer> packetWriter(packet, ids.qname, ids.qtype, ids.qclass, 0);
+ packetWriter.addOpt(4096, 0, EDNS_HEADER_FLAG_DO);
+ packetWriter.commit();
+
+ DNSQuestion dnsQuestion(ids, packet);
+
+ std::string result;
+ EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+ string cookiesOptionStr = cookiesOpt.makeOptString();
+
+ BOOST_REQUIRE(setEDNSOption(dnsQuestion, EDNSOptionCode::COOKIE, cookiesOptionStr));
+
+ const auto& data = dnsQuestion.getData();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(true, reinterpret_cast<const char*>(data.data()), data.size());
+
+ BOOST_CHECK_EQUAL(mdp.d_qname.toString(), ids.qname.toString());
+ BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+ BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::OPT));
+ BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, g_rootdnsname);
+
+ EDNS0Record edns0{};
+ BOOST_REQUIRE(getEDNS0Record(dnsQuestion.getData(), edns0));
+ BOOST_CHECK_EQUAL(edns0.version, 0U);
+ BOOST_CHECK_EQUAL(edns0.extRCode, 0U);
+ BOOST_CHECK_EQUAL(edns0.extFlags, EDNS_HEADER_FLAG_DO);
+
+ BOOST_REQUIRE(parseEDNSOptions(dnsQuestion));
+ BOOST_REQUIRE(dnsQuestion.ednsOptions != nullptr);
+ BOOST_CHECK_EQUAL(dnsQuestion.ednsOptions->size(), 1U);
+ const auto& ecsOption = dnsQuestion.ednsOptions->find(EDNSOptionCode::COOKIE);
+ BOOST_REQUIRE(ecsOption != dnsQuestion.ednsOptions->cend());
+
+ BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U);
+ BOOST_CHECK_EQUAL(cookiesOptionStr, std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
+}
+
+BOOST_AUTO_TEST_SUITE_END();
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
return true;
}
- void handleResponse(const struct timeval&, TCPResponse&&) override
+ void handleResponse([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
{
}
- void handleXFRResponse(const struct timeval&, TCPResponse&&) override
+ void handleXFRResponse([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
{
}
- void notifyIOError(InternalQueryState&&, const struct timeval&) override
+ void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
{
errorRaised = true;
}
- bool errorRaised{false};
+ std::atomic<bool> errorRaised{false};
};
struct DummyCrossProtocolQuery : public CrossProtocolQuery
auto holder = std::make_unique<dnsdist::AsynchronousHolder>(false);
uint16_t asyncID = 1;
uint16_t queryID = 42;
- struct timeval ttd;
- gettimeofday(&ttd, nullptr);
- // timeout in 10 ms
- const timeval add{0, 10000};
- ttd = ttd + add;
+ struct timeval ttd
+ {
+ };
std::shared_ptr<DummyQuerySender> sender{nullptr};
{
+ // timeout in 10 ms
+ const timeval add{0, 10000};
auto query = std::make_unique<DummyCrossProtocolQuery>();
sender = query->d_sender;
BOOST_REQUIRE(sender != nullptr);
+ gettimeofday(&ttd, nullptr);
+ ttd = ttd + add;
holder->push(asyncID, queryID, ttd, std::move(query));
BOOST_CHECK(!holder->empty());
}
- // sleep for 20 ms, to be sure
- usleep(20000);
+ // the event should be triggered after 10 ms, but we have seen
+ // many spurious failures on our CI, likely because the box is
+ // overloaded, so sleep for up to 100 ms to be sure
+ for (size_t counter = 0; !holder->empty() && counter < 10; counter++) {
+ usleep(10000);
+ }
BOOST_CHECK(holder->empty());
- BOOST_CHECK(sender->errorRaised);
+ BOOST_CHECK(sender->errorRaised.load());
holder->stop();
}
sender = query->d_sender;
BOOST_REQUIRE(sender != nullptr);
holder->push(asyncID, queryID, ttd, std::move(query));
- BOOST_CHECK(!holder->empty());
}
- // sleep for 20 ms
- usleep(20000);
+ // the expired event should be triggered almost immediately,
+ // but we have seen many spurious failures on our CI,
+ // likely because the box is overloaded, so sleep for up to
+ // 100 ms to be sure
+ for (size_t counter = 0; !holder->empty() && counter < 10; counter++) {
+ usleep(10000);
+ }
BOOST_CHECK(holder->empty());
- BOOST_CHECK(sender->errorRaised);
+ BOOST_CHECK(sender->errorRaised.load());
holder->stop();
}
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
BOOST_CHECK_EQUAL(ds.getStatus(), "down");
BOOST_CHECK_EQUAL(ds.healthCheckRequired(currentTime), false);
/* and the wait time between two checks will double every time a failure occurs */
- BOOST_CHECK_EQUAL(ds.getNextLazyHealthCheck(), (currentTime + (config.d_lazyHealthCheckFailedInterval * std::pow(2U, ds.currentCheckFailures))));
- BOOST_CHECK_EQUAL(ds.currentCheckFailures, 0U);
+ BOOST_CHECK_EQUAL(ds.getNextLazyHealthCheck(), (currentTime + (config.d_lazyHealthCheckFailedInterval * std::pow(2U, ds.currentCheckFailures - 1))));
+ BOOST_CHECK_EQUAL(ds.currentCheckFailures, 1U);
/* so after 5 failures */
const size_t nbFailures = 5;
BOOST_CHECK(ds.healthCheckRequired(currentTime));
ds.submitHealthCheckResult(false, false);
}
- BOOST_CHECK_EQUAL(ds.currentCheckFailures, nbFailures);
- BOOST_CHECK_EQUAL(ds.getNextLazyHealthCheck(), (currentTime + (config.d_lazyHealthCheckFailedInterval * std::pow(2U, ds.currentCheckFailures))));
+ BOOST_CHECK_EQUAL(ds.currentCheckFailures, nbFailures + 1);
+ BOOST_CHECK_EQUAL(ds.getNextLazyHealthCheck(), (currentTime + (config.d_lazyHealthCheckFailedInterval * std::pow(2U, ds.currentCheckFailures - 1))));
/* we need minRiseSuccesses successful health-checks to go up */
BOOST_REQUIRE(config.minRiseSuccesses >= 1);
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnsdist-backoff.hh"
+
+BOOST_AUTO_TEST_SUITE(dnsdistbackoff)
+
+BOOST_AUTO_TEST_CASE(test_ExponentialBackOffTimer)
+{
+ const unsigned int maxBackOff = 10 * 60;
+ const ExponentialBackOffTimer ebot(maxBackOff);
+ const std::vector<std::pair<size_t, unsigned int>> testVector{
+ {0U, 1U},
+ {1U, 2U},
+ {2U, 4U},
+ {3U, 8U},
+ {4U, 16U},
+ {5U, 32U},
+ {6U, 64U},
+ {7U, 128U},
+ {8U, 256U},
+ {9U, 512U},
+ {10U, maxBackOff}};
+ for (const auto& entry : testVector) {
+ BOOST_CHECK_EQUAL(ebot.get(entry.first), entry.second);
+ }
+
+ /* the behaviour is identical after 32 but let's go to 1024 to be safe */
+ for (size_t consecutiveFailures = testVector.size(); consecutiveFailures < 1024; consecutiveFailures++) {
+ BOOST_CHECK_EQUAL(ebot.get(consecutiveFailures), maxBackOff);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
#include "dnsdist.hh"
#include "dnsdist-dynblocks.hh"
+#include "dnsdist-metrics.hh"
#include "dnsdist-rings.hh"
Rings g_rings;
BOOST_AUTO_TEST_SUITE(dnsdistdynblocks_hh)
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+struct TestFixture
+{
+ TestFixture()
+ {
+ g_rings.reset();
+ g_rings.init();
+ }
+ ~TestFixture()
+ {
+ g_rings.reset();
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress requestor2("192.0.2.2");
const auto action = DNSAction::Action::Drop;
const std::string reason = "Exceeded query rate";
- g_rings.reset();
- g_rings.init();
-
DynBlockRulesGroup dbrg;
dbrg.setQuiet(true);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
/* we do not care about the response during that test, but we want to make sure
these do not interfere with the computation */
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
struct timespec when = now;
when.tv_sec -= (9 - timeIdx);
- g_rings.insertQuery(when, requestor1, qname, qtype, size, dh, protocol);
- g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertQuery(when, requestor1, qname, qtype, size, dnsHeader, protocol);
+ g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries * numberOfSeconds);
}
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6) {
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6, TestFixture) {
/* Check that we correctly group IPv6 addresses from the same /64 subnet into the same
dynamic block entry, if instructed to do so */
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("2001:db8::1");
ComboAddress backend("2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff");
dbrg.setQuiet(true);
dbrg.setMasks(32, 64, 0);
- g_rings.reset();
- g_rings.init();
-
/* block above 50 qps for numberOfSeconds seconds, no warning */
dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
/* we do not care about the response during that test, but we want to make sure
these do not interfere with the computation */
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
ComboAddress requestor("2001:db8::" + std::to_string(idx));
- g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
- g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
+ g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
}
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports) {
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports, TestFixture) {
/* Check that we correctly split IPv4 addresses based on port ranges, when instructed to do so */
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1:42");
ComboAddress backend("192.0.2.254");
/* split v4 by ports using a /2 (0 - 16383, 16384 - 32767, 32768 - 49151, 49152 - 65535) */
dbrg.setMasks(32, 128, 2);
- g_rings.reset();
- g_rings.init();
-
/* block above 50 qps for numberOfSeconds seconds, no warning */
dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
/* we do not care about the response during that test, but we want to make sure
these do not interfere with the computation */
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
ComboAddress requestor("192.0.2.1:" + std::to_string(idx));
- g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
- g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
+ g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
ComboAddress requestor("192.0.2.1:" + std::to_string(idx));
- g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
- g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
+ g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
}
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_responses) {
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate_responses, TestFixture) {
/* check that the responses are not accounted as queries when a
rcode rate rule is defined (sounds very specific but actually happened) */
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress requestor2("192.0.2.2");
struct timespec when = now;
when.tv_sec -= (99 - timeIdx);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(when, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(when, requestor1, qname, qtype, size, dnsHeader, protocol);
/* we do not care about the response during that test, but we want to make sure
these do not interfere with the computation */
- g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries * 100);
}
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QTypeRate, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress requestor2("192.0.2.2");
DynBlockRulesGroup dbrg;
dbrg.setQuiet(true);
- g_rings.reset();
- g_rings.init();
/* block above 50 qps for numberOfSeconds seconds, no warning */
dbrg.setQTypeRate(QType::AAAA, 50, 0, numberOfSeconds, reason, blockDuration, action);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, QType::A, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, QType::A, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_RCodeRate, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress requestor2("192.0.2.2");
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = rcode;
+ dnsHeader.rcode = rcode;
for (size_t idx = 0; idx < numberOfResponses; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = RCode::FormErr;
+ dnsHeader.rcode = RCode::FormErr;
for (size_t idx = 0; idx < numberOfResponses; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = rcode;
+ dnsHeader.rcode = rcode;
for (size_t idx = 0; idx < numberOfResponses; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress requestor2("192.0.2.2");
DynBlockRulesGroup dbrg;
dbrg.setQuiet(true);
- g_rings.reset();
- g_rings.init();
-
/* block above 0.2 ServFail/Total ratio over numberOfSeconds seconds, no warning, minimum number of queries should be at least 51 */
dbrg.setRCodeRatio(rcode, 0.2, 0, numberOfSeconds, reason, blockDuration, action, 51);
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = rcode;
+ dnsHeader.rcode = rcode;
for (size_t idx = 0; idx < 20; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
- dh.rcode = RCode::NoError;
+ dnsHeader.rcode = RCode::NoError;
for (size_t idx = 0; idx < 80; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = RCode::FormErr;
+ dnsHeader.rcode = RCode::FormErr;
for (size_t idx = 0; idx < 50; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50U);
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = rcode;
+ dnsHeader.rcode = rcode;
for (size_t idx = 0; idx < 21; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
- dh.rcode = RCode::NoError;
+ dnsHeader.rcode = RCode::NoError;
for (size_t idx = 0; idx < 79; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = rcode;
+ dnsHeader.rcode = rcode;
for (size_t idx = 0; idx < 11; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
- dh.rcode = RCode::NoError;
+ dnsHeader.rcode = RCode::NoError;
for (size_t idx = 0; idx < 39; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50U);
}
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress requestor2("192.0.2.2");
DynBlockRulesGroup dbrg;
dbrg.setQuiet(true);
- g_rings.reset();
- g_rings.init();
-
/* block above 10kB/s for numberOfSeconds seconds, no warning */
dbrg.setResponseByteRate(10000, 0, numberOfSeconds, reason, blockDuration, action);
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = rcode;
+ dnsHeader.rcode = rcode;
for (size_t idx = 0; idx < numberOfResponses; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
g_dynblockNMG.setState(emptyNMG);
- dh.rcode = rcode;
+ dnsHeader.rcode = rcode;
for (size_t idx = 0; idx < numberOfResponses; idx++) {
- g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
BOOST_CHECK_EQUAL(block.blocks, 0U);
BOOST_CHECK_EQUAL(block.warning, false);
}
+}
+
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_CacheMissRatio, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1");
+ ComboAddress requestor2("192.0.2.2");
+ ComboAddress backend("192.0.2.42");
+ ComboAddress cacheHit;
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+ unsigned int responseTime = 100 * 1000; /* 100ms */
+ struct timespec now
+ {
+ };
+ gettime(&now);
+ NetmaskTree<DynBlock, AddressAndPortRange> emptyNMG;
+
+ time_t numberOfSeconds = 10;
+ unsigned int blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded cache-miss ratio";
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* block above 0.5 Cache-Miss/Total ratio over numberOfSeconds seconds, no warning, minimum number of queries should be at least 51, global cache hit at least 80% */
+ dnsdist::metrics::g_stats.cacheHits.store(80);
+ dnsdist::metrics::g_stats.cacheMisses.store(20);
+ dbrg.setCacheMissRatio(0.5, 0, numberOfSeconds, reason, blockDuration, action, 51, 0.8);
+
+ {
+ /* insert 50 cache misses and 50 cache hits from a given client in the last 10s
+ this should not trigger the rule */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < 20; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+ }
+ for (size_t idx = 0; idx < 80; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, cacheHit, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert 51 cache misses and 49 hits from a given client in the last 10s
+ this should trigger the rule this time */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < 51; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+ }
+ for (size_t idx = 0; idx < 49; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, cacheHit, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ BOOST_REQUIRE(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0U);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+ {
+ /* insert 40 misses and 10 hits from a given client in the last 10s
+ this should NOT trigger the rule since we don't have more than 50 queries */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < 40; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+ }
+ for (size_t idx = 0; idx < 10; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, cacheHit, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50U);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ /* the global cache-hit rate is too low, should not trigger */
+ dnsdist::metrics::g_stats.cacheHits.store(60);
+ dnsdist::metrics::g_stats.cacheMisses.store(40);
+ {
+ /* insert 51 cache misses and 49 hits from a given client in the last 10s */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < 51; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+ }
+ for (size_t idx = 0; idx < 49; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, cacheHit, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_REQUIRE(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_Warning, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress requestor2("192.0.2.2");
DynBlockRulesGroup dbrg;
dbrg.setQuiet(true);
- g_rings.reset();
- g_rings.init();
-
/* warn above 20 qps for numberOfSeconds seconds, block above 50 qps */
dbrg.setQueryRate(50, 20, numberOfSeconds, reason, blockDuration, action);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
}
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Ranges) {
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_Ranges, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
ComboAddress requestor1("192.0.2.1");
ComboAddress requestor2("192.0.2.42");
/* block above 50 qps for numberOfSeconds seconds, no warning */
dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
- g_rings.reset();
- g_rings.init();
-
{
/* insert just above 50 qps from the two clients in the last 10s
this should trigger the rule for the first one but not the second one */
g_dynblockNMG.setState(emptyNMG);
for (size_t idx = 0; idx < numberOfQueries; idx++) {
- g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
- g_rings.insertQuery(now, requestor2, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
+ g_rings.insertQuery(now, requestor2, qname, qtype, size, dnsHeader, protocol);
}
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries * 2);
}
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
- dnsheader dh;
- memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN, TestFixture) {
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
DNSName qname("rings.powerdns.com.");
uint16_t qtype = QType::AAAA;
uint16_t size = 42;
*/
for (size_t idx = 0; idx < 256; idx++) {
const ComboAddress requestor("192.0.2." + std::to_string(idx));
- g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
}
/* we apply the rules, all clients should be blocked */
dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
if (self.queries > 0) {
- return std::tuple<bool, boost::optional<std::string>>(true, boost::none);
+ return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(true, boost::none, boost::none);
}
- return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+ return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(false, boost::none, boost::none);
});
/* insert one fake response for 255 DNS names */
const ComboAddress requestor("192.0.2.1");
for (size_t idx = 0; idx < 256; idx++) {
- g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, outgoingProtocol);
+ g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dnsHeader, requestor /* backend, technically, but we don't care */, outgoingProtocol);
}
/* we apply the rules, all suffixes should be blocked */
const DNSName name(DNSName(std::to_string(idx)) + qname);
const auto* block = g_dynblockSMT.getLocal()->lookup(name);
BOOST_REQUIRE(block != nullptr);
+ BOOST_REQUIRE(block->action == action);
/* simulate that:
- 1.rings.powerdns.com. got 1 query
...
dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
if (self.queries > 0) {
- return std::tuple<bool, boost::optional<std::string>>(true, "blocked for a different reason");
+ return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(true, "blocked for a different reason", static_cast<int>(DNSAction::Action::Truncate));
}
- return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+ return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(false, boost::none, boost::none);
});
/* insert one fake response for 255 DNS names */
const ComboAddress requestor("192.0.2.1");
for (size_t idx = 0; idx < 256; idx++) {
- g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, dnsdist::Protocol::DoUDP);
+ g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dnsHeader, requestor /* backend, technically, but we don't care */, dnsdist::Protocol::DoUDP);
}
/* we apply the rules, all suffixes should be blocked */
const DNSName name(DNSName(std::to_string(idx)) + qname);
const auto* block = g_dynblockSMT.getLocal()->lookup(name);
BOOST_REQUIRE(block != nullptr);
+ BOOST_REQUIRE(block->action == DNSAction::Action::Truncate);
/* simulate that:
- 1.rings.powerdns.com. got 1 query
...
dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
if (self.queries > 0) {
- return std::tuple<bool, boost::optional<std::string>>(true, boost::none);
+ return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(true, boost::none, boost::none);
}
- return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+ return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(false, boost::none, boost::none);
});
bool done = false;
for (size_t idxC = 0; !done && idxC < 256; idxC++) {
for (size_t idxD = 0; !done && idxD < 256; idxD++) {
const DNSName victim(std::to_string(idxB) + "." + std::to_string(idxC) + "." + std::to_string(idxD) + qname.toString());
- g_rings.insertResponse(now, requestor, victim, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, outgoingProtocol);
+ g_rings.insertResponse(now, requestor, victim, qtype, 1000 /*usec*/, size, dnsHeader, requestor /* backend, technically, but we don't care */, outgoingProtocol);
if (g_rings.getNumberOfQueryEntries() == 1000000) {
done = true;
break;
for (size_t idxC = 0; !done && idxC < 256; idxC++) {
for (size_t idxD = 0; !done && idxD < 256; idxD++) {
const ComboAddress requestor("192." + std::to_string(idxB) + "." + std::to_string(idxC) + "." + std::to_string(idxD));
- g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
if (g_rings.getNumberOfQueryEntries() == 1000000) {
done = true;
break;
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnsdist-edns.hh"
+#include "dnsname.hh"
+#include "dnswriter.hh"
+#include "ednscookies.hh"
+#include "ednsextendederror.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+
+BOOST_AUTO_TEST_SUITE(test_dnsdist_edns)
+
+BOOST_AUTO_TEST_CASE(getExtendedDNSError)
+{
+ const DNSName name("www.powerdns.com.");
+
+ {
+ /* no EDNS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ pw.commit();
+
+ auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+ BOOST_CHECK(!infoCode);
+ BOOST_CHECK(!extraText);
+ }
+
+ {
+ /* EDNS but no EDE */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ pw.addOpt(512, 0, 0);
+ pw.commit();
+
+ auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+ BOOST_CHECK(!infoCode);
+ BOOST_CHECK(!extraText);
+ }
+
+ {
+ /* EDE with a numerical code but no text */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ const EDNSExtendedError ede{
+ .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::NetworkError),
+ .extraText = ""};
+ opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+
+ auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+ BOOST_CHECK(infoCode);
+ BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+ BOOST_CHECK(!extraText);
+ }
+
+ {
+ /* EDE with both code and text */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ const EDNSExtendedError ede{
+ .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+ .extraText = "Synthesized from aggressive NSEC cache"};
+ opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+
+ auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+ BOOST_CHECK(infoCode);
+ BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+ BOOST_CHECK(extraText);
+ BOOST_CHECK_EQUAL(*extraText, ede.extraText);
+ }
+
+ {
+ /* EDE with truncated text */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ const EDNSExtendedError ede{
+ .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+ .extraText = "Synthesized from aggressive NSEC cache"};
+ opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+
+ /* truncate the EDE text by one byte */
+ query.resize(query.size() - 1U);
+
+ BOOST_CHECK_THROW(dnsdist::edns::getExtendedDNSError(query), std::range_error);
+ }
+
+ {
+ /* EDE before ECS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ const EDNSExtendedError ede{
+ .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+ .extraText = "Synthesized from aggressive NSEC cache"};
+ opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+ EDNSSubnetOpts ecsOpt;
+ ecsOpt.source = Netmask(ComboAddress("192.0.2.1"), 24U);
+ const auto ecsOptStr = makeEDNSSubnetOptsString(ecsOpt);
+ opts.emplace_back(EDNSOptionCode::ECS, ecsOptStr);
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+
+ auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+ BOOST_CHECK(infoCode);
+ BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+ BOOST_CHECK(extraText);
+ BOOST_CHECK_EQUAL(*extraText, ede.extraText);
+ }
+
+ {
+ /* EDE after ECS */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ EDNSSubnetOpts ecsOpt;
+ ecsOpt.source = Netmask(ComboAddress("192.0.2.1"), 24U);
+ const auto ecsOptStr = makeEDNSSubnetOptsString(ecsOpt);
+ opts.emplace_back(EDNSOptionCode::ECS, ecsOptStr);
+ const EDNSExtendedError ede{
+ .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+ .extraText = "Synthesized from aggressive NSEC cache"};
+ opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+
+ auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+ BOOST_CHECK(infoCode);
+ BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+ BOOST_CHECK(extraText);
+ BOOST_CHECK_EQUAL(*extraText, ede.extraText);
+ }
+
+ {
+ /* Cookie, EDE, padding */
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+ pw.getHeader()->rd = 1;
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+ const EDNSCookiesOpt cookieOpt("deadbeefdeadbeef");
+ const auto cookieOptStr = cookieOpt.makeOptString();
+ opts.emplace_back(EDNSOptionCode::COOKIE, cookieOptStr);
+ const EDNSExtendedError ede{
+ .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+ .extraText = "Synthesized from aggressive NSEC cache"};
+ opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+ std::string paddingOptStr;
+ paddingOptStr.resize(42U);
+ opts.emplace_back(EDNSOptionCode::PADDING, paddingOptStr);
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+
+ auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+ BOOST_CHECK(infoCode);
+ BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+ BOOST_CHECK(extraText);
+ BOOST_CHECK_EQUAL(*extraText, ede.extraText);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
bool g_snmpEnabled{false};
bool g_snmpTrapsEnabled{false};
-DNSDistSNMPAgent* g_snmpAgent{nullptr};
+std::unique_ptr<DNSDistSNMPAgent> g_snmpAgent{nullptr};
#if BENCH_POLICIES
bool g_verbose{true};
-bool g_syslog{true};
-bool g_logtimestamps{false};
#include "dnsdist-rings.hh"
Rings g_rings;
GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
/* add stub implementations, we don't want to include the corresponding object files
and their dependencies */
-#ifdef HAVE_DNS_OVER_HTTPS
-std::unordered_map<std::string, std::string> DOHUnit::getHTTPHeaders() const
-{
- return {};
-}
-
-std::string DOHUnit::getHTTPPath() const
-{
- return "";
-}
-
-std::string DOHUnit::getHTTPHost() const
-{
- return "";
-}
-
-std::string DOHUnit::getHTTPScheme() const
-{
- return "";
-}
-
-std::string DOHUnit::getHTTPQueryString() const
-{
- return "";
-}
-
-void DOHUnit::setHTTPResponse(uint16_t statusCode, PacketBuffer&& body_, const std::string& contentType_)
-{
-}
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU)
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static): this is a stub, the real one is not that simple..
+bool TLSFrontend::setupTLS()
{
+ return true;
}
std::string DNSQuestion::getTrailingData() const
servers.push_back({ 2, std::make_shared<DownstreamState>(ComboAddress("192.0.2.2:53")) });
/* Second server has a higher order, so most queries should be routed to the first (remember that
we need to keep them ordered!).
- However the first server has a QPS limit at 10 qps, so any query above that should be routed
+ However the first server has a QPS limit at 10 qps, so any query above that should be routed
to the second server. */
servers.at(0).second->d_config.order = 1;
servers.at(1).second->d_config.order = 2;
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnswriter.hh"
+#include "dnsdist.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-nghttp2-in.hh"
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+#include <nghttp2/nghttp2.h>
+
+extern std::function<ProcessQueryResult(DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
+
+BOOST_AUTO_TEST_SUITE(test_dnsdistnghttp2_in_cc)
+
+struct ExpectedStep
+{
+public:
+ enum class ExpectedRequest
+ {
+ handshakeClient,
+ readFromClient,
+ writeToClient,
+ closeClient,
+ };
+
+ ExpectedStep(ExpectedRequest req, IOState next, size_t bytes_ = 0, std::function<void(int descriptor)> func = nullptr) :
+ cb(std::move(func)), request(req), nextState(next), bytes(bytes_)
+ {
+ }
+
+ std::function<void(int descriptor)> cb{nullptr};
+ ExpectedRequest request;
+ IOState nextState;
+ size_t bytes{0};
+};
+
+struct ExpectedData
+{
+ PacketBuffer d_proxyProtocolPayload;
+ std::vector<PacketBuffer> d_queries;
+ std::vector<PacketBuffer> d_responses;
+ std::vector<uint16_t> d_responseCodes;
+};
+
+class DOHConnection;
+
+static std::deque<ExpectedStep> s_steps;
+static std::map<uint64_t, ExpectedData> s_connectionContexts;
+static std::map<int, std::unique_ptr<DOHConnection>> s_connectionBuffers;
+static uint64_t s_connectionID{0};
+
+std::ostream& operator<<(std::ostream& outs, ExpectedStep::ExpectedRequest step);
+
+std::ostream& operator<<(std::ostream& outs, ExpectedStep::ExpectedRequest step)
+{
+ static const std::vector<std::string> requests = {"handshake with client", "read from client", "write to client", "close connection to client", "connect to the backend", "read from the backend", "write to the backend", "close connection to backend"};
+ outs << requests.at(static_cast<size_t>(step));
+ return outs;
+}
+
+class DOHConnection
+{
+public:
+ DOHConnection(uint64_t connectionID) :
+ d_session(std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(nullptr, nghttp2_session_del)), d_connectionID(connectionID)
+ {
+ const auto& context = s_connectionContexts.at(connectionID);
+ d_clientOutBuffer.insert(d_clientOutBuffer.begin(), context.d_proxyProtocolPayload.begin(), context.d_proxyProtocolPayload.end());
+
+ nghttp2_session_callbacks* cbs = nullptr;
+ nghttp2_session_callbacks_new(&cbs);
+ std::unique_ptr<nghttp2_session_callbacks, void (*)(nghttp2_session_callbacks*)> callbacks(cbs, nghttp2_session_callbacks_del);
+ cbs = nullptr;
+ nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback);
+ nghttp2_session_callbacks_set_on_header_callback(callbacks.get(), on_header_callback);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback);
+ nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback);
+ nghttp2_session* sess = nullptr;
+ nghttp2_session_client_new(&sess, callbacks.get(), this);
+ d_session = std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(sess, nghttp2_session_del);
+
+ std::array<nghttp2_settings_entry, 3> settings{
+ /* rfc7540 section-8.2.2:
+ "Advertising a SETTINGS_MAX_CONCURRENT_STREAMS value of zero disables
+ server push by preventing the server from creating the necessary
+ streams."
+ */
+ nghttp2_settings_entry{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 0},
+ nghttp2_settings_entry{NGHTTP2_SETTINGS_ENABLE_PUSH, 0},
+ /* we might want to make the initial window size configurable, but 16M is a large enough default */
+ nghttp2_settings_entry{NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 16 * 1024 * 1024}};
+ /* client 24 bytes magic string will be sent by nghttp2 library */
+ auto result = nghttp2_submit_settings(d_session.get(), NGHTTP2_FLAG_NONE, settings.data(), settings.size());
+ if (result != 0) {
+ throw std::runtime_error("Error submitting settings:" + std::string(nghttp2_strerror(result)));
+ }
+
+ const std::string host("unit-tests");
+ const std::string path("/dns-query");
+ for (const auto& query : context.d_queries) {
+ const auto querySize = std::to_string(query.size());
+ std::vector<nghttp2_nv> headers;
+ /* Pseudo-headers need to come first (rfc7540 8.1.2.1) */
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::METHOD_NAME, NGHTTP2Headers::HeaderConstantIndexes::METHOD_VALUE);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_NAME, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_VALUE);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::AUTHORITY_NAME, host);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::PATH_NAME, path);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_NAME, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_VALUE);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_VALUE);
+ NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_NAME, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_VALUE);
+ NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_LENGTH_NAME, querySize);
+
+ d_position = 0;
+ d_currentQuery = query;
+ nghttp2_data_provider data_provider;
+ data_provider.source.ptr = this;
+ data_provider.read_callback = [](nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -> ssize_t {
+ auto* conn = static_cast<DOHConnection*>(user_data);
+ auto& pos = conn->d_position;
+ const auto& currentQuery = conn->d_currentQuery;
+ size_t toCopy = 0;
+ if (pos < currentQuery.size()) {
+ size_t remaining = currentQuery.size() - pos;
+ toCopy = length > remaining ? remaining : length;
+ memcpy(buf, ¤tQuery.at(pos), toCopy);
+ pos += toCopy;
+ }
+
+ if (pos >= currentQuery.size()) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+ return static_cast<ssize_t>(toCopy);
+ };
+
+ auto newStreamId = nghttp2_submit_request(d_session.get(), nullptr, headers.data(), headers.size(), &data_provider, this);
+ if (newStreamId < 0) {
+ throw std::runtime_error("Error submitting HTTP request:" + std::string(nghttp2_strerror(newStreamId)));
+ }
+
+ result = nghttp2_session_send(d_session.get());
+ if (result != 0) {
+ throw std::runtime_error("Error in nghttp2_session_send:" + std::to_string(result));
+ }
+ }
+ }
+
+ std::map<int32_t, PacketBuffer> d_responses;
+ std::map<int32_t, uint16_t> d_responseCodes;
+ std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)> d_session;
+ PacketBuffer d_currentQuery;
+ PacketBuffer d_clientOutBuffer;
+ uint64_t d_connectionID{0};
+ size_t d_position{0};
+
+ void submitIncoming(const PacketBuffer& data, size_t pos, size_t toWrite) const
+ {
+ ssize_t readlen = nghttp2_session_mem_recv(d_session.get(), &data.at(pos), toWrite);
+ if (readlen < 0) {
+ throw("Fatal error while submitting line " + std::to_string(__LINE__) + ": " + std::string(nghttp2_strerror(static_cast<int>(readlen))));
+ }
+
+ /* just in case, see if we have anything to send */
+ int got = nghttp2_session_send(d_session.get());
+ if (got != 0) {
+ throw("Fatal error while sending: " + std::string(nghttp2_strerror(got)));
+ }
+ }
+
+private:
+ static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
+ {
+ auto* conn = static_cast<DOHConnection*>(user_data);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
+ conn->d_clientOutBuffer.insert(conn->d_clientOutBuffer.end(), data, data + length);
+ return static_cast<ssize_t>(length);
+ }
+
+ static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
+ {
+ auto* conn = static_cast<DOHConnection*>(user_data);
+ if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) {
+ const auto& response = conn->d_responses.at(frame->hd.stream_id);
+ if (conn->d_responseCodes.at(frame->hd.stream_id) != 200U) {
+ return 0;
+ }
+
+ BOOST_REQUIRE_GT(response.size(), sizeof(dnsheader));
+ const dnsheader_aligned dnsHeader(response.data());
+ uint16_t queryID = ntohs(dnsHeader.get()->id);
+
+ const auto& expected = s_connectionContexts.at(conn->d_connectionID).d_responses.at(queryID);
+ BOOST_REQUIRE_EQUAL(expected.size(), response.size());
+ for (size_t idx = 0; idx < response.size(); idx++) {
+ if (expected.at(idx) != response.at(idx)) {
+ cerr << "Mismatch at offset " << idx << ", expected " << std::to_string(response.at(idx)) << " got " << std::to_string(expected.at(idx)) << endl;
+ BOOST_CHECK(false);
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data)
+ {
+ auto* conn = static_cast<DOHConnection*>(user_data);
+ auto& response = conn->d_responses[stream_id];
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
+ response.insert(response.end(), data, data + len);
+ return 0;
+ }
+
+ static int on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data)
+ {
+ auto* conn = static_cast<DOHConnection*>(user_data);
+ const std::string status(":status");
+ if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
+ if (namelen == status.size() && memcmp(status.data(), name, status.size()) == 0) {
+ try {
+ uint16_t responseCode{0};
+ auto expected = s_connectionContexts.at(conn->d_connectionID).d_responseCodes.at((frame->hd.stream_id - 1) / 2);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+ pdns::checked_stoi_into(responseCode, std::string(reinterpret_cast<const char*>(value), valuelen));
+ conn->d_responseCodes[frame->hd.stream_id] = responseCode;
+ if (responseCode != expected) {
+ cerr << "Mismatch response code, expected " << std::to_string(expected) << " got " << std::to_string(responseCode) << endl;
+ BOOST_CHECK(false);
+ }
+ }
+ catch (const std::exception& e) {
+ infolog("Error parsing the status header for stream ID %d: %s", frame->hd.stream_id, e.what());
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ }
+ return 0;
+ }
+
+ static int on_stream_close_callback(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data)
+ {
+ return 0;
+ }
+};
+
+class MockupTLSConnection : public TLSConnection
+{
+public:
+ MockupTLSConnection(int descriptor, [[maybe_unused]] bool client = false, [[maybe_unused]] bool needProxyProtocol = false) :
+ d_descriptor(descriptor)
+ {
+ auto connectionID = s_connectionID++;
+ auto conn = std::make_unique<DOHConnection>(connectionID);
+ s_connectionBuffers[d_descriptor] = std::move(conn);
+ }
+ MockupTLSConnection(const MockupTLSConnection&) = delete;
+ MockupTLSConnection(MockupTLSConnection&&) = delete;
+ MockupTLSConnection& operator=(const MockupTLSConnection&) = delete;
+ MockupTLSConnection& operator=(MockupTLSConnection&&) = delete;
+ ~MockupTLSConnection() override = default;
+
+ IOState tryHandshake() override
+ {
+ auto step = getStep();
+ BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::handshakeClient);
+
+ return step.nextState;
+ }
+
+ IOState tryWrite(const PacketBuffer& buffer, size_t& pos, size_t toWrite) override
+ {
+ auto& conn = s_connectionBuffers.at(d_descriptor);
+ auto step = getStep();
+ BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::writeToClient);
+
+ if (step.bytes == 0) {
+ if (step.nextState == IOState::NeedWrite) {
+ return step.nextState;
+ }
+ throw std::runtime_error("Remote host closed the connection");
+ }
+
+ toWrite -= pos;
+ BOOST_REQUIRE_GE(buffer.size(), pos + toWrite);
+
+ if (step.bytes < toWrite) {
+ toWrite = step.bytes;
+ }
+
+ conn->submitIncoming(buffer, pos, toWrite);
+ pos += toWrite;
+
+ return step.nextState;
+ }
+
+ IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete = false) override
+ {
+ auto& conn = s_connectionBuffers.at(d_descriptor);
+ auto step = getStep();
+ BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::readFromClient);
+
+ if (step.bytes == 0) {
+ if (step.nextState == IOState::NeedRead) {
+ return step.nextState;
+ }
+ throw std::runtime_error("Remote host closed the connection");
+ }
+
+ auto& externalBuffer = conn->d_clientOutBuffer;
+ toRead -= pos;
+
+ if (step.bytes < toRead) {
+ toRead = step.bytes;
+ }
+ if (allowIncomplete) {
+ if (toRead > externalBuffer.size()) {
+ toRead = externalBuffer.size();
+ }
+ }
+ else {
+ BOOST_REQUIRE_GE(externalBuffer.size(), toRead);
+ }
+
+ BOOST_REQUIRE_GE(buffer.size(), toRead);
+
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+ std::copy(externalBuffer.begin(), externalBuffer.begin() + toRead, buffer.begin() + pos);
+ pos += toRead;
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+ externalBuffer.erase(externalBuffer.begin(), externalBuffer.begin() + toRead);
+
+ return step.nextState;
+ }
+
+ IOState tryConnect(bool fastOpen, const ComboAddress& remote) override
+ {
+ throw std::runtime_error("Should not happen");
+ }
+
+ void close() override
+ {
+ auto step = getStep();
+ BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::closeClient);
+ }
+
+ [[nodiscard]] bool isUsable() const override
+ {
+ return true;
+ }
+
+ [[nodiscard]] std::string getServerNameIndication() const override
+ {
+ return "";
+ }
+
+ [[nodiscard]] std::vector<uint8_t> getNextProtocol() const override
+ {
+ return std::vector<uint8_t>{'h', '2'};
+ }
+
+ [[nodiscard]] LibsslTLSVersion getTLSVersion() const override
+ {
+ return LibsslTLSVersion::TLS13;
+ }
+
+ [[nodiscard]] bool hasSessionBeenResumed() const override
+ {
+ return false;
+ }
+
+ [[nodiscard]] std::vector<std::unique_ptr<TLSSession>> getSessions() override
+ {
+ return {};
+ }
+
+ void setSession(std::unique_ptr<TLSSession>& session) override
+ {
+ }
+
+ [[nodiscard]] std::vector<int> getAsyncFDs() override
+ {
+ return {};
+ }
+
+ /* unused in that context, don't bother */
+ void doHandshake() override
+ {
+ }
+
+ void connect(bool fastOpen, const ComboAddress& remote, const struct timeval& timeout) override
+ {
+ }
+
+ size_t read(void* buffer, size_t bufferSize, const struct timeval& readTimeout, const struct timeval& totalTimeout = {0, 0}, bool allowIncomplete = false) override
+ {
+ return 0;
+ }
+
+ size_t write(const void* buffer, size_t bufferSize, const struct timeval& writeTimeout) override
+ {
+ return 0;
+ }
+
+private:
+ [[nodiscard]] ExpectedStep getStep() const
+ {
+ BOOST_REQUIRE(!s_steps.empty());
+ auto step = s_steps.front();
+ s_steps.pop_front();
+
+ if (step.cb) {
+ step.cb(d_descriptor);
+ }
+
+ return step;
+ }
+
+ const int d_descriptor;
+};
+
+#include "test-dnsdistnghttp2_common.hh"
+
+struct TestFixture
+{
+ TestFixture()
+ {
+ reset();
+ }
+ TestFixture(const TestFixture&) = delete;
+ TestFixture(TestFixture&&) = delete;
+ TestFixture& operator=(const TestFixture&) = delete;
+ TestFixture& operator=(TestFixture&&) = delete;
+ ~TestFixture()
+ {
+ reset();
+ }
+
+private:
+ void reset()
+ {
+ s_steps.clear();
+ s_connectionContexts.clear();
+ s_connectionBuffers.clear();
+ s_connectionID = 0;
+ /* we _NEED_ to set this function to empty otherwise we might get what was set
+ by the last test, and we might not like it at all */
+ s_processQuery = nullptr;
+ g_proxyProtocolACL.clear();
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_SelfAnswered, TestFixture)
+{
+ auto local = getBackendAddress("1", 80);
+ ClientState localCS(local, true, false, 0, "", {}, true);
+ localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
+ localCS.dohFrontend->d_urls.insert("/dns-query");
+
+ TCPClientThreadData threadData;
+ threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
+
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
+
+ size_t counter = 0;
+ DNSName name("powerdns.com.");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ pwQ.getHeader()->id = htons(counter);
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->id = htons(counter);
+ pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfr32BitInt(0x01020304);
+ pwR.commit();
+
+ {
+ /* dnsdist drops the query right away after receiving it, client closes the connection */
+ s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {403U}};
+ s_steps = {
+ /* opening */
+ {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+ /* settings server -> client */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+ /* settings + headers + data client -> server.. */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+ /* .. continued */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+ /* headers + data */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max()},
+ /* wait for next query, but the client closes the connection */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
+ /* server close */
+ {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+ };
+
+ auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+ state->handleIO();
+ }
+
+ {
+ /* client closes the connection right in the middle of sending the query */
+ s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {403U}};
+ s_steps = {
+ /* opening */
+ {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+ /* settings server -> client */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+ /* client sends one byte */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, 1},
+ /* then closes the connection */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
+ /* server close */
+ {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+ };
+
+ /* mark the incoming FD as always ready */
+ dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
+
+ auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+ state->handleIO();
+ while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+ threadData.mplexer->run(&now);
+ }
+ }
+
+ {
+ /* dnsdist sends a response right away, client closes the connection after getting the response */
+ s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+ /* self answered */
+ dnsQuestion.getMutableData() = response;
+ return ProcessQueryResult::SendAnswer;
+ };
+
+ s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
+
+ s_steps = {
+ /* opening */
+ {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+ /* settings server -> client */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+ /* settings + headers + data client -> server.. */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+ /* .. continued */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+ /* headers + data */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max()},
+ /* wait for next query, but the client closes the connection */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
+ /* server close */
+ {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+ };
+
+ auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+ state->handleIO();
+ }
+
+ {
+ /* dnsdist sends a response right away, but the client closes the connection without even reading the response */
+ s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+ /* self answered */
+ dnsQuestion.getMutableData() = response;
+ return ProcessQueryResult::SendAnswer;
+ };
+
+ s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
+
+ s_steps = {
+ /* opening */
+ {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+ /* settings server -> client */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+ /* settings + headers + data client -> server.. */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+ /* .. continued */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+ /* we want to send the response but the client closes the connection */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 0},
+ /* server close */
+ {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+ };
+
+ /* mark the incoming FD as always ready */
+ dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
+
+ auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+ state->handleIO();
+ while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+ threadData.mplexer->run(&now);
+ }
+ }
+
+ {
+ /* dnsdist sends a response right away, client closes the connection while getting the response */
+ s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+ /* self answered */
+ dnsQuestion.getMutableData() = response;
+ return ProcessQueryResult::SendAnswer;
+ };
+
+ s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
+
+ s_steps = {
+ /* opening */
+ {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+ /* settings server -> client */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+ /* settings + headers + data client -> server.. */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+ /* .. continued */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+ /* headers + data (partial write) */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::NeedWrite, 1},
+ /* nothing to read after that */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, 0},
+ /* then the client closes the connection before we are done */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 0},
+ /* server close */
+ {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+ };
+
+ /* mark the incoming FD as always ready */
+ dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
+
+ auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+ state->handleIO();
+ while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+ threadData.mplexer->run(&now);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_BackendTimeout, TestFixture)
+{
+ auto local = getBackendAddress("1", 80);
+ ClientState localCS(local, true, false, 0, "", {}, true);
+ localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
+ localCS.dohFrontend->d_urls.insert("/dns-query");
+
+ TCPClientThreadData threadData;
+ threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
+
+ auto backend = std::make_shared<DownstreamState>(getBackendAddress("42", 53));
+
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
+
+ size_t counter = 0;
+ DNSName name("powerdns.com.");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ pwQ.getHeader()->id = htons(counter);
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->id = htons(counter);
+ pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfr32BitInt(0x01020304);
+ pwR.commit();
+
+ {
+ /* dnsdist forwards the query to the backend, which does not answer -> timeout */
+ s_processQuery = [backend](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+ selectedBackend = backend;
+ return ProcessQueryResult::PassToBackend;
+ };
+ s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {502U}};
+ s_steps = {
+ /* opening */
+ {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+ /* settings server -> client */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+ /* settings + headers + data client -> server.. */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+ /* .. continued */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+ /* trying to read a new request while processing the first one */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead},
+ /* headers + data */
+ {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max(), [&threadData](int desc) {
+ /* set the incoming descriptor as ready */
+ dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(desc);
+ }},
+ /* wait for next query, but the client closes the connection */
+ {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
+ /* server close */
+ {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+ };
+
+ auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+ state->handleIO();
+ TCPResponse resp;
+ resp.d_idstate.d_streamID = 1;
+ state->notifyIOError(now, std::move(resp));
+ while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+ threadData.mplexer->run(&now);
+ }
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
#include "dnsdist-nghttp2.hh"
#include "sstuff.hh"
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
#include <nghttp2/nghttp2.h>
BOOST_AUTO_TEST_SUITE(test_dnsdistnghttp2_cc)
static std::deque<ExpectedStep> s_steps;
static std::map<uint16_t, ExpectedData> s_responses;
+static std::unique_ptr<FDMultiplexer> s_mplexer;
std::ostream& operator<<(std::ostream& os, const ExpectedStep::ExpectedRequest d);
auto& query = conn->d_queries.at(frame->hd.stream_id);
BOOST_REQUIRE_GT(query.size(), sizeof(dnsheader));
- auto dh = reinterpret_cast<const dnsheader*>(query.data());
+ const dnsheader_aligned dh(query.data());
uint16_t id = ntohs(dh->id);
// cerr<<"got query ID "<<id<<endl;
BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::closeClient : ExpectedStep::ExpectedRequest::closeBackend);
}
- bool hasBufferedData() const override
- {
- return false;
- }
-
bool isUsable() const override
{
return true;
bool d_client{false};
};
-class MockupTLSCtx : public TLSCtx
-{
-public:
- ~MockupTLSCtx()
- {
- }
-
- std::unique_ptr<TLSConnection> getConnection(int socket, const struct timeval& timeout, time_t now) override
- {
- return std::make_unique<MockupTLSConnection>(socket);
- }
-
- std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout) override
- {
- return std::make_unique<MockupTLSConnection>(socket, true, d_needProxyProtocol);
- }
-
- void rotateTicketsKey(time_t now) override
- {
- }
-
- size_t getTicketsKeysCount() override
- {
- return 0;
- }
-
- std::string getName() const override
- {
- return "Mockup TLS";
- }
-
- bool d_needProxyProtocol{false};
-};
-
-class MockupFDMultiplexer : public FDMultiplexer
-{
-public:
- MockupFDMultiplexer()
- {
- }
-
- ~MockupFDMultiplexer()
- {
- }
-
- int run(struct timeval* tv, int timeout = 500) override
- {
- int ret = 0;
-
- gettimeofday(tv, nullptr); // MANDATORY
-
- /* 'ready' might be altered by a callback while we are iterating */
- const auto readyFDs = ready;
- for (const auto fd : readyFDs) {
- {
- const auto& it = d_readCallbacks.find(fd);
-
- if (it != d_readCallbacks.end()) {
- it->d_callback(it->d_fd, it->d_parameter);
- }
- }
-
- {
- const auto& it = d_writeCallbacks.find(fd);
-
- if (it != d_writeCallbacks.end()) {
- it->d_callback(it->d_fd, it->d_parameter);
- }
- }
- }
-
- return ret;
- }
-
- void getAvailableFDs(std::vector<int>& fds, int timeout) override
- {
- }
-
- void addFD(int fd, FDMultiplexer::EventKind kind) override
- {
- }
-
- void removeFD(int fd, FDMultiplexer::EventKind) override
- {
- }
-
- string getName() const override
- {
- return "mockup";
- }
-
- void setReady(int fd)
- {
- ready.insert(fd);
- }
-
- void setNotReady(int fd)
- {
- ready.erase(fd);
- }
-
-private:
- std::set<int> ready;
-};
+#include "test-dnsdistnghttp2_common.hh"
class MockupQuerySender : public TCPQuerySender
{
}
BOOST_REQUIRE_GT(response.d_buffer.size(), sizeof(dnsheader));
- auto dh = reinterpret_cast<const dnsheader*>(response.d_buffer.data());
+ const dnsheader_aligned dh(response.d_buffer.data());
uint16_t id = ntohs(dh->id);
BOOST_REQUIRE_EQUAL(id, d_id);
d_valid = true;
}
- void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+ void handleXFRResponse([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
{
}
- void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
+ void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
{
d_error = true;
}
bool d_error{false};
};
-static bool isIPv6Supported()
-{
- try {
- ComboAddress addr("[2001:db8:53::1]:53");
- auto socket = std::make_unique<Socket>(addr.sin4.sin_family, SOCK_STREAM, 0);
- socket->setNonBlocking();
- int res = SConnectWithTimeout(socket->getHandle(), addr, timeval{0, 0});
- if (res == 0 || res == EINPROGRESS) {
- return true;
- }
- return false;
- }
- catch (const std::exception& e) {
- return false;
- }
-}
-
-static ComboAddress getBackendAddress(const std::string& lastDigit, uint16_t port)
-{
- static const bool useV6 = isIPv6Supported();
-
- if (useV6) {
- return ComboAddress("2001:db8:53::" + lastDigit, port);
- }
-
- return ComboAddress("192.0.2." + lastDigit, port);
-}
-
-static std::unique_ptr<FDMultiplexer> s_mplexer;
-
struct TestFixture
{
TestFixture()
BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_ConcurrentQueries, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_ConnectionReuse, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_InvalidDNSAnswer, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileWriting, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileReading, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_ShortWrite, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileWriting, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_GoAwayFromServer, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_HTTP500FromServer, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_WrongStreamID, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
BOOST_FIXTURE_TEST_CASE(test_ProxyProtocol, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
tlsCtx->d_needProxyProtocol = true;
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
}
BOOST_AUTO_TEST_SUITE_END();
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+class MockupTLSCtx : public TLSCtx
+{
+public:
+ ~MockupTLSCtx()
+ {
+ }
+
+ std::unique_ptr<TLSConnection> getConnection(int socket, const struct timeval& timeout, time_t now) override
+ {
+ return std::make_unique<MockupTLSConnection>(socket);
+ }
+
+ std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout) override
+ {
+ return std::make_unique<MockupTLSConnection>(socket, true, d_needProxyProtocol);
+ }
+
+ void rotateTicketsKey(time_t now) override
+ {
+ }
+
+ size_t getTicketsKeysCount() override
+ {
+ return 0;
+ }
+
+ std::string getName() const override
+ {
+ return "Mockup TLS";
+ }
+
+ bool d_needProxyProtocol{false};
+};
+
+class MockupFDMultiplexer : public FDMultiplexer
+{
+public:
+ MockupFDMultiplexer()
+ {
+ }
+
+ ~MockupFDMultiplexer()
+ {
+ }
+
+ int run(struct timeval* tv, int timeout = 500) override
+ {
+ int ret = 0;
+
+ gettimeofday(tv, nullptr); // MANDATORY
+
+ /* 'ready' might be altered by a callback while we are iterating */
+ const auto readyFDs = ready;
+ for (const auto fd : readyFDs) {
+ {
+ const auto& it = d_readCallbacks.find(fd);
+
+ if (it != d_readCallbacks.end()) {
+ it->d_callback(it->d_fd, it->d_parameter);
+ }
+ }
+
+ {
+ const auto& it = d_writeCallbacks.find(fd);
+
+ if (it != d_writeCallbacks.end()) {
+ it->d_callback(it->d_fd, it->d_parameter);
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ void getAvailableFDs(std::vector<int>& fds, int timeout) override
+ {
+ }
+
+ void addFD(int fd, FDMultiplexer::EventKind kind) override
+ {
+ }
+
+ void removeFD(int fd, FDMultiplexer::EventKind) override
+ {
+ }
+
+ string getName() const override
+ {
+ return "mockup";
+ }
+
+ void setReady(int fd)
+ {
+ ready.insert(fd);
+ }
+
+ void setNotReady(int fd)
+ {
+ ready.erase(fd);
+ }
+
+private:
+ std::set<int> ready;
+};
+
+static bool isIPv6Supported()
+{
+ try {
+ ComboAddress addr("[2001:db8:53::1]:53");
+ auto socket = std::make_unique<Socket>(addr.sin4.sin_family, SOCK_STREAM, 0);
+ socket->setNonBlocking();
+ int res = SConnectWithTimeout(socket->getHandle(), addr, timeval{0, 0});
+ if (res == 0 || res == EINPROGRESS) {
+ return true;
+ }
+ return false;
+ }
+ catch (const std::exception& e) {
+ return false;
+ }
+}
+
+static ComboAddress getBackendAddress(const std::string& lastDigit, uint16_t port)
+{
+ static const bool useV6 = isIPv6Supported();
+
+ if (useV6) {
+ return ComboAddress("2001:db8:53::" + lastDigit, port);
+ }
+
+ return ComboAddress("192.0.2." + lastDigit, port);
+}
+++ /dev/null
-../test-dnsdistpacketcache_cc.cc
\ No newline at end of file
--- /dev/null
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "ednscookies.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+#include "dnsdist.hh"
+#include "iputils.hh"
+#include "dnswriter.hh"
+#include "dnsdist-cache.hh"
+#include "gettime.hh"
+#include "packetcache.hh"
+
+BOOST_AUTO_TEST_SUITE(test_dnsdistpacketcache_cc)
+
+static bool receivedOverUDP = true;
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheSimple)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, 86400, 1);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+ size_t counter = 0;
+ size_t skipped = 0;
+ bool dnssecOK = false;
+ const time_t now = time(nullptr);
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ try {
+ for (counter = 0; counter < 100000; ++counter) {
+ ids.qname = DNSName(std::to_string(counter)) + DNSName(" hello");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfr32BitInt(0x01020304);
+ pwR.commit();
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ if (found) {
+ BOOST_CHECK_EQUAL(dnsQuestion.getData().size(), response.size());
+ int match = memcmp(dnsQuestion.getData().data(), response.data(), dnsQuestion.getData().size());
+ BOOST_CHECK_EQUAL(match, 0);
+ BOOST_CHECK(!subnet);
+ }
+ else {
+ skipped++;
+ }
+ }
+
+ BOOST_CHECK_EQUAL(skipped, localCache.getInsertCollisions());
+ BOOST_CHECK_EQUAL(localCache.getSize(), counter - skipped);
+
+ size_t deleted = 0;
+ size_t delcounter = 0;
+ for (delcounter = 0; delcounter < counter / 1000; ++delcounter) {
+ ids.qname = DNSName(std::to_string(delcounter)) + DNSName(" hello");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ if (found) {
+ auto removed = localCache.expungeByName(ids.qname);
+ BOOST_CHECK_EQUAL(removed, 1U);
+ deleted += removed;
+ }
+ }
+ BOOST_CHECK_EQUAL(localCache.getSize(), counter - skipped - deleted);
+
+ size_t matches = 0;
+ size_t expected = counter - skipped - deleted;
+ for (; delcounter < counter; ++delcounter) {
+ ids.qname = DNSName(std::to_string(delcounter)) + DNSName(" hello");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ if (localCache.get(dnsQuestion, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
+ matches++;
+ }
+ }
+
+ /* in the unlikely event that the test took so long that the entries did expire.. */
+ auto expired = localCache.purgeExpired(0, now);
+ BOOST_CHECK_EQUAL(matches + expired, expected);
+
+ auto remaining = localCache.getSize();
+ auto removed = localCache.expungeByName(DNSName(" hello"), QType::ANY, true);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+ BOOST_CHECK_EQUAL(removed, remaining);
+
+ /* nothing to remove */
+ BOOST_CHECK_EQUAL(localCache.purgeExpired(0, now), 0U);
+ }
+ catch (const PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheSharded)
+{
+ const size_t maxEntries = 150000;
+ const size_t numberOfShards = 10;
+ DNSDistPacketCache localCache(maxEntries, 86400, 1, 60, 3600, 60, false, numberOfShards);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+ size_t counter = 0;
+ size_t skipped = 0;
+ ComboAddress remote;
+ bool dnssecOK = false;
+ const time_t now = time(nullptr);
+ InternalQueryState ids;
+ ids.qtype = QType::AAAA;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ try {
+ for (counter = 0; counter < 100000; ++counter) {
+ ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ ComboAddress v6addr("2001:db8::1");
+ pwR.xfrCAWithoutPort(6, v6addr);
+ pwR.commit();
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::AAAA, QClass::IN, response, receivedOverUDP, 0, boost::none);
+
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ if (found) {
+ BOOST_CHECK_EQUAL(dnsQuestion.getData().size(), response.size());
+ int match = memcmp(dnsQuestion.getData().data(), response.data(), dnsQuestion.getData().size());
+ BOOST_CHECK_EQUAL(match, 0);
+ BOOST_CHECK(!subnet);
+ }
+ else {
+ skipped++;
+ }
+ }
+
+ BOOST_CHECK_EQUAL(skipped, localCache.getInsertCollisions());
+ BOOST_CHECK_EQUAL(localCache.getSize(), counter - skipped);
+
+ size_t matches = 0;
+ for (counter = 0; counter < 100000; ++counter) {
+ ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ if (localCache.get(dnsQuestion, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
+ matches++;
+ }
+ }
+
+ BOOST_CHECK_EQUAL(matches, counter - skipped);
+
+ auto remaining = localCache.getSize();
+
+ /* no entry should have expired */
+ auto expired = localCache.purgeExpired(0, now);
+ BOOST_CHECK_EQUAL(expired, 0U);
+
+ /* but after the TTL .. let's ask for at most 1k entries */
+ auto removed = localCache.purgeExpired(1000, now + 7200 + 3600);
+ BOOST_CHECK_EQUAL(removed, remaining - 1000U);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 1000U);
+
+ /* now remove everything */
+ removed = localCache.purgeExpired(0, now + 7200 + 3600);
+ BOOST_CHECK_EQUAL(removed, 1000U);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+ /* nothing to remove */
+ BOOST_CHECK_EQUAL(localCache.purgeExpired(0, now), 0U);
+ }
+ catch (const PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheTCP)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, 86400, 1);
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ ComboAddress remote;
+ bool dnssecOK = false;
+ try {
+ ids.qname = DNSName("tcp");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ ComboAddress v6addr("2001:db8::1");
+ pwR.xfrCAWithoutPort(6, v6addr);
+ pwR.commit();
+
+ {
+ /* UDP */
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+ }
+
+ {
+ /* same but over TCP */
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ ids.protocol = dnsdist::Protocol::DoTCP;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+ }
+ }
+ catch (PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, 86400, 1);
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ ComboAddress remote;
+ bool dnssecOK = false;
+ try {
+ ids.qname = DNSName("servfail");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 0;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->rcode = RCode::ServFail;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.commit();
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ // Insert with failure-TTL of 0 (-> should not enter cache).
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(0));
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ // Insert with failure-TTL non-zero (-> should enter cache).
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(300));
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+ }
+ catch (PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
+
+ ComboAddress remote;
+ bool dnssecOK = false;
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ try {
+ DNSName name("nodata");
+ ids.qname = name;
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 0;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->rcode = RCode::NoError;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.commit();
+ pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
+ pwR.commit();
+ pwR.addOpt(4096, 0, 0);
+ pwR.commit();
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+ /* it should have expired by now */
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+ }
+ catch (const PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
+
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ ComboAddress remote;
+ bool dnssecOK = false;
+ try {
+ DNSName name("nxdomain");
+ ids.qname = name;
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 0;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->rcode = RCode::NXDomain;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.commit();
+ pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
+ pwR.commit();
+ pwR.addOpt(4096, 0, 0);
+ pwR.commit();
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+ /* it should have expired by now */
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+ }
+ catch (const PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheTruncated)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
+
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.queryRealTime.start(); // does not have to be accurate ("realTime") in tests
+ bool dnssecOK = false;
+
+ try {
+ ids.qname = DNSName("truncated");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 0;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->tc = 1;
+ pwR.getHeader()->rcode = RCode::NoError;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.commit();
+ pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfr32BitInt(0x01020304);
+ pwR.commit();
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
+
+ bool allowTruncated = true;
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+
+ allowTruncated = false;
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
+ BOOST_CHECK_EQUAL(found, false);
+ }
+ catch (const PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheMaximumSize)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache packetCache(maxEntries, 86400, 1);
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ ComboAddress remote;
+ bool dnssecOK = false;
+ ids.qname = DNSName("maximum.size");
+
+ PacketBuffer query;
+ uint16_t queryID{0};
+ {
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ queryID = pwQ.getHeader()->id;
+ }
+
+ PacketBuffer response;
+ {
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = queryID;
+ pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ ComboAddress v6addr("2001:db8::1");
+ pwR.xfrCAWithoutPort(6, v6addr);
+ pwR.commit();
+ }
+
+ /* first, we set the maximum entry size to the response packet size */
+ packetCache.setMaximumEntrySize(response.size());
+
+ {
+ /* UDP */
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+ found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+ }
+
+ {
+ /* same but over TCP */
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ ids.protocol = dnsdist::Protocol::DoTCP;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+ found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+ }
+
+ /* then we set it slightly below response packet size */
+ packetCache.expunge(0);
+ packetCache.setMaximumEntrySize(response.size() - 1);
+ {
+ /* UDP */
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+ found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, false);
+ }
+
+ {
+ /* same but over TCP */
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ ids.protocol = dnsdist::Protocol::DoTCP;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+ found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, false);
+ }
+
+ /* now we generate a very big response packet, it should be cached over TCP and UDP (although in practice dnsdist will refuse to cache it for the UDP case) */
+ packetCache.expunge(0);
+ response.clear();
+ {
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = queryID;
+ for (size_t idx = 0; idx < 1000; idx++) {
+ pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ ComboAddress v6addr("2001:db8::1");
+ pwR.xfrCAWithoutPort(6, v6addr);
+ }
+ pwR.commit();
+ }
+
+ BOOST_REQUIRE_GT(response.size(), 4096U);
+ packetCache.setMaximumEntrySize(response.size());
+
+ {
+ /* UDP */
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+ found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ }
+
+ {
+ /* same but over TCP */
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ ids.protocol = dnsdist::Protocol::DoTCP;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+ found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, true);
+ }
+}
+
+static DNSDistPacketCache s_localCache(500000);
+
+static void threadMangler(unsigned int offset)
+{
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+
+ try {
+ ComboAddress remote;
+ bool dnssecOK = false;
+ for (unsigned int counter = 0; counter < 100000; ++counter) {
+ ids.qname = DNSName("hello ") + DNSName(std::to_string(counter + offset));
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.startRecord(ids.qname, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfr32BitInt(0x01020304);
+ pwR.commit();
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ s_localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+
+ s_localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+ }
+ }
+ catch (PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+AtomicCounter g_missing;
+
+static void threadReader(unsigned int offset)
+{
+ InternalQueryState ids;
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.qname = DNSName("www.powerdns.com.");
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ bool dnssecOK = false;
+ try {
+ ComboAddress remote;
+ for (unsigned int counter = 0; counter < 100000; ++counter) {
+ ids.qname = DNSName("hello ") + DNSName(std::to_string(counter + offset));
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = s_localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ if (!found) {
+ g_missing++;
+ }
+ }
+ }
+ catch (PDNSException& e) {
+ cerr << "Had error in threadReader: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded)
+{
+ try {
+ std::vector<std::thread> threads;
+ threads.reserve(4);
+ for (int i = 0; i < 4; ++i) {
+ threads.emplace_back(threadMangler, i * 1000000UL);
+ }
+
+ for (auto& thr : threads) {
+ thr.join();
+ }
+
+ threads.clear();
+
+ BOOST_CHECK_EQUAL(s_localCache.getSize() + s_localCache.getDeferredInserts() + s_localCache.getInsertCollisions(), 400000U);
+ BOOST_CHECK_SMALL(1.0 * s_localCache.getInsertCollisions(), 10000.0);
+
+ for (int i = 0; i < 4; ++i) {
+ threads.emplace_back(threadReader, i * 1000000UL);
+ }
+
+ for (auto& thr : threads) {
+ thr.join();
+ }
+
+ BOOST_CHECK((s_localCache.getDeferredInserts() + s_localCache.getDeferredLookups() + s_localCache.getInsertCollisions()) >= g_missing);
+ }
+ catch (const PDNSException& e) {
+ cerr << "Had error: " << e.reason << endl;
+ throw;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PCCollision)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+ InternalQueryState ids;
+ ids.qtype = QType::AAAA;
+ ids.qclass = QClass::IN;
+ ids.qname = DNSName("www.powerdns.com.");
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ uint16_t qid = 0x42;
+ uint32_t key{};
+ uint32_t secondKey{};
+ boost::optional<Netmask> subnetOut;
+ bool dnssecOK = false;
+
+ /* lookup for a query with a first ECS value,
+ insert a corresponding response */
+ {
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ pwQ.getHeader()->id = qid;
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
+ EDNSSubnetOpts opt;
+ opt.source = Netmask("10.0.59.220/32");
+ ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
+ pwQ.addOpt(512, 0, 0, ednsOptions);
+ pwQ.commit();
+
+ ComboAddress remote("192.0.2.1");
+ ids.queryRealTime.start();
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_REQUIRE(subnetOut);
+ BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->id = qid;
+ pwR.startRecord(ids.qname, ids.qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
+ ComboAddress v6addr("::1");
+ pwR.xfrCAWithoutPort(6, v6addr);
+ pwR.commit();
+ pwR.addOpt(512, 0, 0, ednsOptions);
+ pwR.commit();
+
+ localCache.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), dnssecOK, ids.qname, ids.qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 1U);
+
+ found = localCache.get(dnsQuestion, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_REQUIRE(subnetOut);
+ BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+ }
+
+ /* now lookup for the same query with a different ECS value,
+ we should get the same key (collision) but no match */
+ {
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ pwQ.getHeader()->id = qid;
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
+ EDNSSubnetOpts opt;
+ opt.source = Netmask("10.0.167.48/32");
+ ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
+ pwQ.addOpt(512, 0, 0, ednsOptions);
+ pwQ.commit();
+
+ ComboAddress remote("192.0.2.1");
+ ids.queryRealTime.start();
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &secondKey, subnetOut, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK_EQUAL(secondKey, key);
+ BOOST_REQUIRE(subnetOut);
+ BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+ BOOST_CHECK_EQUAL(localCache.getLookupCollisions(), 1U);
+ }
+
+#if 0
+ /* to be able to compute a new collision if the packet cache hashing code is updated */
+ {
+ DNSDistPacketCache pc(10000);
+ GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
+ EDNSSubnetOpts opt;
+ std::map<uint32_t, Netmask> colMap;
+ size_t collisions = 0;
+ size_t total = 0;
+ //qname = DNSName("collision-with-ecs-parsing.cache.tests.powerdns.com.");
+
+ for (size_t idxA = 0; idxA < 256; idxA++) {
+ for (size_t idxB = 0; idxB < 256; idxB++) {
+ for (size_t idxC = 0; idxC < 256; idxC++) {
+ PacketBuffer secondQuery;
+ GenericDNSPacketWriter<PacketBuffer> pwFQ(secondQuery, ids.qname, QType::AAAA, QClass::IN, 0);
+ pwFQ.getHeader()->rd = 1;
+ pwFQ.getHeader()->qr = false;
+ pwFQ.getHeader()->id = 0x42;
+ opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+ ednsOptions.clear();
+ ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
+ pwFQ.addOpt(512, 0, 0, ednsOptions);
+ pwFQ.commit();
+ secondKey = pc.getKey(ids.qname.toDNSString(), ids.qname.wirelength(), secondQuery, false);
+ auto pair = colMap.emplace(secondKey, opt.source);
+ total++;
+ if (!pair.second) {
+ collisions++;
+ cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
+ goto done;
+ }
+ }
+ }
+ }
+ done:
+ cerr<<"collisions: "<<collisions<<endl;
+ cerr<<"total: "<<total<<endl;
+ }
+#endif
+}
+
+BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+ InternalQueryState ids;
+ ids.qtype = QType::AAAA;
+ ids.qclass = QClass::IN;
+ ids.qname = DNSName("www.powerdns.com.");
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ uint16_t qid = 0x42;
+ uint32_t key{};
+ boost::optional<Netmask> subnetOut;
+
+ /* lookup for a query with DNSSEC OK,
+ insert a corresponding response with DO set,
+ check that it doesn't match without DO, but does with it */
+ {
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ pwQ.getHeader()->id = qid;
+ pwQ.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+ pwQ.commit();
+
+ ComboAddress remote("192.0.2.1");
+ ids.queryRealTime.start();
+ ids.origRemote = remote;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnetOut, true, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->id = qid;
+ pwR.startRecord(ids.qname, ids.qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
+ ComboAddress v6addr("::1");
+ pwR.xfrCAWithoutPort(6, v6addr);
+ pwR.commit();
+ pwR.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+ pwR.commit();
+
+ localCache.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), /* DNSSEC OK is set */ true, ids.qname, ids.qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 1U);
+
+ found = localCache.get(dnsQuestion, 0, &key, subnetOut, false, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+
+ found = localCache.get(dnsQuestion, 0, &key, subnetOut, true, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, true);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheInspection)
+{
+ const size_t maxEntries = 100;
+ DNSDistPacketCache localCache(maxEntries, 86400, 1);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+ ComboAddress remote;
+ bool dnssecOK = false;
+
+ uint32_t key = 0;
+
+ /* insert powerdns.com A 192.0.2.1, 192.0.2.2 */
+ {
+ DNSName qname("powerdns.com");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ {
+ ComboAddress addr("192.0.2.1");
+ pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfrCAWithoutPort(4, addr);
+ pwR.commit();
+ }
+ {
+ ComboAddress addr("192.0.2.2");
+ pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfrCAWithoutPort(4, addr);
+ pwR.commit();
+ }
+
+ localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+ BOOST_CHECK_EQUAL(localCache.getSize(), key);
+ }
+
+ /* insert powerdns1.com A 192.0.2.3, 192.0.2.4, AAAA 2001:db8::3, 2001:db8::4 */
+ {
+ DNSName qname("powerdns1.com");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ {
+ ComboAddress addr("192.0.2.3");
+ pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfrCAWithoutPort(4, addr);
+ pwR.commit();
+ }
+ {
+ ComboAddress addr("192.0.2.4");
+ pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfrCAWithoutPort(4, addr);
+ pwR.commit();
+ }
+ {
+ ComboAddress addr("2001:db8::3");
+ pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+ pwR.xfrCAWithoutPort(6, addr);
+ pwR.commit();
+ }
+ {
+ ComboAddress addr("2001:db8::4");
+ pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+ pwR.xfrCAWithoutPort(6, addr);
+ pwR.commit();
+ }
+
+ localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+ BOOST_CHECK_EQUAL(localCache.getSize(), key);
+ }
+
+ /* insert powerdns2.com NODATA */
+ {
+ DNSName qname("powerdns2.com");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.commit();
+ pwR.startRecord(qname, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
+ pwR.commit();
+ pwR.addOpt(4096, 0, 0);
+ pwR.commit();
+
+ localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+ BOOST_CHECK_EQUAL(localCache.getSize(), key);
+ }
+
+ /* insert powerdns3.com AAAA 2001:db8::4, 2001:db8::5 */
+ {
+ DNSName qname("powerdns3.com");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ {
+ ComboAddress addr("2001:db8::4");
+ pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+ pwR.xfrCAWithoutPort(6, addr);
+ pwR.commit();
+ }
+ {
+ ComboAddress addr("2001:db8::5");
+ pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+ pwR.xfrCAWithoutPort(6, addr);
+ pwR.commit();
+ }
+
+ localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+ BOOST_CHECK_EQUAL(localCache.getSize(), key);
+ }
+
+ /* insert powerdns4.com A 192.0.2.1 */
+ {
+ DNSName qname("powerdns4.com");
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ {
+ ComboAddress addr("192.0.2.1");
+ pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+ pwR.xfrCAWithoutPort(4, addr);
+ pwR.commit();
+ }
+
+ localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+ BOOST_CHECK_EQUAL(localCache.getSize(), key);
+ }
+
+ {
+ auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.1"));
+ BOOST_CHECK_EQUAL(domains.size(), 2U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns4.com")), 1U);
+ }
+ {
+ auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.2"));
+ BOOST_CHECK_EQUAL(domains.size(), 1U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
+ }
+ {
+ auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.3"));
+ BOOST_CHECK_EQUAL(domains.size(), 1U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+ }
+ {
+ auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.4"));
+ BOOST_CHECK_EQUAL(domains.size(), 1U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+ }
+ {
+ auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.5"));
+ BOOST_CHECK_EQUAL(domains.size(), 0U);
+ }
+ {
+ auto domains = localCache.getDomainsContainingRecords(ComboAddress("2001:db8::3"));
+ BOOST_CHECK_EQUAL(domains.size(), 1U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+ }
+ {
+ auto domains = localCache.getDomainsContainingRecords(ComboAddress("2001:db8::4"));
+ BOOST_CHECK_EQUAL(domains.size(), 2U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
+ }
+ {
+ auto domains = localCache.getDomainsContainingRecords(ComboAddress("2001:db8::5"));
+ BOOST_CHECK_EQUAL(domains.size(), 1U);
+ BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
+ }
+
+ {
+ auto records = localCache.getRecordsForDomain(DNSName("powerdns.com"));
+ BOOST_CHECK_EQUAL(records.size(), 2U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.2")), 1U);
+ }
+
+ {
+ auto records = localCache.getRecordsForDomain(DNSName("powerdns1.com"));
+ BOOST_CHECK_EQUAL(records.size(), 4U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.3")), 1U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.4")), 1U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::3")), 1U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+ }
+
+ {
+ auto records = localCache.getRecordsForDomain(DNSName("powerdns2.com"));
+ BOOST_CHECK_EQUAL(records.size(), 0U);
+ }
+
+ {
+ auto records = localCache.getRecordsForDomain(DNSName("powerdns3.com"));
+ BOOST_CHECK_EQUAL(records.size(), 2U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+ }
+
+ {
+ auto records = localCache.getRecordsForDomain(DNSName("powerdns4.com"));
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
+ }
+
+ {
+ auto records = localCache.getRecordsForDomain(DNSName("powerdns5.com"));
+ BOOST_CHECK_EQUAL(records.size(), 0U);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheXFR)
+{
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache localCache(maxEntries, 86400, 1);
+ BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+ const std::set<QType> xfrTypes = {QType::AXFR, QType::IXFR};
+ for (const auto& type : xfrTypes) {
+ bool dnssecOK = false;
+ InternalQueryState ids;
+ ids.qtype = type;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.qname = DNSName("powerdns.com.");
+
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, ids.qclass, 0);
+ pwQ.getHeader()->rd = 1;
+
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, ids.qclass, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->id = pwQ.getHeader()->id;
+ pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfr32BitInt(0x01020304);
+ pwR.commit();
+
+ uint32_t key = 0;
+ boost::optional<Netmask> subnet;
+ DNSQuestion dnsQuestion(ids, query);
+ bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
+
+ localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, ids.qtype, ids.qclass, response, receivedOverUDP, 0, boost::none);
+ found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+ BOOST_CHECK_EQUAL(found, false);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <thread>
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <thread>
BOOST_CHECK_EQUAL(pOR2.matches(&dq), false);
}
+BOOST_AUTO_TEST_CASE(test_payloadSizeRule) {
+ auto dnsQuestion = getDQ();
+
+ {
+ PayloadSizeRule rule("equal", dnsQuestion.getData().size());
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+ BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to " + std::to_string(dnsQuestion.getData().size()));
+ }
+
+ {
+ PayloadSizeRule rule("equal", dnsQuestion.getData().size() + 1);
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+ }
+
+ {
+ PayloadSizeRule rule("greater", dnsQuestion.getData().size());
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+ BOOST_CHECK_EQUAL(rule.toString(), "payload size is greater than " + std::to_string(dnsQuestion.getData().size()));
+ }
+
+ {
+ PayloadSizeRule rule("greater", dnsQuestion.getData().size() - 1);
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+ }
+
+ {
+ PayloadSizeRule rule("smaller", dnsQuestion.getData().size());
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+ BOOST_CHECK_EQUAL(rule.toString(), "payload size is smaller than " + std::to_string(dnsQuestion.getData().size()));
+ }
+
+ {
+ PayloadSizeRule rule("smaller", dnsQuestion.getData().size() + 1);
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+ }
+
+ {
+ PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size());
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+ BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to or greater than " + std::to_string(dnsQuestion.getData().size()));
+ }
+
+ {
+ PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size() - 1);
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+ }
+
+ {
+ PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size() + 1);
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+ }
+
+ {
+ PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size());
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+ BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to or smaller than " + std::to_string(dnsQuestion.getData().size()));
+ }
+
+ {
+ PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size() + 1);
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+ }
+
+ {
+ PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size() - 1);
+ BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+ }
+
+ BOOST_CHECK_THROW(PayloadSizeRule("invalid", 42U), std::runtime_error);
+}
+
BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
#include "dnsdist-tcp-downstream.hh"
#include "dnsdist-tcp-upstream.hh"
-struct DNSDistStats g_stats;
GlobalStateHolder<NetmaskGroup> g_ACL;
-GlobalStateHolder<vector<DNSDistRuleAction> > g_ruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_respruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cachehitrespruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cacheInsertedRespRuleActions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_selfansweredrespruleactions;
+GlobalStateHolder<std::vector<dnsdist::rules::RuleAction> > g_ruleactions;
GlobalStateHolder<servers_t> g_dstates;
QueryCount g_qcount;
return false;
}
-bool checkQueryHeaders(const struct dnsheader* dh, ClientState&)
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState)
{
return true;
}
{
}
-static std::function<ProcessQueryResult(DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
+std::function<ProcessQueryResult(DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
{
return ProcessQueryResult::Drop;
}
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote, unsigned int& qnameWireLength)
+bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote)
{
return true;
}
static std::function<bool(PacketBuffer& response, DNSResponse& dr, bool muted)> s_processResponse;
-bool processResponse(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& localRespRuleActions, const std::vector<DNSDistResponseRuleAction>& localCacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
+bool processResponse(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted)
{
if (s_processResponse) {
- return s_processResponse(response, dr, muted);
+ return s_processResponse(response, dnsResponse, muted);
}
return false;
BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::closeClient : ExpectedStep::ExpectedRequest::closeBackend);
}
- bool hasBufferedData() const override
- {
- return false;
- }
-
bool isUsable() const override
{
return true;
buffer.insert(buffer.begin(), newPayload.begin(), newPayload.end());
}
+struct TestFixture
+{
+ TestFixture()
+ {
+ reset();
+ }
+ TestFixture(const TestFixture&) = delete;
+ TestFixture(TestFixture&&) = delete;
+ TestFixture& operator=(const TestFixture&) = delete;
+ TestFixture& operator=(TestFixture&&) = delete;
+ ~TestFixture()
+ {
+ reset();
+ }
+
+ static void reset()
+ {
+ s_steps.clear();
+ s_readBuffer.clear();
+ s_writeBuffer.clear();
+ s_backendReadBuffer.clear();
+ s_backendWriteBuffer.clear();
+
+ g_proxyProtocolACL.clear();
+ g_verbose = false;
+ IncomingTCPConnectionState::clearAllDownstreamConnections();
+
+ /* we _NEED_ to set this function to empty otherwise we might get what was set
+ by the last test, and we might not like it at all */
+ s_processQuery = nullptr;
+ }
+};
+
static void testInit(const std::string& name, TCPClientThreadData& threadData)
{
#ifdef DEBUGLOG_ENABLED
(void) name;
#endif
- s_steps.clear();
- s_readBuffer.clear();
- s_writeBuffer.clear();
- s_backendReadBuffer.clear();
- s_backendWriteBuffer.clear();
-
- g_proxyProtocolACL.clear();
- g_verbose = false;
- IncomingTCPConnectionState::clearAllDownstreamConnections();
-
+ TestFixture::reset();
threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
}
#define TEST_INIT(str) testInit(str, threadData)
-BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_SelfAnswered, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
BOOST_CHECK(s_writeBuffer == query);
}
dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * count);
#endif
}
dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setNotReady(-1);
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
struct timeval later = now;
later.tv_sec += g_tcpRecvTimeout + 1;
dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setNotReady(-1);
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
struct timeval later = now;
later.tv_sec += g_tcpRecvTimeout + 1;
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
}
}
-BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setNotReady(-1);
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * 2U);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
}
dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setNotReady(-1);
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
struct timeval later = now;
later.tv_sec += g_tcpRecvTimeout + 1;
}
}
-BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_BackendNoOOOR, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
auto tlsCtx = std::make_shared<MockupTLSCtx>();
localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
BOOST_CHECK(s_writeBuffer == query);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
BOOST_CHECK(s_backendWriteBuffer == query);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
BOOST_CHECK(s_backendWriteBuffer == query);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
BOOST_CHECK(s_backendWriteBuffer == query);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
/* set the incoming descriptor as ready! */
dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
struct timeval later = now;
later.tv_sec += backend->d_config.tcpSendTimeout + 1;
auto expiredWriteConns = threadData.mplexer->getTimeouts(later, true);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
struct timeval later = now;
later.tv_sec += backend->d_config.tcpRecvTimeout + 1;
auto expiredConns = threadData.mplexer->getTimeouts(later, false);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
BOOST_CHECK(s_writeBuffer == query);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * backend->d_config.d_retries);
BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
BOOST_CHECK(s_writeBuffer == query);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * backend->d_config.d_retries);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
BOOST_CHECK(s_backendWriteBuffer == query);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * count);
BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
/* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
}
}
-BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
/* enable out-of-order on the front side */
localCS.d_maxInFlightQueriesPerConn = 65536;
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while ((threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
}
}
-BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR, TestFixture)
{
auto local = getBackendAddress("1", 80);
- ClientState localCS(local, true, false, false, "", {});
+ ClientState localCS(local, true, false, 0, "", {}, true);
/* enable out-of-order on the front side */
localCS.d_maxInFlightQueriesPerConn = 65536;
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
threadData.mplexer->run(&now);
}
};
auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
- IncomingTCPConnectionState::handleIO(state, now);
+ state->handleIO();
while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
threadData.mplexer->run(&now);
}
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_MAIN
#define BOOST_TEST_MODULE unit
--- /dev/null
+../views.hh
\ No newline at end of file
--- /dev/null
+../xsk.cc
\ No newline at end of file
--- /dev/null
+../xsk.hh
\ No newline at end of file
}
if(mdp.d_header.rd && !mdp.d_header.qr) {
- rdqcounts[pr.d_pheader.ts.tv_sec + 0.01*(pr.d_pheader.ts.tv_usec/10000)]++;
+ rdqcounts[pr.d_pheader.ts.tv_sec + 0.01*(pr.d_pheader.ts.tv_usec/10000.0)]++;
g_lastquestionTime=pr.d_pheader.ts;
g_clientQuestions++;
totalQueries++;
questions.emplace(mdp.d_qname, mdp.d_qtype);
}
else if(mdp.d_header.rd && mdp.d_header.qr) {
- rdacounts[pr.d_pheader.ts.tv_sec + 0.01*(pr.d_pheader.ts.tv_usec/10000)]++;
+ rdacounts[pr.d_pheader.ts.tv_sec + 0.01*(pr.d_pheader.ts.tv_usec/10000.0)]++;
g_lastanswerTime=pr.d_pheader.ts;
g_clientResponses++;
answers.emplace(mdp.d_qname, mdp.d_qtype);
DOH = 4; // DNS over HTTPS (RFC 8484)
DNSCryptUDP = 5; // DNSCrypt over UDP (https://dnscrypt.info/protocol)
DNSCryptTCP = 6; // DNSCrypt over TCP (https://dnscrypt.info/protocol)
+ DOQ = 7; // DNS over QUIC (RFC 9250)
+ }
+ enum HTTPVersion {
+ HTTP1 = 1; // HTTP/1.1
+ HTTP2 = 2; // HTTP/2
+ HTTP3 = 3; // HTTP/3
}
enum PolicyType {
UNKNOWN = 1; // No RPZ policy applied, or unknown type
optional string custom = 8; // The name of the event for custom events
}
repeated Event trace = 23;
+ optional HTTPVersion httpVersion = 24; // HTTP version used for DNS over HTTP
}
message PBDNSMessageList {
}
}
-
-DNSName::DNSName(const char* pos, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, uint16_t minOffset)
+DNSName::DNSName(const char* pos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, uint16_t minOffset)
{
if (offset >= len)
throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
packetParser(pos, len, offset, uncompress, qtype, qclass, consumed, 0, minOffset);
}
-// this should be the __only__ dns name parser in PowerDNS.
-void DNSName::packetParser(const char* qpos, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset)
+static void checkLabelLength(uint8_t length)
{
- const unsigned char* pos=(const unsigned char*)qpos;
- unsigned char labellen;
- const unsigned char *opos = pos;
+ if (length == 0) {
+ throw std::range_error("no such thing as an empty label to append");
+ }
+ if (length > 63) {
+ throw std::range_error("label too long to append");
+ }
+}
- if (offset >= len)
- throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
- if (offset < (int) minOffset)
- throw std::range_error("Trying to read before the beginning of the buffer ("+std::to_string(offset)+ " < "+std::to_string(minOffset)+")");
+// this parses a DNS name until a compression pointer is found
+size_t DNSName::parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t pos, bool uncompress)
+{
+ const size_t initialPos = pos;
+ size_t totalLength = 0;
+ unsigned char labellen = 0;
- const unsigned char* end = pos + len;
- pos += offset;
- while((labellen=*pos++) && pos < end) { // "scan and copy"
- if(labellen >= 0xc0) {
- if(!uncompress)
- throw std::range_error("Found compressed label, instructed not to follow");
+ do {
+ labellen = view.at(pos);
+ ++pos;
+
+ if (labellen == 0) {
+ --pos;
+ break;
+ }
- labellen &= (~0xc0);
- int newpos = (labellen << 8) + *(const unsigned char*)pos;
-
- if(newpos < offset) {
- if(newpos < (int) minOffset)
- throw std::range_error("Invalid label position during decompression ("+std::to_string(newpos)+ " < "+std::to_string(minOffset)+")");
- if (++depth > 100)
- throw std::range_error("Abort label decompression after 100 redirects");
- packetParser((const char*)opos, len, newpos, true, nullptr, nullptr, nullptr, depth, minOffset);
- } else
- throw std::range_error("Found a forward reference during label decompression");
- pos++;
+ if (labellen >= 0xc0) {
+ if (!uncompress) {
+ throw std::range_error("Found compressed label, instructed not to follow");
+ }
+ --pos;
break;
- } else if(labellen & 0xc0) {
+ }
+
+ if ((labellen & 0xc0) != 0) {
throw std::range_error("Found an invalid label length in qname (only one of the first two bits is set)");
}
- if (pos + labellen < end) {
- appendRawLabel((const char*)pos, labellen);
+ checkLabelLength(labellen);
+ // reserve one byte for the label length
+ if (totalLength + labellen > s_maxDNSNameLength - 1) {
+ throw std::range_error("name too long to append");
}
- else
+ if (pos + labellen >= view.size()) {
throw std::range_error("Found an invalid label length in qname");
- pos+=labellen;
- }
- if(d_storage.empty())
- d_storage.append(1, (char)0); // we just parsed the root
- if(consumed)
- *consumed = pos - opos - offset;
- if(qtype) {
- if (pos + 2 > end) {
- throw std::range_error("Trying to read qtype past the end of the buffer ("+std::to_string((pos - opos) + 2)+ " > "+std::to_string(len)+")");
}
- *qtype=(*(const unsigned char*)pos)*256 + *((const unsigned char*)pos+1);
+ pos += labellen;
+ totalLength += 1 + labellen;
}
- pos+=2;
- if(qclass) {
- if (pos + 2 > end) {
- throw std::range_error("Trying to read qclass past the end of the buffer ("+std::to_string((pos - opos) + 2)+ " > "+std::to_string(len)+")");
+ while (pos < view.size());
+
+ if (totalLength != 0) {
+ auto existingSize = d_storage.size();
+ if (existingSize > 0) {
+ // remove the last label count, we are about to override it */
+ --existingSize;
}
- *qclass=(*(const unsigned char*)pos)*256 + *((const unsigned char*)pos+1);
+ d_storage.reserve(existingSize + totalLength + 1);
+ d_storage.resize(existingSize + totalLength);
+ memcpy(&d_storage.at(existingSize), &view.at(initialPos), totalLength);
+ d_storage.append(1, static_cast<char>(0));
+ }
+ return pos;
+}
+
+// this should be the __only__ dns name parser in PowerDNS.
+void DNSName::packetParser(const char* qpos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset)
+{
+ if (offset >= len) {
+ throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
+ }
+
+ if (offset < static_cast<size_t>(minOffset)) {
+ throw std::range_error("Trying to read before the beginning of the buffer ("+std::to_string(offset)+ " < "+std::to_string(minOffset)+")");
+ }
+ unsigned char labellen{0};
+
+ pdns::views::UnsignedCharView view(qpos, len);
+ auto pos = parsePacketUncompressed(view, offset, uncompress);
+
+ labellen = view.at(pos);
+ pos++;
+ if (labellen != 0 && pos < view.size()) {
+ if (labellen < 0xc0) {
+ abort();
+ }
+
+ if (!uncompress) {
+ throw std::range_error("Found compressed label, instructed not to follow");
+ }
+
+ labellen &= (~0xc0);
+ size_t newpos = (labellen << 8) + view.at(pos);
+
+ if (newpos >= offset) {
+ throw std::range_error("Found a forward reference during label decompression");
+ }
+
+ if (newpos < minOffset) {
+ throw std::range_error("Invalid label position during decompression ("+std::to_string(newpos)+ " < "+std::to_string(minOffset)+")");
+ }
+
+ if (++depth > 100) {
+ throw std::range_error("Abort label decompression after 100 redirects");
+ }
+
+ packetParser(qpos, len, newpos, true, nullptr, nullptr, nullptr, depth, minOffset);
+
+ pos++;
+ }
+
+ if (d_storage.empty()) {
+ d_storage.append(1, static_cast<char>(0)); // we just parsed the root
+ }
+
+ if (consumed != nullptr) {
+ *consumed = pos - offset;
+ }
+
+ if (qtype != nullptr) {
+ if (pos + 2 > view.size()) {
+ throw std::range_error("Trying to read qtype past the end of the buffer ("+std::to_string(pos + 2)+ " > "+std::to_string(len)+")");
+ }
+ *qtype = view.at(pos)*256 + view.at(pos+1);
+ }
+
+ pos += 2;
+ if (qclass != nullptr) {
+ if (pos + 2 > view.size()) {
+ throw std::range_error("Trying to read qclass past the end of the buffer ("+std::to_string(pos + 2)+ " > "+std::to_string(len)+")");
+ }
+ *qclass = view.at(pos)*256 + view.at(pos+1);
}
}
std::string DNSName::toDNSString() const
{
- if (empty())
+ if (empty()) {
throw std::out_of_range("Attempt to DNSString an unset dnsname");
+ }
return std::string(d_storage.c_str(), d_storage.length());
}
// Are WE part of parent
bool DNSName::isPartOf(const DNSName& parent) const
{
- if(parent.empty() || empty())
+ if(parent.empty() || empty()) {
throw std::out_of_range("empty dnsnames aren't part of anything");
+ }
- if(parent.d_storage.size() > d_storage.size())
+ if(parent.d_storage.size() > d_storage.size()) {
return false;
+ }
// this is slightly complicated since we can't start from the end, since we can't see where a label begins/ends then
for(auto us=d_storage.cbegin(); us<d_storage.cend(); us+=*us+1) {
return ret;
}
-void DNSName::makeUsRelative(const DNSName& zone)
+void DNSName::makeUsRelative(const DNSName& zone)
{
if (isPartOf(zone)) {
d_storage.erase(d_storage.size()-zone.d_storage.size());
- d_storage.append(1, (char)0); // put back the trailing 0
- }
- else
+ d_storage.append(1, static_cast<char>(0)); // put back the trailing 0
+ }
+ else {
clear();
+ }
}
DNSName DNSName::getCommonLabels(const DNSName& other) const
{
DNSName ret;
- if(isRoot())
+ if (isRoot()) {
return *this; // we don't create the root automatically below
+ }
if (!empty()) {
vector<string> l=getRawLabels();
void DNSName::appendRawLabel(const char* start, unsigned int length)
{
- if(length==0)
- throw std::range_error("no such thing as an empty label to append");
- if(length > 63)
- throw std::range_error("label too long to append");
- if(d_storage.size() + length > s_maxDNSNameLength - 1) // reserve one byte for the label length
+ checkLabelLength(length);
+
+ // reserve one byte for the label length
+ if (d_storage.size() + length > s_maxDNSNameLength - 1) {
throw std::range_error("name too long to append");
+ }
- if(d_storage.empty()) {
- d_storage.append(1, (char)length);
+ if (d_storage.empty()) {
+ d_storage.reserve(1 + length + 1);
+ d_storage.append(1, static_cast<char>(length));
}
else {
- *d_storage.rbegin()=(char)length;
+ d_storage.reserve(d_storage.size() + length + 1);
+ *d_storage.rbegin() = static_cast<char>(length);
}
d_storage.append(start, length);
- d_storage.append(1, (char)0);
+ d_storage.append(1, static_cast<char>(0));
}
void DNSName::prependRawLabel(const std::string& label)
{
- if(label.empty())
- throw std::range_error("no such thing as an empty label to prepend");
- if(label.size() > 63)
- throw std::range_error("label too long to prepend");
- if(d_storage.size() + label.size() > s_maxDNSNameLength - 1) // reserve one byte for the label length
+ checkLabelLength(label.size());
+
+ // reserve one byte for the label length
+ if (d_storage.size() + label.size() > s_maxDNSNameLength - 1) {
throw std::range_error("name too long to prepend");
+ }
- if(d_storage.empty())
- d_storage.append(1, (char)0);
+ if (d_storage.empty()) {
+ d_storage.reserve(1 + label.size() + 1);
+ d_storage.append(1, static_cast<char>(0));
+ }
+ else {
+ d_storage.reserve(d_storage.size() + 1 + label.size());
+ }
- string_t prep(1, (char)label.size());
+ string_t prep(1, static_cast<char>(label.size()));
prep.append(label.c_str(), label.size());
d_storage = prep+d_storage;
}
-bool DNSName::slowCanonCompare(const DNSName& rhs) const
+bool DNSName::slowCanonCompare(const DNSName& rhs) const
{
auto ours=getRawLabels(), rhsLabels = rhs.getRawLabels();
return std::lexicographical_compare(ours.rbegin(), ours.rend(), rhsLabels.rbegin(), rhsLabels.rend(), CIStringCompare());
bool DNSName::chopOff()
{
- if(d_storage.empty() || d_storage[0]==0)
+ if (d_storage.empty() || d_storage[0]==0) {
return false;
+ }
d_storage.erase(0, (unsigned int)d_storage[0]+1);
return true;
}
bool DNSName::isWildcard() const
{
- if(d_storage.size() < 2)
+ if (d_storage.size() < 2) {
return false;
+ }
auto p = d_storage.begin();
return (*p == 0x01 && *++p == '*');
}
void DNSName::trimToLabels(unsigned int to)
{
- while(countLabels() > to && chopOff())
+ while(countLabels() > to && chopOff()) {
;
+ }
}
while (pos < len) {
auto p = static_cast<uint8_t>(orig[pos]);
- if(p=='.')
+ if (p=='.') {
appendTo+="\\.";
- else if(p=='\\')
+ }
+ else if (p=='\\') {
appendTo+="\\\\";
- else if(p > 0x20 && p < 0x7f)
- appendTo.append(1, (char)p);
+ }
+ else if (p > 0x20 && p < 0x7f) {
+ appendTo.append(1, static_cast<char>(p));
+ }
else {
char buf[] = "000";
auto got = snprintf(buf, sizeof(buf), "%03" PRIu8, p);
for (size_t idx = 0; idx < length; idx++) {
++pos;
char c = s.at(pos);
- if(!((c >= 'a' && c <= 'z') ||
- (c >= 'A' && c <= 'Z') ||
- (c >= '0' && c <= '9') ||
- c =='-' || c == '_' || c=='*' || c=='.' || c=='/' || c=='@' || c==' ' || c=='\\' || c==':'))
+ if (!((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c =='-' || c == '_' || c=='*' || c=='.' || c=='/' || c=='@' || c==' ' || c=='\\' || c==':')) {
return true;
+ }
}
++pos;
length = s.at(pos);
}
#include "burtle.hh"
+#include "views.hh"
// #include "dns.hh"
// #include "logger.hh"
public:
static const size_t s_maxDNSNameLength = 255;
- DNSName() {} //!< Constructs an *empty* DNSName, NOT the root!
+ DNSName() = default; //!< Constructs an *empty* DNSName, NOT the root!
// Work around assertion in some boost versions that do not like self-assignment of boost::container::string
DNSName& operator=(const DNSName& rhs)
{
}
return *this;
}
- DNSName& operator=(DNSName&& rhs)
+ DNSName& operator=(DNSName&& rhs) noexcept
{
if (this != &rhs) {
d_storage = std::move(rhs.d_storage);
DNSName(DNSName&& a) = default;
explicit DNSName(std::string_view sw); //!< Constructs from a human formatted, escaped presentation
- DNSName(const char* p, int len, int offset, bool uncompress, uint16_t* qtype=nullptr, uint16_t* qclass=nullptr, unsigned int* consumed=nullptr, uint16_t minOffset=0); //!< Construct from a DNS Packet, taking the first question if offset=12. If supplied, consumed is set to the number of bytes consumed from the packet, which will not be equal to the wire length of the resulting name in case of compression.
+ DNSName(const char* p, size_t len, size_t offset, bool uncompress, uint16_t* qtype = nullptr, uint16_t* qclass = nullptr, unsigned int* consumed = nullptr, uint16_t minOffset = 0); //!< Construct from a DNS Packet, taking the first question if offset=12. If supplied, consumed is set to the number of bytes consumed from the packet, which will not be equal to the wire length of the resulting name in case of compression.
bool isPartOf(const DNSName& rhs) const; //!< Are we part of the rhs name? Note that name.isPartOf(name).
inline bool operator==(const DNSName& rhs) const; //!< DNS-native comparison (case insensitive) - empty compares to empty
private:
string_t d_storage;
- void packetParser(const char* p, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset);
+ void packetParser(const char* qpos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset);
+ size_t parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t position, bool uncompress);
static void appendEscapedLabel(std::string& appendTo, const char* orig, size_t len);
static std::string unescapeLabel(const std::string& orig);
static void throwSafeRangeError(const std::string& msg, const char* buf, size_t length);
struct SuffixMatchNode
{
public:
- SuffixMatchNode()
- {}
+ SuffixMatchNode() = default;
SuffixMatchTree<bool> d_tree;
void add(const DNSName& dnsname)
}
d_dedup.insert(hash);
}
-
d_rrs.push_back(std::move(rr));
}
if (d_haveednscookie) {
if (d_eco.isWellFormed()) {
- optsize += EDNSCookiesOpt::EDNSCookieOptSize;
+ optsize += EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + EDNSCookiesOpt::EDNSCookieOptSize;
}
}
try {
uint8_t maxScopeMask=0;
for(pos=d_rrs.begin(); pos < d_rrs.end(); ++pos) {
- // cerr<<"during wrapup, content=["<<pos->content<<"]"<<endl;
maxScopeMask = max(maxScopeMask, pos->scopeMask);
-
+
pw.startRecord(pos->dr.d_name, pos->dr.d_type, pos->dr.d_ttl, pos->dr.d_class, pos->dr.d_place);
pos->dr.getContent()->toPacket(pw);
if(pw.size() + optsize > (d_tcp ? 65535 : getMaxReplyLen())) {
if(d_haveednssubnet) {
EDNSSubnetOpts eso = d_eso;
+ // use the scopeMask from the resolver, if it is greater - issue #5469
+ maxScopeMask = max(maxScopeMask, eso.scope.getBits());
eso.scope = Netmask(eso.source.getNetwork(), maxScopeMask);
string opt = makeEDNSSubnetOptsString(eso);
*/
d_ednsRawPacketSizeLimit=edo.d_packetsize;
d_maxreplylen=std::min(std::max(static_cast<uint16_t>(512), edo.d_packetsize), s_udpTruncationThreshold);
-// cerr<<edo.d_extFlags<<endl;
- if(edo.d_extFlags & EDNSOpts::DNSSECOK)
+ if((edo.d_extFlags & EDNSOpts::DNSSECOK) != 0) {
d_dnssecOk=true;
+ }
for(const auto & option : edo.d_options) {
if(option.first == EDNSOptionCode::NSID) {
static bool s_doEDNSSubnetProcessing;
static bool s_doEDNSCookieProcessing;
static string s_EDNSCookieKey;
+ EDNSSubnetOpts d_eso;
#ifdef ENABLE_GSS_TSIG
void cleanupGSS(int rcode);
vector<DNSZoneRecord> d_rrs; // 8
std::unordered_set<size_t> d_dedup;
string d_rawpacket; // this is where everything lives 8
- EDNSSubnetOpts d_eso;
EDNSCookiesOpt d_eco;
int d_maxreplylen{0};
PacketReader pr(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), packet.size() - serialized.size() - sizeof(dnsrecordheader));
/* needed to get the record boundaries right */
pr.getDnsrecordheader(drh);
- auto content = DNSRecordContent::mastermake(dr, pr, Opcode::Query);
+ auto content = DNSRecordContent::make(dr, pr, Opcode::Query);
return content;
}
-std::shared_ptr<DNSRecordContent> DNSRecordContent::mastermake(const DNSRecord &dr,
- PacketReader& pr)
+std::shared_ptr<DNSRecordContent> DNSRecordContent::make(const DNSRecord& dr,
+ PacketReader& pr)
{
uint16_t searchclass = (dr.d_type == QType::OPT) ? 1 : dr.d_class; // class is invalid for OPT
return i->second(dr, pr);
}
-std::shared_ptr<DNSRecordContent> DNSRecordContent::mastermake(uint16_t qtype, uint16_t qclass,
- const string& content)
+std::shared_ptr<DNSRecordContent> DNSRecordContent::make(uint16_t qtype, uint16_t qclass,
+ const string& content)
{
auto i = getZmakermap().find(pair(qclass, qtype));
if(i==getZmakermap().end()) {
return i->second(content);
}
-std::shared_ptr<DNSRecordContent> DNSRecordContent::mastermake(const DNSRecord &dr, PacketReader& pr, uint16_t oc) {
+std::shared_ptr<DNSRecordContent> DNSRecordContent::make(const DNSRecord& dr, PacketReader& pr, uint16_t oc)
+{
// For opcode UPDATE and where the DNSRecord is an answer record, we don't care about content, because this is
// not used within the prerequisite section of RFC2136, so - we can simply use unknownrecordcontent.
// For section 3.2.3, we do need content so we need to get it properly. But only for the correct QClasses.
return zmakermap;
}
+bool DNSRecordContent::isRegisteredType(uint16_t rtype, uint16_t rclass)
+{
+ return getTypemap().count(pair(rclass, rtype)) != 0;
+}
+
DNSRecord::DNSRecord(const DNSResourceRecord& rr): d_name(rr.qname)
{
d_type = rr.qtype.getCode();
d_class = rr.qclass;
d_place = DNSResourceRecord::ANSWER;
d_clen = 0;
- d_content = DNSRecordContent::mastermake(d_type, rr.qclass, rr.content);
+ d_content = DNSRecordContent::make(d_type, rr.qclass, rr.content);
}
// If you call this and you are not parsing a packet coming from a socket, you are doing it wrong.
-DNSResourceRecord DNSResourceRecord::fromWire(const DNSRecord& d) {
- DNSResourceRecord rr;
- rr.qname = d.d_name;
- rr.qtype = QType(d.d_type);
- rr.ttl = d.d_ttl;
- rr.content = d.getContent()->getZoneRepresentation(true);
- rr.auth = false;
- rr.qclass = d.d_class;
- return rr;
+DNSResourceRecord DNSResourceRecord::fromWire(const DNSRecord& wire)
+{
+ DNSResourceRecord resourceRecord;
+ resourceRecord.qname = wire.d_name;
+ resourceRecord.qtype = QType(wire.d_type);
+ resourceRecord.ttl = wire.d_ttl;
+ resourceRecord.content = wire.getContent()->getZoneRepresentation(true);
+ resourceRecord.auth = false;
+ resourceRecord.qclass = wire.d_class;
+ return resourceRecord;
}
void MOADNSParser::init(bool query, const std::string_view& packet)
dr.d_type=ah.d_type;
dr.d_class=ah.d_class;
- dr.d_name=name;
- dr.d_clen=ah.d_clen;
+ dr.d_name = std::move(name);
+ dr.d_clen = ah.d_clen;
if (query &&
!(d_qtype == QType::IXFR && dr.d_place == DNSResourceRecord::AUTHORITY && dr.d_type == QType::SOA) && // IXFR queries have a SOA in their AUTHORITY section
}
else {
// cerr<<"parsing RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl;
- dr.setContent(DNSRecordContent::mastermake(dr, pr, d_header.opcode));
+ dr.setContent(DNSRecordContent::make(dr, pr, d_header.opcode));
}
/* XXX: XPF records should be allowed after TSIG as soon as the actual XPF option code has been assigned:
break;
}
+ if (ret.empty() && !lenField) {
+ // all lenField == false cases (CAA and URI at the time of this writing) want that emptiness to be explicit
+ return "\"\"";
+ }
return ret;
}
return "";
d_pos++;
- string ret(&d_content.at(d_pos), &d_content.at(stop_at));
+ string ret(d_content.substr(d_pos, stop_at-d_pos));
d_pos = stop_at;
return ret;
}
}
try {
- auto dh = reinterpret_cast<const dnsheader*>(packet.data());
+ const dnsheader_aligned dh(packet.data());
DNSPacketMangler dpm(const_cast<char*>(reinterpret_cast<const char*>(packet.data())), length);
const uint16_t qdcount = ntohs(dh->qdcount);
return EINVAL;
}
try {
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
+ const dnsheader_aligned dh(initialPacket.data());
if (ntohs(dh->qdcount) == 0)
return ENOENT;
}
try
{
- const dnsheader* dh = (const dnsheader*) packet;
+ const dnsheader_aligned dh(packet);
DNSPacketMangler dpm(const_cast<char*>(packet), length);
const uint16_t qdcount = ntohs(dh->qdcount);
}
try
{
- const dnsheader* dh = reinterpret_cast<const dnsheader*>(packet);
+ const dnsheader_aligned dh(packet);
DNSPacketMangler dpm(const_cast<char*>(packet), length);
const uint16_t qdcount = ntohs(dh->qdcount);
}
try
{
- const dnsheader* dh = (const dnsheader*) packet;
+ const dnsheader_aligned dh(packet);
DNSPacketMangler dpm(const_cast<char*>(packet), length);
const uint16_t qdcount = ntohs(dh->qdcount);
try
{
- const dnsheader* dh = (const dnsheader*) packet;
+ const dnsheader_aligned dh(packet);
DNSPacketMangler dpm(const_cast<char*>(packet), length);
const uint16_t qdcount = ntohs(dh->qdcount);
try
{
- dnsheader dh;
- memcpy(&dh, reinterpret_cast<const dnsheader*>(packet.data()), sizeof(dh));
- uint64_t numrecords = ntohs(dh.ancount) + ntohs(dh.nscount) + ntohs(dh.arcount);
+ const dnsheader_aligned dh(packet.data());
+ uint64_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount);
PacketReader reader(packet);
uint64_t n;
- for (n = 0; n < ntohs(dh.qdcount) ; ++n) {
+ for (n = 0; n < ntohs(dh->qdcount) ; ++n) {
(void) reader.getName();
/* type and class */
reader.skip(4);
for (n = 0; n < numrecords; ++n) {
(void) reader.getName();
- uint8_t section = n < ntohs(dh.ancount) ? 1 : (n < (ntohs(dh.ancount) + ntohs(dh.nscount)) ? 2 : 3);
+ uint8_t section = n < ntohs(dh->ancount) ? 1 : (n < (ntohs(dh->ancount) + ntohs(dh->nscount)) ? 2 : 3);
uint16_t dnstype = reader.get16BitInt();
uint16_t dnsclass = reader.get16BitInt();
class DNSRecordContent
{
public:
- static std::shared_ptr<DNSRecordContent> mastermake(const DNSRecord &dr, PacketReader& pr);
- static std::shared_ptr<DNSRecordContent> mastermake(const DNSRecord &dr, PacketReader& pr, uint16_t opcode);
- static std::shared_ptr<DNSRecordContent> mastermake(uint16_t qtype, uint16_t qclass, const string& zone);
+ static std::shared_ptr<DNSRecordContent> make(const DNSRecord& dr, PacketReader& pr);
+ static std::shared_ptr<DNSRecordContent> make(const DNSRecord& dr, PacketReader& pr, uint16_t opcode);
+ static std::shared_ptr<DNSRecordContent> make(uint16_t qtype, uint16_t qclass, const string& zone);
static string upgradeContent(const DNSName& qname, const QType& qtype, const string& content);
virtual std::string getZoneRepresentation(bool noDot=false) const = 0;
- virtual ~DNSRecordContent() {}
+ virtual ~DNSRecordContent() = default;
virtual void toPacket(DNSPacketWriter& pw) const = 0;
// returns the wire format of the content, possibly including compressed pointers pointing to the owner name (unless canonic or lowerCase are set)
string serialize(const DNSName& qname, bool canonic=false, bool lowerCase=false) const
throw runtime_error("Unknown DNS type '"+name+"'");
}
- static const string NumberToType(uint16_t num, uint16_t classnum=1)
+ static const string NumberToType(uint16_t num, uint16_t classnum = QClass::IN)
{
auto iter = getT2Namemap().find(pair(classnum, num));
if(iter == getT2Namemap().end())
return iter->second;
}
+ /**
+ * \brief Return whether we have implemented a content representation for this type
+ */
+ static bool isRegisteredType(uint16_t rtype, uint16_t rclass = QClass::IN);
+
virtual uint16_t getType() const = 0;
protected:
#include "namespaces.hh"
PcapPacketReader::PcapPacketReader(const string& fname) : d_fname(fname)
{
- d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname.c_str(), "r"), fclose);
+ d_fp = pdns::UniqueFilePtr(fopen(fname.c_str(), "r"));
if (!d_fp) {
unixDie("Unable to open file " + fname);
}
PcapPacketWriter::PcapPacketWriter(const string& fname) : d_fname(fname)
{
- d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname.c_str(),"w"), fclose);
-
+ d_fp = pdns::openFileForWriting(fname, 0600, true, false);
if (!d_fp) {
unixDie("Unable to open file");
}
}
};
- PcapPacketReader(const string& fname);
+ PcapPacketReader(const string& fname);
template<typename T>
void checkedFread(T* ptr)
char *d_buffer;
size_t d_bufsize;
private:
- std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
+ pdns::UniqueFilePtr d_fp{nullptr};
string d_fname;
unsigned int d_skipMediaHeader;
};
class PcapPacketWriter
{
-public:
+public:
PcapPacketWriter(const string& fname, const PcapPacketReader& ppr);
PcapPacketWriter(const string& fname);
-
+
void write();
void setPPR(const PcapPacketReader& ppr) { d_ppr = &ppr; }
string d_fname;
const PcapPacketReader* d_ppr{nullptr};
- std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
+ pdns::UniqueFilePtr d_fp{nullptr};
bool d_first{true};
-};
+};
PcapPacketReader pr(argv[1]);
- auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(argv[2], "w"), fclose);
- if (!fp) {
- cerr<<"Error opening output file "<<argv[2]<<": "<<stringerror()<<endl;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): it's argv..
+ auto filePtr = pdns::openFileForWriting(argv[2], 0600, true, false);
+ if (!filePtr) {
+ auto error = errno;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): it's argv..
+ cerr<<"Error opening output file "<<argv[2]<<": "<<stringerror(error)<<endl;
exit(EXIT_FAILURE);
}
}
uint16_t mlen = htons(pbBuffer.length());
- fwrite(&mlen, 1, sizeof(mlen), fp.get());
- fwrite(pbBuffer.c_str(), 1, pbBuffer.length(), fp.get());
+ fwrite(&mlen, 1, sizeof(mlen), filePtr.get());
+ fwrite(pbBuffer.c_str(), 1, pbBuffer.length(), filePtr.get());
}
}
catch (const std::exception& e) {
#include "stubresolver.hh"
#include "arguments.hh"
#include "threadname.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
extern StatBag S;
-DNSProxy::DNSProxy(const string &remote): d_xor(dns_random_uint16())
+DNSProxy::DNSProxy(const string& remote) :
+ d_xor(dns_random_uint16())
{
- d_resanswers=S.getPointer("recursing-answers");
- d_resquestions=S.getPointer("recursing-questions");
- d_udpanswers=S.getPointer("udp-answers");
+ d_resanswers = S.getPointer("recursing-answers");
+ d_resquestions = S.getPointer("recursing-questions");
+ d_udpanswers = S.getPointer("udp-answers");
vector<string> addresses;
stringtok(addresses, remote, " ,\t");
d_remote = ComboAddress(addresses[0], 53);
- if((d_sock=socket(d_remote.sin4.sin_family, SOCK_DGRAM,0))<0) {
- throw PDNSException(string("socket: ")+stringerror());
+ if ((d_sock = socket(d_remote.sin4.sin_family, SOCK_DGRAM, 0)) < 0) {
+ throw PDNSException(string("socket: ") + stringerror());
}
ComboAddress local;
- if(d_remote.sin4.sin_family==AF_INET) {
+ if (d_remote.sin4.sin_family == AF_INET) {
local = ComboAddress("0.0.0.0");
}
else {
local = ComboAddress("::");
}
-
- unsigned int n=0;
- for(;n<10;n++) {
- local.sin4.sin_port = htons(10000+dns_random(50000));
-
- if(::bind(d_sock, (struct sockaddr *)&local, local.getSocklen()) >= 0)
+
+ unsigned int attempts = 0;
+ for (; attempts < 10; attempts++) {
+ local.sin4.sin_port = htons(10000 + dns_random(50000));
+
+ if (::bind(d_sock, (struct sockaddr*)&local, local.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
break;
+ }
}
- if(n==10) {
+ if (attempts == 10) {
closesocket(d_sock);
- d_sock=-1;
- throw PDNSException(string("binding dnsproxy socket: ")+stringerror());
+ d_sock = -1;
+ throw PDNSException(string("binding dnsproxy socket: ") + stringerror());
}
- if(connect(d_sock, (sockaddr *)&d_remote, d_remote.getSocklen())<0) {
- throw PDNSException("Unable to UDP connect to remote nameserver "+d_remote.toStringWithPort()+": "+stringerror());
+ if (connect(d_sock, (sockaddr*)&d_remote, d_remote.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+ throw PDNSException("Unable to UDP connect to remote nameserver " + d_remote.toStringWithPort() + ": " + stringerror());
}
- g_log<<Logger::Error<<"DNS Proxy launched, local port "<<ntohs(local.sin4.sin_port)<<", remote "<<d_remote.toStringWithPort()<<endl;
-}
+ g_log << Logger::Error << "DNS Proxy launched, local port " << ntohs(local.sin4.sin_port) << ", remote " << d_remote.toStringWithPort() << endl;
+}
void DNSProxy::go()
{
- std::thread t([this](){mainloop();});
- t.detach();
+ std::thread proxythread([this]() { mainloop(); });
+ proxythread.detach();
}
//! look up qname target with r->qtype, plonk it in the answer section of 'r' with name aname
-bool DNSProxy::completePacket(std::unique_ptr<DNSPacket>& r, const DNSName& target,const DNSName& aname, const uint8_t scopeMask)
+bool DNSProxy::completePacket(std::unique_ptr<DNSPacket>& reply, const DNSName& target, const DNSName& aname, const uint8_t scopeMask)
{
- if(r->d_tcp) {
- vector<DNSZoneRecord> ips;
- int ret1 = 0, ret2 = 0;
+ string ECSOptionStr;
- if(r->qtype == QType::A || r->qtype == QType::ANY)
- ret1 = stubDoResolve(target, QType::A, ips);
- if(r->qtype == QType::AAAA || r->qtype == QType::ANY)
- ret2 = stubDoResolve(target, QType::AAAA, ips);
+ if (reply->hasEDNSSubnet()) {
+ DLOG(g_log << "dnsproxy::completePacket: Parsed edns source: " << reply->d_eso.source.toString() << ", scope: " << reply->d_eso.scope.toString() << ", family = " << reply->d_eso.scope.getNetwork().sin4.sin_family << endl);
+ ECSOptionStr = makeEDNSSubnetOptsString(reply->d_eso);
+ DLOG(g_log << "from dnsproxy::completePacket: Creating ECS option string " << makeHexDump(ECSOptionStr) << endl);
+ }
- if(ret1 != RCode::NoError || ret2 != RCode::NoError) {
- g_log<<Logger::Error<<"Error resolving for "<<aname<<" ALIAS "<<target<<" over UDP, original query came in over TCP";
+ if (reply->d_tcp) {
+ vector<DNSZoneRecord> ips;
+ int ret1 = 0;
+ int ret2 = 0;
+ // rip out edns info here, pass it to the stubDoResolve
+ if (reply->qtype == QType::A || reply->qtype == QType::ANY) {
+ ret1 = stubDoResolve(target, QType::A, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr);
+ }
+ if (reply->qtype == QType::AAAA || reply->qtype == QType::ANY) {
+ ret2 = stubDoResolve(target, QType::AAAA, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr);
+ }
+
+ if (ret1 != RCode::NoError || ret2 != RCode::NoError) {
+ g_log << Logger::Error << "Error resolving for " << aname << " ALIAS " << target << " over UDP, original query came in over TCP";
if (ret1 != RCode::NoError) {
- g_log<<Logger::Error<<", A-record query returned "<<RCode::to_s(ret1);
+ g_log << Logger::Error << ", A-record query returned " << RCode::to_s(ret1);
}
if (ret2 != RCode::NoError) {
- g_log<<Logger::Error<<", AAAA-record query returned "<<RCode::to_s(ret2);
+ g_log << Logger::Error << ", AAAA-record query returned " << RCode::to_s(ret2);
}
- g_log<<Logger::Error<<", returning SERVFAIL"<<endl;
- r->clearRecords();
- r->setRcode(RCode::ServFail);
- } else {
- for (auto &ip : ips)
- {
+ g_log << Logger::Error << ", returning SERVFAIL" << endl;
+ reply->clearRecords();
+ reply->setRcode(RCode::ServFail);
+ }
+ else {
+ for (auto& ip : ips) { // NOLINT(readability-identifier-length)
ip.dr.d_name = aname;
- r->addRecord(std::move(ip));
+ reply->addRecord(std::move(ip));
}
}
- uint16_t len=htons(r->getString().length());
+ uint16_t len = htons(reply->getString().length());
string buffer((const char*)&len, 2);
- buffer.append(r->getString());
- writen2WithTimeout(r->getSocket(), buffer.c_str(), buffer.length(), timeval{::arg().asNum("tcp-idle-timeout"),0});
+ buffer.append(reply->getString());
+ writen2WithTimeout(reply->getSocket(), buffer.c_str(), buffer.length(), timeval{::arg().asNum("tcp-idle-timeout"), 0});
return true;
}
uint16_t id;
- uint16_t qtype = r->qtype.getCode();
+ uint16_t qtype = reply->qtype.getCode();
{
auto conntrack = d_conntrack.lock();
id = getID_locked(*conntrack);
ConntrackEntry ce;
- ce.id = r->d.id;
- ce.remote = r->d_remote;
- ce.outsock = r->getSocket();
- ce.created = time( nullptr );
- ce.qtype = r->qtype.getCode();
+ ce.id = reply->d.id;
+ ce.remote = reply->d_remote;
+ ce.outsock = reply->getSocket();
+ ce.created = time(nullptr);
+ ce.qtype = reply->qtype.getCode();
ce.qname = target;
- ce.anyLocal = r->d_anyLocal;
- ce.complete = std::move(r);
- ce.aname=aname;
+ ce.anyLocal = reply->d_anyLocal;
+ ce.complete = std::move(reply);
+ ce.aname = aname;
ce.anameScopeMask = scopeMask;
- (*conntrack)[id]=std::move(ce);
+ (*conntrack)[id] = std::move(ce);
}
vector<uint8_t> packet;
DNSPacketWriter pw(packet, target, qtype);
- pw.getHeader()->rd=true;
- pw.getHeader()->id=id ^ d_xor;
+ pw.getHeader()->rd = true;
+ pw.getHeader()->id = id ^ d_xor;
+ // Add EDNS Subnet if the client sent one - issue #5469
+ if (!ECSOptionStr.empty()) {
+ DLOG(g_log << "from dnsproxy::completePacket: adding ECS option string to packet options " << makeHexDump(ECSOptionStr) << endl);
+ DNSPacketWriter::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, ECSOptionStr);
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+ }
- if(send(d_sock,&packet[0], packet.size() , 0)<0) { // zoom
- g_log<<Logger::Error<<"Unable to send a packet to our recursing backend: "<<stringerror()<<endl;
+ if (send(d_sock, packet.data(), packet.size(), 0) < 0) { // zoom
+ g_log << Logger::Error << "Unable to send a packet to our recursing backend: " << stringerror() << endl;
}
return true;
-
}
-
/** This finds us an unused or stale ID. Does not actually clean the contents */
int DNSProxy::getID_locked(map_t& conntrack)
{
- map_t::iterator i;
- for(int n=0;;++n) {
- i=conntrack.find(n);
- if(i==conntrack.end()) {
+ map_t::iterator iter;
+ for (int n = 0;; ++n) { // NOLINT(readability-identifier-length)
+ iter = conntrack.find(n);
+ if (iter == conntrack.end()) {
return n;
}
- else if(i->second.created<time(nullptr)-60) {
- if(i->second.created) {
- g_log<<Logger::Warning<<"Recursive query for remote "<<
- i->second.remote.toStringWithPort()<<" with internal id "<<n<<
- " was not answered by backend within timeout, reusing id"<<endl;
- i->second.complete.reset();
- S.inc("recursion-unanswered");
+ if (iter->second.created < time(nullptr) - 60) {
+ if (iter->second.created != 0) {
+ g_log << Logger::Warning << "Recursive query for remote " << iter->second.remote.toStringWithPort() << " with internal id " << n << " was not answered by backend within timeout, reusing id" << endl;
+ iter->second.complete.reset();
+ S.inc("recursion-unanswered");
}
return n;
}
cmsgbuf_aligned cbuf;
ComboAddress fromaddr;
- for(;;) {
+ for (;;) {
socklen_t fromaddrSize = sizeof(fromaddr);
- len=recvfrom(d_sock, buffer, sizeof(buffer),0, (struct sockaddr*) &fromaddr, &fromaddrSize); // answer from our backend
- if(len<(ssize_t)sizeof(dnsheader)) {
- if(len<0)
- g_log<<Logger::Error<<"Error receiving packet from recursor backend: "<<stringerror()<<endl;
- else if(len==0)
- g_log<<Logger::Error<<"Error receiving packet from recursor backend, EOF"<<endl;
- else
- g_log<<Logger::Error<<"Short packet from recursor backend, "<<len<<" bytes"<<endl;
-
+ len = recvfrom(d_sock, &buffer[0], sizeof(buffer), 0, (struct sockaddr*)&fromaddr, &fromaddrSize); // answer from our backend NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+ if (len < (ssize_t)sizeof(dnsheader)) {
+ if (len < 0) {
+ g_log << Logger::Error << "Error receiving packet from recursor backend: " << stringerror() << endl;
+ }
+ else if (len == 0) {
+ g_log << Logger::Error << "Error receiving packet from recursor backend, EOF" << endl;
+ }
+ else {
+ g_log << Logger::Error << "Short packet from recursor backend, " << len << " bytes" << endl;
+ }
+
continue;
}
if (fromaddr != d_remote) {
- g_log<<Logger::Error<<"Got answer from unexpected host "<<fromaddr.toStringWithPort()<<" instead of our recursor backend "<<d_remote.toStringWithPort()<<endl;
+ g_log << Logger::Error << "Got answer from unexpected host " << fromaddr.toStringWithPort() << " instead of our recursor backend " << d_remote.toStringWithPort() << endl;
continue;
}
(*d_resanswers)++;
(*d_udpanswers)++;
- dnsheader d;
- memcpy(&d,buffer,sizeof(d));
+ dnsheader dHead{};
+ memcpy(&dHead, &buffer[0], sizeof(dHead));
{
auto conntrack = d_conntrack.lock();
#if BYTE_ORDER == BIG_ENDIAN
// this is needed because spoof ID down below does not respect the native byteorder
- d.id = ( 256 * (uint16_t)buffer[1] ) + (uint16_t)buffer[0];
+ d.id = (256 * (uint16_t)buffer[1]) + (uint16_t)buffer[0];
#endif
- map_t::iterator i=conntrack->find(d.id^d_xor);
- if(i==conntrack->end()) {
- g_log<<Logger::Error<<"Discarding untracked packet from recursor backend with id "<<(d.id^d_xor)<<
- ". Conntrack table size="<<conntrack->size()<<endl;
+ auto iter = conntrack->find(dHead.id ^ d_xor);
+ if (iter == conntrack->end()) {
+ g_log << Logger::Error << "Discarding untracked packet from recursor backend with id " << (dHead.id ^ d_xor) << ". Conntrack table size=" << conntrack->size() << endl;
continue;
}
- else if(i->second.created==0) {
- g_log<<Logger::Error<<"Received packet from recursor backend with id "<<(d.id^d_xor)<<" which is a duplicate"<<endl;
+ if (iter->second.created == 0) {
+ g_log << Logger::Error << "Received packet from recursor backend with id " << (dHead.id ^ d_xor) << " which is a duplicate" << endl;
continue;
}
-
- d.id=i->second.id;
- memcpy(buffer,&d,sizeof(d)); // commit spoofed id
- DNSPacket p(false),q(false);
- p.parse(buffer,(size_t)len);
- q.parse(buffer,(size_t)len);
+ dHead.id = iter->second.id;
+ memcpy(&buffer[0], &dHead, sizeof(dHead)); // commit spoofed id
- if(p.qtype.getCode() != i->second.qtype || p.qdomain != i->second.qname) {
- g_log<<Logger::Error<<"Discarding packet from recursor backend with id "<<(d.id^d_xor)<<
- ", qname or qtype mismatch ("<<p.qtype.getCode()<<" v " <<i->second.qtype<<", "<<p.qdomain<<" v "<<i->second.qname<<")"<<endl;
+ DNSPacket packet(false);
+ packet.parse(&buffer[0], (size_t)len);
+
+ if (packet.qtype.getCode() != iter->second.qtype || packet.qdomain != iter->second.qname) {
+ g_log << Logger::Error << "Discarding packet from recursor backend with id " << (dHead.id ^ d_xor) << ", qname or qtype mismatch (" << packet.qtype.getCode() << " v " << iter->second.qtype << ", " << packet.qdomain << " v " << iter->second.qname << ")" << endl;
continue;
}
/* Set up iov and msgh structures. */
memset(&msgh, 0, sizeof(struct msghdr));
string reply; // needs to be alive at time of sendmsg!
- MOADNSParser mdp(false, p.getString());
- // cerr<<"Got completion, "<<mdp.d_answers.size()<<" answers, rcode: "<<mdp.d_header.rcode<<endl;
+ MOADNSParser mdp(false, packet.getString());
+ // update the EDNS options with info from the resolver - issue #5469
+ // note that this relies on the ECS string encoder to use the source network, and only take the prefix length from scope
+ iter->second.complete->d_eso.scope = packet.d_eso.scope;
+ DLOG(g_log << "from dnsproxy::mainLoop: updated EDNS options from resolver EDNS source: " << iter->second.complete->d_eso.source.toString() << " EDNS scope: " << iter->second.complete->d_eso.scope.toString() << endl);
+
if (mdp.d_header.rcode == RCode::NoError) {
- for (auto& answer : mdp.d_answers) {
- // cerr<<"comp: "<<(int)j->first.d_place-1<<" "<<j->first.d_label<<" " << DNSRecordContent::NumberToType(j->first.d_type)<<" "<<j->first.d_content->getZoneRepresentation()<<endl;
- if(answer.first.d_place == DNSResourceRecord::ANSWER || (answer.first.d_place == DNSResourceRecord::AUTHORITY && answer.first.d_type == QType::SOA)) {
+ for (const auto& answer : mdp.d_answers) {
+ if (answer.first.d_place == DNSResourceRecord::ANSWER || (answer.first.d_place == DNSResourceRecord::AUTHORITY && answer.first.d_type == QType::SOA)) {
- if(answer.first.d_type == i->second.qtype || (i->second.qtype == QType::ANY && (answer.first.d_type == QType::A || answer.first.d_type == QType::AAAA))) {
+ if (answer.first.d_type == iter->second.qtype || (iter->second.qtype == QType::ANY && (answer.first.d_type == QType::A || answer.first.d_type == QType::AAAA))) {
DNSZoneRecord dzr;
- dzr.dr.d_name=i->second.aname;
+ dzr.dr.d_name = iter->second.aname;
dzr.dr.d_type = answer.first.d_type;
- dzr.dr.d_ttl=answer.first.d_ttl;
- dzr.dr.d_place= answer.first.d_place;
+ dzr.dr.d_ttl = answer.first.d_ttl;
+ dzr.dr.d_place = answer.first.d_place;
dzr.dr.setContent(answer.first.getContent());
- i->second.complete->addRecord(std::move(dzr));
+ iter->second.complete->addRecord(std::move(dzr));
}
}
}
- i->second.complete->setRcode(mdp.d_header.rcode);
- } else {
- g_log<<Logger::Error<<"Error resolving for "<<i->second.aname<<" ALIAS "<<i->second.qname<<" over UDP, "<<QType(i->second.qtype).toString()<<"-record query returned "<<RCode::to_s(mdp.d_header.rcode)<<", returning SERVFAIL"<<endl;
- i->second.complete->clearRecords();
- i->second.complete->setRcode(RCode::ServFail);
+
+ iter->second.complete->setRcode(mdp.d_header.rcode);
}
- reply=i->second.complete->getString();
+ else {
+ g_log << Logger::Error << "Error resolving for " << iter->second.aname << " ALIAS " << iter->second.qname << " over UDP, " << QType(iter->second.qtype).toString() << "-record query returned " << RCode::to_s(mdp.d_header.rcode) << ", returning SERVFAIL" << endl;
+ iter->second.complete->clearRecords();
+ iter->second.complete->setRcode(RCode::ServFail);
+ }
+ reply = iter->second.complete->getString();
iov.iov_base = (void*)reply.c_str();
iov.iov_len = reply.length();
- i->second.complete.reset();
+ iter->second.complete.reset();
msgh.msg_iov = &iov;
msgh.msg_iovlen = 1;
- msgh.msg_name = (struct sockaddr*)&i->second.remote;
- msgh.msg_namelen = i->second.remote.getSocklen();
- msgh.msg_control=nullptr;
+ msgh.msg_name = (struct sockaddr*)&iter->second.remote; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+ msgh.msg_namelen = iter->second.remote.getSocklen();
+ msgh.msg_control = nullptr;
- if(i->second.anyLocal) {
- addCMsgSrcAddr(&msgh, &cbuf, i->second.anyLocal.get_ptr(), 0);
+ if (iter->second.anyLocal) {
+ addCMsgSrcAddr(&msgh, &cbuf, iter->second.anyLocal.get_ptr(), 0);
}
- if(sendmsg(i->second.outsock, &msgh, 0) < 0) {
+ if (sendmsg(iter->second.outsock, &msgh, 0) < 0) {
int err = errno;
- g_log<<Logger::Warning<<"dnsproxy.cc: Error sending reply with sendmsg (socket="<<i->second.outsock<<"): "<<stringerror(err)<<endl;
+ g_log << Logger::Warning << "dnsproxy.cc: Error sending reply with sendmsg (socket=" << iter->second.outsock << "): " << stringerror(err) << endl;
}
- i->second.created=0;
+ iter->second.created = 0;
}
}
}
- catch(PDNSException &ae) {
- g_log<<Logger::Error<<"Fatal error in DNS proxy: "<<ae.reason<<endl;
+ catch (PDNSException& ae) {
+ g_log << Logger::Error << "Fatal error in DNS proxy: " << ae.reason << endl;
}
- catch(std::exception &e) {
- g_log<<Logger::Error<<"Communicator thread died because of STL error: "<<e.what()<<endl;
+ catch (std::exception& e) {
+ g_log << Logger::Error << "Communicator thread died because of STL error: " << e.what() << endl;
}
- catch( ... )
- {
+ catch (...) {
g_log << Logger::Error << "Caught unknown exception." << endl;
}
- g_log<<Logger::Error<<"Exiting because DNS proxy failed"<<endl;
+ g_log << Logger::Error << "Exiting because DNS proxy failed" << endl;
_exit(1);
}
-DNSProxy::~DNSProxy() {
- if (d_sock>-1) {
+DNSProxy::~DNSProxy()
+{
+ if (d_sock > -1) {
try {
closesocket(d_sock);
}
- catch(const PDNSException& e) {
+ catch (const PDNSException& e) {
}
}
- d_sock=-1;
+ d_sock = -1;
}
Consists of a thread receiving packets back from the backend and retransmitting them to the original client.
-Furthermore, it provides a member function that reports the packet to the connection tracker and actually sends it out.
+Furthermore, it provides a member function that reports the packet to the connection tracker and actually sends it out.
The sending happens from a source port that is determined by the constructor, but IS random. Furthermore, the ID is XOR-ed with a random value
to make sure outside parties can't spoof us.
class DNSProxy
{
public:
- DNSProxy(const string &ip); //!< creates socket
+ DNSProxy(const string& remote); //!< creates socket
~DNSProxy(); //<! dtor for DNSProxy
void go(); //!< launches the actual thread
- bool completePacket(std::unique_ptr<DNSPacket>& r, const DNSName& target,const DNSName& aname, const uint8_t scopeMask);
+ bool completePacket(std::unique_ptr<DNSPacket>& reply, const DNSName& target, const DNSName& aname, uint8_t scopeMask);
- void mainloop(); //!< this is the main loop that receives reply packets and sends them out again
- bool recurseFor(DNSPacket* p);
+ void mainloop(); //!< this is the main loop that receives reply packets and sends them out again
private:
struct ConntrackEntry
{
int outsock;
};
- typedef map<int,ConntrackEntry> map_t;
+ using map_t = map<int, ConntrackEntry>;
// Data
ComboAddress d_remote;
int d_sock;
const uint16_t d_xor;
- int getID_locked(map_t&);
+ static int getID_locked(map_t&);
};
conv.xfrHexBlob(d_cert, true);
)
-DSRecordContent::DSRecordContent() {}
+DSRecordContent::DSRecordContent() = default;
boilerplate_conv(DS,
conv.xfr16BitInt(d_tag);
conv.xfr8BitInt(d_algorithm);
conv.xfrHexBlob(d_digest, true); // keep reading across spaces
)
-CDSRecordContent::CDSRecordContent() {}
+CDSRecordContent::CDSRecordContent() = default;
boilerplate_conv(CDS,
conv.xfr16BitInt(d_tag);
conv.xfr8BitInt(d_algorithm);
conv.xfrHexBlob(d_digest, true); // keep reading across spaces
)
-DLVRecordContent::DLVRecordContent() {}
+DLVRecordContent::DLVRecordContent() = default;
boilerplate_conv(DLV,
conv.xfr16BitInt(d_tag);
conv.xfr8BitInt(d_algorithm);
conv.xfrBlob(d_signature);
)
-RRSIGRecordContent::RRSIGRecordContent() {}
+RRSIGRecordContent::RRSIGRecordContent() = default;
boilerplate_conv(DNSKEY,
conv.xfr16BitInt(d_flags);
conv.xfr8BitInt(d_algorithm);
conv.xfrBlob(d_key);
)
-DNSKEYRecordContent::DNSKEYRecordContent() {}
+DNSKEYRecordContent::DNSKEYRecordContent() = default;
boilerplate_conv(CDNSKEY,
conv.xfr16BitInt(d_flags);
conv.xfr8BitInt(d_algorithm);
conv.xfrBlob(d_key);
)
-CDNSKEYRecordContent::CDNSKEYRecordContent() {}
+CDNSKEYRecordContent::CDNSKEYRecordContent() = default;
boilerplate_conv(RKEY,
conv.xfr16BitInt(d_flags);
conv.xfr8BitInt(d_algorithm);
conv.xfrBlob(d_key);
)
-RKEYRecordContent::RKEYRecordContent() {}
+RKEYRecordContent::RKEYRecordContent() = default;
boilerplate_conv(NID,
conv.xfr16BitInt(d_preference);
{
public:
includeboilerplate(TSIG)
- TSIGRecordContent() {}
+ TSIGRecordContent() = default;
uint16_t d_origID{0};
uint16_t d_fudge{0};
class OPTRecordContent : public DNSRecordContent
{
public:
- OPTRecordContent(){}
+ OPTRecordContent() = default;
includeboilerplate(OPT)
void getData(vector<pair<uint16_t, string> > &opts) const;
private:
return *this;
}
- NSECBitmap(NSECBitmap&& rhs): d_bitset(std::move(rhs.d_bitset)), d_set(std::move(rhs.d_set))
+ NSECBitmap(NSECBitmap&& rhs) noexcept :
+ d_bitset(std::move(rhs.d_bitset)), d_set(std::move(rhs.d_set))
{
}
bool isSet(uint16_t type) const
class NSECRecordContent : public DNSRecordContent
{
public:
- static void report(void);
- NSECRecordContent()
- {}
+ static void report();
+ NSECRecordContent() = default;
NSECRecordContent(const string& content, const DNSName& zone=DNSName());
static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
class NSEC3RecordContent : public DNSRecordContent
{
public:
- static void report(void);
- NSEC3RecordContent()
- {}
+ static void report();
+ NSEC3RecordContent() = default;
NSEC3RecordContent(const string& content, const DNSName& zone=DNSName());
static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
class CSYNCRecordContent : public DNSRecordContent
{
public:
- static void report(void);
- CSYNCRecordContent()
- {}
+ static void report();
+ CSYNCRecordContent() = default;
CSYNCRecordContent(const string& content, const DNSName& zone=DNSName());
static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
class NSEC3PARAMRecordContent : public DNSRecordContent
{
public:
- static void report(void);
- NSEC3PARAMRecordContent()
- {}
+ static void report();
+ NSEC3PARAMRecordContent() = default;
NSEC3PARAMRecordContent(const string& content, const DNSName& zone=DNSName());
static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
class LOCRecordContent : public DNSRecordContent
{
public:
- static void report(void);
- LOCRecordContent()
- {}
+ static void report();
+ LOCRecordContent() = default;
LOCRecordContent(const string& content, const string& zone="");
static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
class EUI48RecordContent : public DNSRecordContent
{
public:
- EUI48RecordContent() {};
- static void report(void);
+ EUI48RecordContent() = default;
+ static void report();
static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
static std::shared_ptr<DNSRecordContent> make(const string& zone); // FIXME400: DNSName& zone?
string getZoneRepresentation(bool noDot=false) const override;
class EUI64RecordContent : public DNSRecordContent
{
public:
- EUI64RecordContent() {};
- static void report(void);
+ EUI64RecordContent() = default;
+ static void report();
static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
static std::shared_ptr<DNSRecordContent> make(const string& zone); // FIXME400: DNSName& zone?
string getZoneRepresentation(bool noDot=false) const override;
class APLRecordContent : public DNSRecordContent
{
public:
- APLRecordContent() {};
+ APLRecordContent() = default;
includeboilerplate(APL)
private:
std::vector<APLRDataElement> aplrdata;
int d_assignedID;
MOADNSParser::answers_t d_origAnswers, d_newAnswers;
int d_origRcode, d_newRcode;
- struct timeval d_resentTime;
+ struct timeval d_resentTime{};
bool d_norecursionavailable;
bool d_origlate, d_newlate;
};
static void receiveFromReference()
try
{
- string packet;
+ PacketBuffer packet;
ComboAddress remote;
int res=waitForData(s_socket->getHandle(), g_timeoutMsec/1000, 1000*(g_timeoutMsec%1000));
if(res < 0 || res==0)
return;
- while(s_socket->recvFromAsync(packet)) {
+ while (s_socket->recvFromAsync(packet, remote)) {
try {
s_weanswers++;
- MOADNSParser mdp(false, packet.c_str(), packet.length());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ MOADNSParser mdp(false, reinterpret_cast<const char*>(packet.data()), packet.size());
if(!mdp.d_header.qr) {
cout<<"Received a question from our reference nameserver!"<<endl;
continue;
static bool g_rdSelector;
static uint16_t g_pcapDnsPort;
-static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, int stamp, bool usePCAPSourceIP)
+static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, int stamp, [[maybe_unused]] bool usePCAPSourceIP)
{
bool sent=false;
if (pr.d_len <= sizeof(dnsheader)) {
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#if HAVE_BOOST_GE_148
+#ifdef HAVE_BOOST_GE_148
#include "histog.hh"
#endif
("rd", po::value<bool>(), "If set to true, only process RD packets, to false only non-RD, unset: both")
("ipv4", po::value<bool>()->default_value(true), "Process IPv4 packets")
("ipv6", po::value<bool>()->default_value(true), "Process IPv6 packets")
-#if HAVE_BOOST_GE_148
+#ifdef HAVE_BOOST_GE_148
("log-histogram", "Write a log-histogram to file 'log-histogram'")
("full-histogram", po::value<double>(), "Write a log-histogram to file 'full-histogram' with this millisecond bin size")
#endif
("filter-name,f", po::value<string>(), "Do statistics only for queries within this domain")
("load-stats,l", po::value<string>()->default_value(""), "if set, emit per-second load statistics (questions, answers, outstanding)")
("no-servfail-stats", "Don't include servfails in response time stats")
+ ("port", po::value<uint16_t>()->default_value(0), "The source and destination port to consider. Default is looking at packets from and to ports 53 and 5300")
("servfail-tree", "Figure out subtrees that generate servfails")
("stats-dir", po::value<string>()->default_value("."), "Directory where statistics will be saved")
("write-failures,w", po::value<string>()->default_value(""), "if set, write weird packets to this PCAP file")
bool doIPv6 = g_vm["ipv6"].as<bool>();
bool doServFailTree = g_vm.count("servfail-tree");
bool noservfailstats = g_vm.count("no-servfail-stats");
- int dnserrors=0, parsefail=0;
+ int dnserrors = 0;
+ int parsefail = 0;
typedef map<uint32_t,uint32_t> cumul_t;
cumul_t cumul;
- unsigned int untracked=0, errorresult=0, nonRDQueries=0, queries=0;
- unsigned int ipv4DNSPackets=0, ipv6DNSPackets=0, fragmented=0, rdNonRAAnswers=0;
- unsigned int answers=0, nonDNSIP=0, rdFilterMismatch=0;
- unsigned int dnssecOK=0, edns=0;
- unsigned int dnssecCD=0, dnssecAD=0;
- unsigned int reuses=0;
+ unsigned int untracked = 0;
+ unsigned int nonRDQueries = 0;
+ unsigned int queries = 0;
+ unsigned int ipv4DNSPackets = 0;
+ unsigned int ipv6DNSPackets = 0;
+ unsigned int fragmented = 0;
+ unsigned int rdNonRAAnswers = 0;
+ unsigned int answers = 0;
+ unsigned int nonDNSIP = 0;
+ unsigned int rdFilterMismatch = 0;
+ unsigned int dnssecOK = 0;
+ unsigned int edns = 0;
+ unsigned int dnssecCD = 0;
+ unsigned int dnssecAD = 0;
+ unsigned int reuses = 0;
typedef map<uint16_t,uint32_t> rcodes_t;
rcodes_t rcodes;
std::unordered_set<ComboAddress, ComboAddress::addressOnlyHash> requestors, recipients, rdnonra;
typedef vector<pair<time_t, LiveCounts> > pcounts_t;
pcounts_t pcounts;
+ const uint16_t port = g_vm["port"].as<uint16_t>();
OPTRecordContent::report();
for(unsigned int fno=0; fno < files.size(); ++fno) {
EDNSOpts edo;
while(pr.getUDPPacket()) {
- if((ntohs(pr.d_udp->uh_dport)==5300 || ntohs(pr.d_udp->uh_sport)==5300 ||
- ntohs(pr.d_udp->uh_dport)==53 || ntohs(pr.d_udp->uh_sport)==53) &&
- pr.d_len > 12) {
- try {
- if((pr.d_ip->ip_v == 4 && !doIPv4) || (pr.d_ip->ip_v == 6 && !doIPv6))
- continue;
- if(pr.d_ip->ip_v == 4) {
- uint16_t frag = ntohs(pr.d_ip->ip_off);
- if((frag & IP_MF) || (frag & IP_OFFMASK)) { // more fragments or IS a fragment
- fragmented++;
- continue;
- }
- }
- uint16_t qtype;
- DNSName qname((const char*)pr.d_payload, pr.d_len, 12, false, &qtype);
- struct dnsheader header;
- memcpy(&header, (struct dnsheader*)pr.d_payload, 12);
-
- if(haveRDFilter && header.rd != rdFilter) {
- rdFilterMismatch++;
- continue;
- }
-
- if(!filtername.empty() && !qname.isPartOf(filtername)) {
- nameMismatch++;
+ if (pr.d_len <= 12) {
+ // non-DNS ip
+ nonDNSIP++;
+ continue;
+ }
+ if (port > 0 &&
+ (ntohs(pr.d_udp->uh_dport) != port && ntohs(pr.d_udp->uh_sport) != port)) {
+ // non-DNS ip
+ nonDNSIP++;
+ continue;
+ }
+
+ if (port == 0 &&
+ (ntohs(pr.d_udp->uh_dport) != 5300 && ntohs(pr.d_udp->uh_sport) != 5300 &&
+ ntohs(pr.d_udp->uh_dport) != 53 && ntohs(pr.d_udp->uh_sport) != 53)) {
+ // non-DNS ip
+ nonDNSIP++;
+ continue;
+ }
+
+ try {
+ if ((pr.d_ip->ip_v == 4 && !doIPv4) || (pr.d_ip->ip_v == 6 && !doIPv6)) {
+ continue;
+ }
+
+ if (pr.d_ip->ip_v == 4) {
+ uint16_t frag = ntohs(pr.d_ip->ip_off);
+ if((frag & IP_MF) || (frag & IP_OFFMASK)) { // more fragments or IS a fragment
+ fragmented++;
continue;
}
+ }
+ uint16_t qtype;
+ DNSName qname((const char*)pr.d_payload, pr.d_len, 12, false, &qtype);
+ struct dnsheader header;
+ memcpy(&header, (struct dnsheader*)pr.d_payload, 12);
+
+ if(haveRDFilter && header.rd != rdFilter) {
+ rdFilterMismatch++;
+ continue;
+ }
+
+ if(!filtername.empty() && !qname.isPartOf(filtername)) {
+ nameMismatch++;
+ continue;
+ }
+
+ if(!header.qr) {
+ uint16_t udpsize, z;
+ if(getEDNSUDPPayloadSizeAndZ((const char*)pr.d_payload, pr.d_len, &udpsize, &z)) {
+ edns++;
+ if(z & EDNSOpts::DNSSECOK)
+ dnssecOK++;
+ if(header.cd)
+ dnssecCD++;
+ if(header.ad)
+ dnssecAD++;
+ }
+ }
+
+ if(pr.d_ip->ip_v == 4)
+ ++ipv4DNSPackets;
+ else
+ ++ipv6DNSPackets;
- if(!header.qr) {
- uint16_t udpsize, z;
- if(getEDNSUDPPayloadSizeAndZ((const char*)pr.d_payload, pr.d_len, &udpsize, &z)) {
- edns++;
- if(z & EDNSOpts::DNSSECOK)
- dnssecOK++;
- if(header.cd)
- dnssecCD++;
- if(header.ad)
- dnssecAD++;
+ if(pr.d_pheader.ts.tv_sec != lastsec) {
+ LiveCounts lc;
+ if(lastsec) {
+ lc.questions = queries;
+ lc.answers = answers;
+ lc.outstanding = liveQuestions();
+
+ LiveCounts diff = lc - lastcounts;
+ pcounts.emplace_back(pr.d_pheader.ts.tv_sec, diff);
+ }
+ lastsec = pr.d_pheader.ts.tv_sec;
+ lastcounts = lc;
+ }
+
+ if(lowestTime) { lowestTime = min((time_t)lowestTime, (time_t)pr.d_pheader.ts.tv_sec); }
+ else { lowestTime = pr.d_pheader.ts.tv_sec; }
+ highestTime=max((time_t)highestTime, (time_t)pr.d_pheader.ts.tv_sec);
+
+ QuestionIdentifier qi=QuestionIdentifier::create(pr.getSource(), pr.getDest(), header, qname, qtype);
+
+ if(!header.qr) { // question
+ // cout<<"Query "<<qi<<endl;
+ if(!header.rd)
+ nonRDQueries++;
+ queries++;
+
+ ComboAddress rem = pr.getSource();
+ rem.sin4.sin_port=0;
+ requestors.insert(rem);
+
+ QuestionData& qd=statmap[qi];
+
+ if(!qd.d_firstquestiontime.tv_sec)
+ qd.d_firstquestiontime=pr.d_pheader.ts;
+ else {
+ auto delta=makeFloat(pr.d_pheader.ts - qd.d_firstquestiontime);
+ // cout<<"Reuse of "<<qi<<", delta t="<<delta<<", count="<<qd.d_qcount<<endl;
+ if(delta > 2.0) {
+ // cout<<"Resetting old entry for "<<qi<<", too old"<<endl;
+ qd.d_qcount=0;
+ qd.d_answercount=0;
+ qd.d_firstquestiontime=pr.d_pheader.ts;
}
}
+ if(qd.d_qcount++)
+ reuses++;
+ }
+ else { // answer
+ // cout<<"Response "<<qi<<endl;
+ rcodes[header.rcode]++;
+ answers++;
+ if(header.rd && !header.ra) {
+ rdNonRAAnswers++;
+ rdnonra.insert(pr.getDest());
+ }
- if(pr.d_ip->ip_v == 4)
- ++ipv4DNSPackets;
- else
- ++ipv6DNSPackets;
+ if(header.ra) {
+ ComboAddress rem = pr.getDest();
+ rem.sin4.sin_port=0;
+ recipients.insert(rem);
+ }
- if(pr.d_pheader.ts.tv_sec != lastsec) {
- LiveCounts lc;
- if(lastsec) {
- lc.questions = queries;
- lc.answers = answers;
- lc.outstanding = liveQuestions();
+ QuestionData& qd=statmap[qi];
+ if(!qd.d_qcount) {
+ // cout<<"Untracked answer: "<<qi<<endl;
+ untracked++;
+ }
- LiveCounts diff = lc - lastcounts;
- pcounts.emplace_back(pr.d_pheader.ts.tv_sec, diff);
- }
- lastsec = pr.d_pheader.ts.tv_sec;
- lastcounts = lc;
+ qd.d_answercount++;
+
+ if(qd.d_qcount) {
+ uint32_t usecs= (pr.d_pheader.ts.tv_sec - qd.d_firstquestiontime.tv_sec) * 1000000 +
+ (pr.d_pheader.ts.tv_usec - qd.d_firstquestiontime.tv_usec) ;
+
+ // cout<<"Usecs for "<<qi<<": "<<usecs<<endl;
+ if(!noservfailstats || header.rcode != 2)
+ cumul[usecs]++;
+
+ ComboAddress rem = pr.getDest();
+ rem.sin4.sin_port=0;
+
+ if(doServFailTree)
+ root.submit(qname, header.rcode, pr.d_len, false, rem);
}
- if(lowestTime) { lowestTime = min((time_t)lowestTime, (time_t)pr.d_pheader.ts.tv_sec); }
- else { lowestTime = pr.d_pheader.ts.tv_sec; }
- highestTime=max((time_t)highestTime, (time_t)pr.d_pheader.ts.tv_sec);
-
- QuestionIdentifier qi=QuestionIdentifier::create(pr.getSource(), pr.getDest(), header, qname, qtype);
-
- if(!header.qr) { // question
- // cout<<"Query "<<qi<<endl;
- if(!header.rd)
- nonRDQueries++;
- queries++;
-
- ComboAddress rem = pr.getSource();
- rem.sin4.sin_port=0;
- requestors.insert(rem);
-
- QuestionData& qd=statmap[qi];
-
- if(!qd.d_firstquestiontime.tv_sec)
- qd.d_firstquestiontime=pr.d_pheader.ts;
- else {
- auto delta=makeFloat(pr.d_pheader.ts - qd.d_firstquestiontime);
- // cout<<"Reuse of "<<qi<<", delta t="<<delta<<", count="<<qd.d_qcount<<endl;
- if(delta > 2.0) {
- // cout<<"Resetting old entry for "<<qi<<", too old"<<endl;
- qd.d_qcount=0;
- qd.d_answercount=0;
- qd.d_firstquestiontime=pr.d_pheader.ts;
- }
- }
- if(qd.d_qcount++)
- reuses++;
- }
- else { // answer
- // cout<<"Response "<<qi<<endl;
- rcodes[header.rcode]++;
- answers++;
- if(header.rd && !header.ra) {
- rdNonRAAnswers++;
- rdnonra.insert(pr.getDest());
- }
-
- if(header.ra) {
- ComboAddress rem = pr.getDest();
- rem.sin4.sin_port=0;
- recipients.insert(rem);
- }
-
- QuestionData& qd=statmap[qi];
- if(!qd.d_qcount) {
- // cout<<"Untracked answer: "<<qi<<endl;
- untracked++;
- }
-
- qd.d_answercount++;
-
- if(qd.d_qcount) {
- uint32_t usecs= (pr.d_pheader.ts.tv_sec - qd.d_firstquestiontime.tv_sec) * 1000000 +
- (pr.d_pheader.ts.tv_usec - qd.d_firstquestiontime.tv_usec) ;
-
- // cout<<"Usecs for "<<qi<<": "<<usecs<<endl;
- if(!noservfailstats || header.rcode != 2)
- cumul[usecs]++;
-
- if(header.rcode != 0 && header.rcode!=3)
- errorresult++;
- ComboAddress rem = pr.getDest();
- rem.sin4.sin_port=0;
-
- if(doServFailTree)
- root.submit(qname, header.rcode, pr.d_len, false, rem);
- }
-
- if(!qd.d_qcount || qd.d_qcount == qd.d_answercount) {
- // cout<<"Clearing state for "<<qi<<endl<<endl;
- statmap.erase(qi);
- }
- else {
- // cout<<"State for qi remains open, qcount="<<qd.d_qcount<<", answercount="<<qd.d_answercount<<endl;
- }
- }
- }
- catch(std::exception& e) {
- if(verbose)
- cout<<"error parsing packet: "<<e.what()<<endl;
-
- if(pw)
- pw->write();
- parsefail++;
- continue;
- }
+ if(!qd.d_qcount || qd.d_qcount == qd.d_answercount) {
+ // cout<<"Clearing state for "<<qi<<endl<<endl;
+ statmap.erase(qi);
+ }
+ else {
+ // cout<<"State for qi remains open, qcount="<<qd.d_qcount<<", answercount="<<qd.d_answercount<<endl;
+ }
+ }
}
- else { // non-DNS ip
- nonDNSIP++;
+ catch(std::exception& e) {
+ if(verbose)
+ cout<<"error parsing packet: "<<e.what()<<endl;
+
+ if(pw)
+ pw->write();
+ parsefail++;
+ continue;
}
}
- cout<<"PCAP contained "<<pr.d_correctpackets<<" correct packets, "<<pr.d_runts<<" runts, "<< pr.d_oversized<<" oversize, "<<pr.d_nonetheripudp<<" non-UDP.\n";
+ cout<<"PCAP contained "<<pr.d_correctpackets<<" correct packets, "<<pr.d_runts<<" runts, "<< pr.d_oversized<<" oversize, "<<pr.d_nonetheripudp<<" non-UDP.\n";
}
/*
cout.precision(4);
sum=0;
-#if HAVE_BOOST_GE_148
+#ifdef HAVE_BOOST_GE_148
if(g_vm.count("log-histogram")) {
string fname = g_vm["stats-dir"].as<string>()+"/log-histogram";
ofstream loglog(fname);
std::unique_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromISCFile(DNSKEYRecordContent& drc, const char* fname)
{
string sline, isc;
- auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname, "r"), fclose);
- if(!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fopen(fname, "r"));
+ if(!filePtr) {
throw runtime_error("Unable to read file '"+string(fname)+"' for generating DNS Private Key");
}
- while(stringfgets(fp.get(), sline)) {
+ while(stringfgets(filePtr.get(), sline)) {
isc += sline;
}
- fp.reset();
+ filePtr.reset();
auto dke = makeFromISCString(drc, isc);
auto checkKeyErrors = std::vector<std::string>{};
return ret;
}
+string DNSCryptoKeyEngine::listSupportedAlgoNames()
+{
+ set<unsigned int> algos;
+ auto pairs = DNSCryptoKeyEngine::listAllAlgosWithBackend();
+ for (const auto& pair : pairs) {
+ algos.insert(pair.first);
+ }
+ string ret;
+ bool first = true;
+ for (auto algo : algos) {
+ if (!first) {
+ ret.append(" ");
+ }
+ else {
+ first = false;
+ }
+ ret.append(DNSSECKeeper::algorithm2name(algo));
+ if (isAlgorithmSwitchedOff(algo)) {
+ ret.append("(disabled)");
+ }
+ }
+ ret.append("\n");
+ return ret;
+}
+
void DNSCryptoKeyEngine::report(unsigned int algo, maker_t* maker, bool fallback)
{
getAllMakers()[algo].push_back(maker);
return ret;
}
+static map<string, string> ISCStringtoMap(const string& argStr)
+{
+ unsigned int algorithm = 0;
+ string sline;
+ string key;
+ string value;
+ string raw;
+ std::istringstream str(argStr);
+ map<string, string> stormap;
+
+ while(std::getline(str, sline)) {
+ std::tie(key,value)=splitField(sline, ':');
+ boost::trim(value);
+ if(pdns_iequals(key,"algorithm")) {
+ pdns::checked_stoi_into(algorithm, value);
+ stormap["algorithm"] = std::to_string(algorithm);
+ continue;
+ }
+ if (pdns_iequals(key,"pin")) {
+ stormap["pin"] = value;
+ continue;
+ }
+ if (pdns_iequals(key,"engine")) {
+ stormap["engine"] = value;
+ continue;
+ }
+ if (pdns_iequals(key,"slot")) {
+ int slot = std::stoi(value);
+ stormap["slot"]=std::to_string(slot);
+ continue;
+ }
+ if (pdns_iequals(key,"label")) {
+ stormap["label"] = value;
+ continue;
+ }
+ if(pdns_iequals(key, "Private-key-format")) {
+ continue;
+ }
+ raw.clear();
+ B64Decode(value, raw);
+ stormap[toLower(key)] = raw;
+ }
+ return stormap;
+}
+
+bool DNSCryptoKeyEngine::testVerify(unsigned int algo, maker_t* verifier)
+{
+ const string message("Hi! How is life?");
+ const string pubkey5 = "AwEAAe2srzo8UfPx5WwoRXTRdo0H8U4iYW6qneronwKlRtXrpOqgZWPtYGVZl1Q7JXqbxxH9aVK5iK6aYOVfxbwwGHejaY0NraqrxL60F5FhHGHg+zox1en8kEX2TcQHxoZaiK1iUgPkMrHJlX5yI5+p2V4qap5VPQsR/WfeFVudNsBEF/XRvg0Exh65fPI/e8sYNgAiflzdN9/5RM644r6viBdieuwUNwEV2HPizCBMssYzx2F29CqNseToqCKQlj1tghuGAsiiSKeosfDLlRPDe/uxtij0wqe0FNybj1oL3OG8Lq3xp8yXIG4CF59xmRDKdnGDmVycKzUWkVOZpesCsUU=";
+ const string sig5 = "nMnMakbQiiCKIYsEiv4R75+8wvjQav2LPGIKucbqUZUz5sy1ovc2Pp7JVcOuyVyzQu5XH+CetDnTlqiEJWFHNU1jqEwwFK83GVOLABtvXSOvgmGwZGnHOouAchkrzgSSBoEh3+UUN3OsFZA21q6TZVRJBNBm7Ch/PxqSBkFS46ko/qLAUJ1p7/ymzwGNhuOfguHO3dAJ+LgcrNGLZQFDJ1aqT3kZ7LtXX2CQdd7EXgUs6VkE4Z3JN1RmPTk8kAJdZ4JLUR6lgu1dRlSPLGzqv+5d1yI7+h+B0LFNuDdQblDlBstO3LEs1KSaQld+TqVExpjj87oEg6wL/G/XOGabmQ==";
+
+ const string pubkey7 = "AwEAAc4n7xPG6yJe6YAsg6oQ+7YjbL7wuDLCP4juOSaDsst2Mehc5eYdT7xJT2H9foTIq7ABkkp8Er1Bh6gDzB/0xvArARdH6DS3P5pUP6w5Zoz4Gu79y3pP6IsR3ZyhiQRSnht1ElnIGZzb1zpi7Y4Y8LZ18NYN2qdLasXx/h6hpRjdcF1s7svZKvfJdvCSgDHHD/JFtDGSOn6qt6i5UFSrObxMUMWbxfOsnqr/eXUQcF/aePdqDXO47yDaSH8sFZoglgvEDiOIkky9DV5VKamvVW8anxE5Vv7y4EPpZKXB3CgUW+NvaoasdgYPFmGM4EcnXh2EFFnSPDL6iwDubiL7s2k=";
+ const string sig7 = "B04Oqmh/nF6BybBGsInauTXH6nlW3VhT2PeSzXVaxQ42QsbbXUgIKuzp2/R7diiEBzbbQ3Eg5vtHOKfEQDkArmOR1oU6yIkyrKHsJkpCvclCyaFiJXrwxkH+A2y8vB+loeDMJKJVwjn7fH9zwBI3Mk7SFuOgYXgzBUNhb5DeQ9RzRbxMcpSc8Cgtjn+QpmTNgL6olpBNsStYz9bSLXBk1EGhmZeBYhliw/2Fse75OoRxIuufKiN6sAD5bKQxp73QQUU+yunVuSeHJizNct8b4f9RXFe49wtZWt5rB0oYXG6zUv0Dq7xJHpUq6v1eB2wf2NucftCKwWu18r4TxkVC5A==";
+
+ string b64pubkey;
+ string b64sig;
+ switch (algo) {
+ case DNSSECKeeper::RSASHA1:
+ b64pubkey = pubkey5;
+ b64sig = sig5;
+ break;
+ case DNSSECKeeper::RSASHA1NSEC3SHA1:
+ b64pubkey = pubkey7;
+ b64sig = sig7;
+ break;
+ default:
+ throw runtime_error("Verification of verifier called for unimplemented case");
+ }
+
+ string pubkey;
+ string sig;
+ B64Decode(b64pubkey, pubkey);
+ B64Decode(b64sig, sig);
+ auto dckeVerify = verifier(algo);
+ dckeVerify->fromPublicKeyString(pubkey);
+
+ auto ret = dckeVerify->verify(message, sig);
+ return ret;
+}
+
+bool DNSCryptoKeyEngine::verifyOne(unsigned int algo)
+{
+ const auto& makers = getAllMakers();
+ auto iter = makers.find(algo);
+ // No algo found
+ if (iter == makers.cend()) {
+ return false;
+ }
+ // Algo found, but maker empty? Should not happen
+ if (iter->second.empty()) {
+ return false;
+ }
+ // Check that all maker->verify return true
+ return std::all_of(iter->second.begin(), iter->second.end(), [algo](maker_t* verifier) {
+ try {
+ if (!testVerify(algo, verifier)) {
+ return false;
+ }
+ }
+ catch (std::exception& e) {
+ return false;
+ }
+ return true;
+ });
+}
+
void DNSCryptoKeyEngine::testMakers(unsigned int algo, maker_t* creator, maker_t* signer, maker_t* verifier)
{
auto dckeCreate = creator(algo);
cout<<"("<<dckeCreate->getBits()<<" bits) ";
unsigned int udiffCreate = dt.udiff() / 100;
- { // FIXME: this block copy/pasted from makeFromISCString
+ {
DNSKEYRecordContent dkrc;
- unsigned int algorithm = 0;
- string sline, key, value, raw;
- std::istringstream str(dckeCreate->convertToISC());
- map<string, string> stormap;
-
- while(std::getline(str, sline)) {
- std::tie(key,value)=splitField(sline, ':');
- boost::trim(value);
- if(pdns_iequals(key,"algorithm")) {
- pdns::checked_stoi_into(algorithm, value);
- stormap["algorithm"]=std::to_string(algorithm);
- continue;
- } else if (pdns_iequals(key,"pin")) {
- stormap["pin"]=value;
- continue;
- } else if (pdns_iequals(key,"engine")) {
- stormap["engine"]=value;
- continue;
- } else if (pdns_iequals(key,"slot")) {
- int slot = std::stoi(value);
- stormap["slot"]=std::to_string(slot);
- continue;
- } else if (pdns_iequals(key,"label")) {
- stormap["label"]=value;
- continue;
- }
- else if(pdns_iequals(key, "Private-key-format"))
- continue;
- raw.clear();
- B64Decode(value, raw);
- stormap[toLower(key)]=raw;
- }
+ auto stormap = ISCStringtoMap(dckeCreate->convertToISC());
+
dckeSign->fromISCMap(dkrc, stormap);
if(!dckeSign->checkKey()) {
throw runtime_error("Verification of key with creator "+dckeCreate->getName()+" with signer "+dckeSign->getName()+" and verifier "+dckeVerify->getName()+" failed");
if (rrsig_labels < fqdn_labels) {
DNSName choppedQname(qname);
- while (choppedQname.countLabels() > rrsig_labels)
+ while (choppedQname.countLabels() > rrsig_labels) {
choppedQname.chopOff();
+ }
nameToHash = "\x01*" + choppedQname.toDNSStringLC();
} else if (rrsig_labels > fqdn_labels) {
// The RRSIG Labels field is a lie (or the qname is wrong) and the RRSIG
return toHash;
}
+std::unordered_set<unsigned int> DNSCryptoKeyEngine::s_switchedOff;
+
+bool DNSCryptoKeyEngine::isAlgorithmSwitchedOff(unsigned int algo)
+{
+ return s_switchedOff.count(algo) != 0;
+}
+
+void DNSCryptoKeyEngine::switchOffAlgorithm(unsigned int algo)
+{
+ s_switchedOff.insert(algo);
+}
+
bool DNSCryptoKeyEngine::isAlgorithmSupported(unsigned int algo)
{
+ if (isAlgorithmSwitchedOff(algo)) {
+ return false;
+ }
const makers_t& makers = getMakers();
- makers_t::const_iterator iter = makers.find(algo);
+ auto iter = makers.find(algo);
return iter != makers.cend();
}
{
public:
explicit DNSCryptoKeyEngine(unsigned int algorithm) : d_algorithm(algorithm) {}
- virtual ~DNSCryptoKeyEngine() {};
+ virtual ~DNSCryptoKeyEngine() = default;
[[nodiscard]] virtual string getName() const = 0;
using stormap_t = std::map<std::string, std::string>;
void createFromPEMString(DNSKEYRecordContent& drc, const std::string& contents)
{
// NOLINTNEXTLINE(*-cast): POSIX APIs.
- unique_ptr<std::FILE, decltype(&std::fclose)> inputFile{fmemopen(const_cast<char*>(contents.data()), contents.length(), "r"), &std::fclose};
+ pdns::UniqueFilePtr inputFile{fmemopen(const_cast<char*>(contents.data()), contents.length(), "r")};
createFromPEMFile(drc, *inputFile);
}
std::string output{};
output.resize(buflen);
- unique_ptr<std::FILE, decltype(&std::fclose)> outputFile{fmemopen(output.data(), output.length() - 1, "w"), &std::fclose};
+ pdns::UniqueFilePtr outputFile{fmemopen(output.data(), output.length() - 1, "w")};
convertToPEMFile(*outputFile);
std::fflush(outputFile.get());
output.resize(std::ftell(outputFile.get()));
static std::unique_ptr<DNSCryptoKeyEngine> makeFromPublicKeyString(unsigned int algorithm, const std::string& raw);
static std::unique_ptr<DNSCryptoKeyEngine> make(unsigned int algorithm);
static bool isAlgorithmSupported(unsigned int algo);
+ static bool isAlgorithmSwitchedOff(unsigned int algo);
+ static void switchOffAlgorithm(unsigned int algo);
static bool isDigestSupported(uint8_t digest);
using maker_t = std::unique_ptr<DNSCryptoKeyEngine> (unsigned int);
static vector<pair<uint8_t, string>> listAllAlgosWithBackend();
static bool testAll();
static bool testOne(int algo);
+ static bool verifyOne(unsigned int algo);
+ static bool testVerify(unsigned int algo, maker_t* verifier);
+ static string listSupportedAlgoNames();
private:
using makers_t = std::map<unsigned int, maker_t *>;
static allmakers_t s_allmakers;
return s_allmakers;
}
+ // Must be set before going multi-threaded and not changed after that
+ static std::unordered_set<unsigned int> s_switchedOff;
protected:
const unsigned int d_algorithm;
DNSKEYRecordContent d_dnskey;
std::shared_ptr<DNSCryptoKeyEngine> d_key;
- uint16_t d_flags;
- uint8_t d_algorithm;
+ uint16_t d_flags{0};
+ uint8_t d_algorithm{0};
};
bool unsetPublishCDS(const DNSName& zname);
bool TSIGGrantsAccess(const DNSName& zone, const DNSName& keyname);
- bool getTSIGForAccess(const DNSName& zone, const ComboAddress& master, DNSName* keyname);
+ bool getTSIGForAccess(const DNSName& zone, const ComboAddress& primary, DNSName* keyname);
void startTransaction(const DNSName& zone, int zone_id)
{
#include "lock.hh"
#include "arguments.hh"
#include "statbag.hh"
+#include "sha.hh"
+
extern StatBag S;
-typedef map<pair<string, string>, string> signaturecache_t;
+using signaturecache_t = map<pair<string, string>, string>;
static SharedLockGuarded<signaturecache_t> g_signatures;
static int g_cacheweekno;
static std::string getLookupKeyFromMessage(const std::string& msg)
{
try {
- return pdns_md5(msg);
+ return pdns::md5(msg);
}
catch(const std::runtime_error& e) {
- return pdns_sha1(msg);
+ return pdns::sha1(msg);
}
}
if (pubKey.size() <= 64) {
return pubKey;
}
- return pdns_sha1sum(pubKey);
+ return pdns::sha1sum(pubKey);
}
static void fillOutRRSIG(DNSSECPrivateKey& dpk, const DNSName& signQName, RRSIGRecordContent& rrc, const sortedRecords_t& toSign)
rrc.d_signature = rc->sign(msg);
(*g_signatureCount)++;
if(doCache) {
- /* we add some jitter here so not all your slaves start pruning their caches at the very same millisecond */
+ /* we add some jitter here so not all your secondaries start pruning their caches at the very same millisecond */
int weekno = (time(nullptr) - dns_random(3600)) / (86400*7); // we just spent milliseconds doing a signature, microsecond more won't kill us
const static int maxcachesize=::arg().asNum("max-signature-cache-entries", INT_MAX);
#include <protozero/pbf_writer.hpp>
-namespace DnstapBaseFields {
- enum : protozero::pbf_tag_type { identity = 1, version = 2, extra = 3, message = 14, type = 15 };
+namespace DnstapBaseFields
+{
+enum : protozero::pbf_tag_type
+{
+ identity = 1,
+ version = 2,
+ extra = 3,
+ message = 14,
+ type = 15
+};
+}
+
+namespace DnstapMessageTypes
+{
+enum : protozero::pbf_tag_type
+{
+ message = 1
+};
}
-namespace DnstapMessageTypes {
- enum : protozero::pbf_tag_type { message = 1 };
+namespace DnstapSocketFamilyTypes
+{
+enum : protozero::pbf_tag_type
+{
+ inet = 1,
+ inet6 = 2
+};
}
-namespace DnstapSocketFamilyTypes {
- enum : protozero::pbf_tag_type { inet = 1, inet6 = 2 };
+namespace DnstapMessageFields
+{
+enum : protozero::pbf_tag_type
+{
+ type = 1,
+ socket_family = 2,
+ socket_protocol = 3,
+ query_address = 4,
+ response_address = 5,
+ query_port = 6,
+ response_port = 7,
+ query_time_sec = 8,
+ query_time_nsec = 9,
+ query_message = 10,
+ query_zone = 11,
+ response_time_sec = 12,
+ response_time_nsec = 13,
+ response_message = 14
+};
}
-namespace DnstapMessageFields {
- enum : protozero::pbf_tag_type { type = 1, socket_family = 2, socket_protocol = 3, query_address = 4, response_address = 5, query_port = 6, response_port = 7, query_time_sec = 8, query_time_nsec = 9, query_message = 10, query_zone = 11, response_time_sec = 12, response_time_nsec = 13, response_message = 14 };
+std::string&& DnstapMessage::getBuffer()
+{
+ return std::move(d_buffer);
}
-DnstapMessage::DnstapMessage(std::string& buffer, DnstapMessage::MessageType type, const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, DnstapMessage::ProtocolType protocol, const char* packet, const size_t len, const struct timespec* queryTime, const struct timespec* responseTime, boost::optional<const DNSName&> auth): d_buffer(buffer)
+DnstapMessage::DnstapMessage(std::string&& buffer, DnstapMessage::MessageType type, const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, DnstapMessage::ProtocolType protocol, const char* packet, const size_t len, const struct timespec* queryTime, const struct timespec* responseTime, const boost::optional<const DNSName&>& auth) :
+ d_buffer(std::move(buffer))
{
protozero::pbf_writer pbf{d_buffer};
protozero::pbf_writer pbf_message{pbf, DnstapBaseFields::message};
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
pbf_message.add_enum(DnstapMessageFields::type, static_cast<protozero::pbf_tag_type>(type));
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
pbf_message.add_enum(DnstapMessageFields::socket_protocol, static_cast<protozero::pbf_tag_type>(protocol));
if (requestor != nullptr) {
if (requestor != nullptr) {
if (requestor->sin4.sin_family == AF_INET) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
pbf_message.add_bytes(DnstapMessageFields::query_address, reinterpret_cast<const char*>(&requestor->sin4.sin_addr.s_addr), sizeof(requestor->sin4.sin_addr.s_addr));
}
else if (requestor->sin4.sin_family == AF_INET6) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
pbf_message.add_bytes(DnstapMessageFields::query_address, reinterpret_cast<const char*>(&requestor->sin6.sin6_addr.s6_addr), sizeof(requestor->sin6.sin6_addr.s6_addr));
}
pbf_message.add_uint32(DnstapMessageFields::query_port, ntohs(requestor->sin4.sin_port));
if (responder != nullptr) {
if (responder->sin4.sin_family == AF_INET) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
pbf_message.add_bytes(DnstapMessageFields::response_address, reinterpret_cast<const char*>(&responder->sin4.sin_addr.s_addr), sizeof(responder->sin4.sin_addr.s_addr));
}
else if (responder->sin4.sin_family == AF_INET6) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
pbf_message.add_bytes(DnstapMessageFields::response_address, reinterpret_cast<const char*>(&responder->sin6.sin6_addr.s6_addr), sizeof(responder->sin6.sin6_addr.s6_addr));
}
pbf_message.add_uint32(DnstapMessageFields::response_port, ntohs(responder->sin4.sin_port));
}
if (packet != nullptr && len >= sizeof(dnsheader)) {
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
- if (!dh->qr) {
+ const dnsheader_aligned dnsheader(packet);
+ if (!dnsheader->qr) {
pbf_message.add_bytes(DnstapMessageFields::query_message, packet, len);
- } else {
+ }
+ else {
pbf_message.add_bytes(DnstapMessageFields::response_message, packet, len);
}
}
class DnstapMessage
{
public:
- enum class MessageType : uint32_t { auth_query = 1, auth_response = 2, resolver_query = 3, resolver_response = 4, client_query = 5, client_response = 6, forwarder_query = 7, forwarded_response = 8, stub_query = 9, stub_response = 10, tool_query = 11, tool_response = 12 };
- enum class ProtocolType : uint32_t { DoUDP = 1, DoTCP = 2, DoT = 3, DoH = 4, DNSCryptUDP = 5, DNSCryptTCP = 6 };
+ enum class MessageType : uint32_t
+ {
+ auth_query = 1,
+ auth_response = 2,
+ resolver_query = 3,
+ resolver_response = 4,
+ client_query = 5,
+ client_response = 6,
+ forwarder_query = 7,
+ forwarded_response = 8,
+ stub_query = 9,
+ stub_response = 10,
+ tool_query = 11,
+ tool_response = 12
+ };
+ enum class ProtocolType : uint32_t
+ {
+ DoUDP = 1,
+ DoTCP = 2,
+ DoT = 3,
+ DoH = 4,
+ DNSCryptUDP = 5,
+ DNSCryptTCP = 6,
+ DoQ = 7
+ };
- DnstapMessage(std::string& buffer, MessageType type, const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, ProtocolType protocol, const char* packet, const size_t len, const struct timespec* queryTime, const struct timespec* responseTime, boost::optional<const DNSName&> auth=boost::none);
+ DnstapMessage(std::string&& buffer, MessageType type, const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, ProtocolType protocol, const char* packet, size_t len, const struct timespec* queryTime, const struct timespec* responseTime, const boost::optional<const DNSName&>& auth = boost::none);
void setExtra(const std::string& extra);
+ std::string&& getBuffer();
-protected:
- std::string& d_buffer;
+private:
+ std::string d_buffer;
};
#endif /* DISABLE_PROTOBUF */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#if __clang_major__ >= 15
+#pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
+#endif
#include <boost/accumulators/statistics/median.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/accumulators.hpp>
-
#include <boost/accumulators/statistics.hpp>
+#pragma GCC diagnostic pop
#include <thread>
return 1000000*tv.tv_sec + tv.tv_usec;
}
-/* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
+/* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
to prevent running out of free TCP ports */
struct BenchQuery
if(!g_onlyTCP) {
Socket udpsock(g_dest.sin4.sin_family, SOCK_DGRAM);
-
+
udpsock.sendTo(string(packet.begin(), packet.end()), g_dest);
ComboAddress origin;
res = waitForData(udpsock.getHandle(), 0, 1000 * g_timeoutMsec);
Socket sock(g_dest.sin4.sin_family, SOCK_STREAM);
int tmp=1;
- if(setsockopt(sock.getHandle(),SOL_SOCKET,SO_REUSEADDR,(char*)&tmp,sizeof tmp)<0)
- throw runtime_error("Unable to set socket reuse: "+stringerror());
-
- if(g_tcpNoDelay && setsockopt(sock.getHandle(), IPPROTO_TCP, TCP_NODELAY,(char*)&tmp,sizeof tmp)<0)
- throw runtime_error("Unable to set socket no delay: "+stringerror());
+ if (setsockopt(sock.getHandle(), SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof tmp) < 0) {
+ throw runtime_error("Unable to set socket reuse: " + stringerror());
+ }
+
+ if (g_tcpNoDelay && setsockopt(sock.getHandle(), IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof tmp) < 0) {
+ throw runtime_error("Unable to set socket no delay: " + stringerror());
+ }
sock.connect(g_dest);
uint16_t len = htons(packet.size());
g_timeOuts++;
return;
}
-
+
if(sock.read((char *) &len, 2) != 2)
throw PDNSException("tcp read failed");
-
+
len=ntohs(len);
auto creply = std::make_unique<char[]>(len);
int n=0;
throw PDNSException("tcp read failed");
n+=numread;
}
-
+
reply=string(creply.get(), len);
-
+
gettimeofday(&now, 0);
q->tcpUsec = makeUsec(now - tv);
q->answerSecond = now.tv_sec;
{
setThreadName("dnstcpb/worker");
for(;;) {
- unsigned int pos = g_pos++;
+ unsigned int pos = g_pos++;
if(pos >= g_queries.size())
break;
hidden.add_options()
("remote-host", po::value<string>(), "remote-host")
("remote-port", po::value<int>()->default_value(53), "remote-port");
- alloptions.add(desc).add(hidden);
+ alloptions.add(desc).add(hidden);
po::positional_options_description p;
p.add("remote-host", 1);
g_dest = ComboAddress(g_vm["remote-host"].as<string>().c_str(), g_vm["remote-port"].as<int>());
unsigned int numworkers=g_vm["workers"].as<int>();
-
+
if(g_verbose) {
cout<<"Sending queries to: "<<g_dest.toStringWithPort()<<endl;
cout<<"Attempting UDP first: " << (g_onlyTCP ? "no" : "yes") <<endl;
std::vector<std::thread> workers;
workers.reserve(numworkers);
- std::unique_ptr<FILE, int(*)(FILE*)> fp{nullptr, fclose};
+ pdns::UniqueFilePtr filePtr{nullptr};
if (!g_vm.count("file")) {
- fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(0, "r"), fclose);
+ filePtr = pdns::UniqueFilePtr(fdopen(0, "r"));
}
else {
- fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(g_vm["file"].as<string>().c_str(), "r"), fclose);
- if (!fp) {
+ filePtr = pdns::UniqueFilePtr(fopen(g_vm["file"].as<string>().c_str(), "r"));
+ if (!filePtr) {
unixDie("Unable to open "+g_vm["file"].as<string>()+" for input");
}
}
pair<string, string> q;
string line;
- while(stringfgets(fp.get(), line)) {
+ while(stringfgets(filePtr.get(), line)) {
boost::trim_right(line);
q=splitField(line, ' ');
g_queries.push_back(BenchQuery(q.first, DNSRecordContent::TypeToNumber(q.second)));
}
- fp.reset();
+ filePtr.reset();
for (unsigned int n = 0; n < numworkers; ++n) {
workers.push_back(std::thread(worker));
for (auto& w : workers) {
w.join();
}
-
+
using namespace boost::accumulators;
typedef accumulator_set<
double
> acc_t;
acc_t udpspeeds, tcpspeeds, qps;
-
+
typedef map<time_t, uint32_t> counts_t;
counts_t counts;
class IPObfuscator
{
public:
- virtual ~IPObfuscator()
- {
- }
+ virtual ~IPObfuscator() = default;
virtual uint32_t obf4(uint32_t orig)=0;
virtual struct in6_addr obf6(const struct in6_addr& orig)=0;
};
{
}
- ~IPSeqObfuscator()
- {}
-
static std::unique_ptr<IPObfuscator> make()
{
return std::make_unique<IPSeqObfuscator>();
}
}
- ~IPCipherObfuscator()
- {}
- static std::unique_ptr<IPObfuscator> make(std::string key, bool decrypt)
+ static std::unique_ptr<IPObfuscator> make(const std::string& key, bool decrypt)
{
return std::make_unique<IPCipherObfuscator>(key, decrypt);
}
cerr<<"Invalidly encoded base64 key provided"<<endl;
exit(EXIT_FAILURE);
}
- ipo = IPCipherObfuscator::make(key, doDecrypt);
+ ipo = IPCipherObfuscator::make(std::move(key), doDecrypt);
}
else if(!g_vm.count("key") && g_vm.count("passphrase")) {
string key = makeIPCipherKey(g_vm["passphrase"].as<string>());
- ipo = IPCipherObfuscator::make(key, doDecrypt);
+ ipo = IPCipherObfuscator::make(std::move(key), doDecrypt);
}
else {
cerr<<"Can't specify both 'key' and 'passphrase'"<<endl;
void startRecord(const DNSName& name, uint16_t qtype, uint32_t ttl=3600, uint16_t qclass=QClass::IN, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, bool compress=true);
/** Shorthand way to add an Opt-record, for example for EDNS0 purposes */
- typedef vector<pair<uint16_t,std::string> > optvect_t;
+ using optvect_t = vector<pair<uint16_t,std::string> >;
void addOpt(const uint16_t udpsize, const uint16_t extRCode, const uint16_t ednsFlags, const optvect_t& options=optvect_t(), const uint8_t version=0);
/** needs to be called after the last record is added, but can be called again and again later on. Is called internally by startRecord too.
*/
#pragma once
-#include <unordered_map>
+#include "config.h"
-#include "iputils.hh"
-#include "libssl.hh"
-#include "noinitvector.hh"
-#include "stat_t.hh"
+#ifdef HAVE_DNS_OVER_HTTPS
+#ifdef HAVE_LIBH2OEVLOOP
-struct DOHServerConfig;
+#include <ctime>
+#include <memory>
+#include <string>
-class DOHResponseMapEntry
-{
-public:
- DOHResponseMapEntry(const std::string& regex, uint16_t status, const PacketBuffer& content, const boost::optional<std::unordered_map<std::string, std::string>>& headers): d_regex(regex), d_customHeaders(headers), d_content(content), d_status(status)
- {
- if (status >= 400 && !d_content.empty() && d_content.at(d_content.size() -1) != 0) {
- // we need to make sure it's null-terminated
- d_content.push_back(0);
- }
- }
-
- bool matches(const std::string& path) const
- {
- return d_regex.match(path);
- }
-
- uint16_t getStatusCode() const
- {
- return d_status;
- }
-
- const PacketBuffer& getContent() const
- {
- return d_content;
- }
-
- const boost::optional<std::unordered_map<std::string, std::string>>& getHeaders() const
- {
- return d_customHeaders;
- }
-
-private:
- Regex d_regex;
- boost::optional<std::unordered_map<std::string, std::string>> d_customHeaders;
- PacketBuffer d_content;
- uint16_t d_status;
-};
-
-struct DOHFrontend
-{
- DOHFrontend()
- {
- }
-
- std::shared_ptr<DOHServerConfig> d_dsc{nullptr};
- std::shared_ptr<std::vector<std::shared_ptr<DOHResponseMapEntry>>> d_responsesMap;
- TLSConfig d_tlsConfig;
- TLSErrorCounters d_tlsCounters;
- std::string d_serverTokens{"h2o/dnsdist"};
- std::unordered_map<std::string, std::string> d_customResponseHeaders;
- ComboAddress d_local;
-
- uint32_t d_idleTimeout{30}; // HTTP idle timeout in seconds
- std::vector<std::string> d_urls;
-
- pdns::stat_t d_httpconnects{0}; // number of TCP/IP connections established
- pdns::stat_t d_getqueries{0}; // valid DNS queries received via GET
- pdns::stat_t d_postqueries{0}; // valid DNS queries received via POST
- pdns::stat_t d_badrequests{0}; // request could not be converted to dns query
- pdns::stat_t d_errorresponses{0}; // dnsdist set 'error' on response
- pdns::stat_t d_redirectresponses{0}; // dnsdist set 'redirect' on response
- pdns::stat_t d_validresponses{0}; // valid responses sent out
-
- struct HTTPVersionStats
- {
- pdns::stat_t d_nbQueries{0}; // valid DNS queries received
- pdns::stat_t d_nb200Responses{0};
- pdns::stat_t d_nb400Responses{0};
- pdns::stat_t d_nb403Responses{0};
- pdns::stat_t d_nb500Responses{0};
- pdns::stat_t d_nb502Responses{0};
- pdns::stat_t d_nbOtherResponses{0};
- };
-
- HTTPVersionStats d_http1Stats;
- HTTPVersionStats d_http2Stats;
-#ifdef __linux__
- // On Linux this gives us 128k pending queries (default is 8192 queries),
- // which should be enough to deal with huge spikes
- uint32_t d_internalPipeBufferSize{1024*1024};
-#else
- uint32_t d_internalPipeBufferSize{0};
-#endif
- bool d_sendCacheControlHeaders{true};
- bool d_trustForwardedForHeader{false};
- /* whether we require tue query path to exactly match one of configured ones,
- or accept everything below these paths. */
- bool d_exactPathMatching{true};
- bool d_keepIncomingHeaders{false};
-
- time_t getTicketsKeyRotationDelay() const
- {
- return d_tlsConfig.d_ticketsKeyRotationDelay;
- }
-
- bool isHTTPS() const
- {
- return !d_tlsConfig.d_certKeyPairs.empty();
- }
-
-#ifndef HAVE_DNS_OVER_HTTPS
- void setup()
- {
- }
-
- void reloadCertificates()
- {
- }
-
- void rotateTicketsKey(time_t /* now */)
- {
- }
-
- void loadTicketsKeys(const std::string& /* keyFile */)
- {
- }
-
- void handleTicketsKeyRotation()
- {
- }
-
- time_t getNextTicketsKeyRotation() const
- {
- return 0;
- }
-
- size_t getTicketsKeysCount() const
- {
- size_t res = 0;
- return res;
- }
-
-#else
- void setup();
- void reloadCertificates();
-
- void rotateTicketsKey(time_t now);
- void loadTicketsKeys(const std::string& keyFile);
- void handleTicketsKeyRotation();
- time_t getNextTicketsKeyRotation() const;
- size_t getTicketsKeysCount() const;
-#endif /* HAVE_DNS_OVER_HTTPS */
-};
-
-#ifndef HAVE_DNS_OVER_HTTPS
-struct DOHUnit
-{
- static void release(DOHUnit*)
- {
- }
-
- void get()
- {
- }
-
- void release()
- {
- }
-
- size_t proxyProtocolPayloadSize{0};
- uint16_t status_code{200};
-};
-
-#else /* HAVE_DNS_OVER_HTTPS */
-#include <unordered_map>
+struct CrossProtocolQuery;
+struct DNSQuestion;
-#include "dnsdist-idstate.hh"
+std::unique_ptr<CrossProtocolQuery> getDoHCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse);
-struct st_h2o_req_t;
-struct DownstreamState;
+#include "dnsdist-doh-common.hh"
-struct DOHUnit
+struct H2ODOHFrontend : public DOHFrontend
{
- DOHUnit(PacketBuffer&& q, std::string&& p, std::string&& h): path(std::move(p)), host(std::move(h)), query(std::move(q))
- {
- ids.ednsAdded = false;
- }
-
- DOHUnit(const DOHUnit&) = delete;
- DOHUnit& operator=(const DOHUnit&) = delete;
-
- void get()
- {
- ++d_refcnt;
- }
-
- void release()
- {
- if (--d_refcnt == 0) {
- if (self) {
- *self = nullptr;
- }
-
- delete this;
- }
- }
-
- static void release(DOHUnit* ptr)
- {
- if (ptr) {
- ptr->release();
- }
- }
+public:
- InternalQueryState ids;
- std::string sni;
- std::string path;
- std::string scheme;
- std::string host;
- std::string contentType;
- PacketBuffer query;
- PacketBuffer response;
- std::shared_ptr<DownstreamState> downstream{nullptr};
- std::unique_ptr<std::unordered_map<std::string, std::string>> headers;
- st_h2o_req_t* req{nullptr};
- DOHUnit** self{nullptr};
- DOHServerConfig* dsc{nullptr};
- std::atomic<uint64_t> d_refcnt{1};
- size_t query_at{0};
- size_t proxyProtocolPayloadSize{0};
- int rsock{-1};
- /* the status_code is set from
- processDOHQuery() (which is executed in
- the DOH client thread) so that the correct
- response can be sent in on_dnsdist(),
- after the DOHUnit has been passed back to
- the main DoH thread.
- */
- uint16_t status_code{200};
- /* whether the query was re-sent to the backend over
- TCP after receiving a truncated answer over UDP */
- bool tcp{false};
- bool truncated{false};
+ void setup() override;
+ void reloadCertificates() override;
- std::string getHTTPPath() const;
- std::string getHTTPHost() const;
- std::string getHTTPScheme() const;
- std::string getHTTPQueryString() const;
- std::unordered_map<std::string, std::string> getHTTPHeaders() const;
- void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType="");
+ void rotateTicketsKey(time_t now) override;
+ void loadTicketsKeys(const std::string& keyFile) override;
+ void handleTicketsKeyRotation() override;
+ std::string getNextTicketsKeyRotation() const override;
+ size_t getTicketsKeysCount() override;
};
-void handleUDPResponseForDoH(std::unique_ptr<DOHUnit, void(*)(DOHUnit*)>&&, PacketBuffer&& response, InternalQueryState&& state);
-
-struct CrossProtocolQuery;
-struct DNSQuestion;
-
-std::unique_ptr<CrossProtocolQuery> getDoHCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse);
+void dohThread(ClientState* clientState);
+#endif /* HAVE_LIBH2OEVLOOP */
#endif /* HAVE_DNS_OVER_HTTPS */
-
-using DOHUnitUniquePtr = std::unique_ptr<DOHUnit, void(*)(DOHUnit*)>;
-
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
+#include <array>
#include <fstream>
+#include <iomanip>
#include <iostream>
#include <optional>
#include <sstream>
#include "logger.hh"
#endif // RECURSOR
-
/* This file is intended not to be metronome specific, and is simple example of C++2011
variadic templates in action.
- The goal is rapid easy to use logging to console & syslog.
+ The goal is rapid easy to use logging to console & syslog.
- Usage:
+ Usage:
string address="localhost";
vinfolog("Got TCP connection from %s", remote);
infolog("Bound to %s port %d", address, port);
This will happily print a string to %d! Doesn't do further format processing.
*/
-
-#if !defined(RECURSOR)
-inline void dolog(std::ostream& os, const char*s)
+template <typename O>
+inline void dolog(O& outputStream, const char* str)
{
- os<<s;
+ outputStream << str;
}
-template<typename T, typename... Args>
-void dolog(std::ostream& os, const char* s, T value, Args... args)
+template <typename O, typename T, typename... Args>
+void dolog(O& outputStream, const char* formatStr, T value, const Args&... args)
{
- while (*s) {
- if (*s == '%') {
- if (*(s + 1) == '%') {
- ++s;
+ while (*formatStr) {
+ if (*formatStr == '%') {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ if (*(formatStr + 1) == '%') {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ ++formatStr;
}
else {
- os << value;
- s += 2;
- dolog(os, s, args...);
- return;
+ outputStream << value;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ formatStr += 2;
+ dolog(outputStream, formatStr, args...);
+ return;
}
}
- os << *s++;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ outputStream << *formatStr++;
}
}
+#if !defined(RECURSOR)
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern bool g_verbose;
-extern bool g_syslog;
+
#ifdef DNSDIST
-extern bool g_logtimestamps;
-extern std::optional<std::ofstream> g_verboseStream;
+namespace dnsdist::logging
+{
+class LoggingConfiguration
+{
+public:
+ enum class TimeFormat
+ {
+ Numeric,
+ ISO8601
+ };
+
+ static void setVerbose(bool value = true)
+ {
+ g_verbose = value;
+ }
+ static void setSyslog(bool value = true)
+ {
+ s_syslog = value;
+ }
+ static void setStructuredLogging(bool value = true, std::string levelPrefix = "")
+ {
+ s_structuredLogging = value;
+ if (value) {
+ s_structuredLevelPrefix = levelPrefix.empty() ? "prio" : std::move(levelPrefix);
+ }
+ }
+ static void setLogTimestamps(bool value = true)
+ {
+ s_logTimestamps = value;
+ }
+ static void setStructuredTimeFormat(TimeFormat format)
+ {
+ s_structuredTimeFormat = format;
+ }
+ static void setVerboseStream(std::ofstream&& stream)
+ {
+ s_verboseStream = std::move(stream);
+ }
+ static bool getVerbose()
+ {
+ return g_verbose;
+ }
+ static bool getSyslog()
+ {
+ return s_syslog;
+ }
+ static bool getLogTimestamps()
+ {
+ return s_logTimestamps;
+ }
+ static std::optional<std::ofstream>& getVerboseStream()
+ {
+ return s_verboseStream;
+ }
+ static bool getStructuredLogging()
+ {
+ return s_structuredLogging;
+ }
+ static const std::string& getStructuredLoggingLevelPrefix()
+ {
+ return s_structuredLevelPrefix;
+ }
+
+ static TimeFormat getStructuredLoggingTimeFormat()
+ {
+ return s_structuredTimeFormat;
+ }
+
+private:
+ static std::optional<std::ofstream> s_verboseStream;
+ static std::string s_structuredLevelPrefix;
+ static TimeFormat s_structuredTimeFormat;
+ static bool s_structuredLogging;
+ static bool s_logTimestamps;
+ static bool s_syslog;
+};
+
+extern void logTime(std::ostream& stream);
+}
#endif
inline void setSyslogFacility(int facility)
{
/* we always call openlog() right away at startup */
closelog();
- openlog("dnsdist", LOG_PID|LOG_NDELAY, facility);
+ openlog("dnsdist", LOG_PID | LOG_NDELAY, facility);
+}
+
+namespace
+{
+inline const char* syslogLevelToStr(int level)
+{
+ static constexpr std::array levelStrs{
+ "Emergency",
+ "Alert",
+ "Critical",
+ "Error",
+ "Warning",
+ "Notice",
+ "Info",
+ "Debug"};
+ return levelStrs.at(level);
+}
}
-template<typename... Args>
-void genlog(std::ostream& stream, int level, bool doSyslog, const char* s, Args... args)
+template <typename... Args>
+void genlog(std::ostream& stream, [[maybe_unused]] int level, [[maybe_unused]] bool skipSyslog, const char* formatStr, const Args&... args)
{
std::ostringstream str;
- dolog(str, s, args...);
+ dolog(str, formatStr, args...);
auto output = str.str();
- if (doSyslog) {
+#ifdef DNSDIST
+ if (!skipSyslog && dnsdist::logging::LoggingConfiguration::getSyslog()) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): syslog is what it is
syslog(level, "%s", output.c_str());
}
-#ifdef DNSDIST
- if (g_logtimestamps) {
- char buffer[50] = "";
- struct tm tm;
- time_t t;
- time(&t);
- localtime_r(&t, &tm);
- if (strftime(buffer, sizeof(buffer), "%b %d %H:%M:%S ", &tm) == 0) {
- buffer[0] = '\0';
- }
- stream<<buffer;
+ if (dnsdist::logging::LoggingConfiguration::getLogTimestamps()) {
+ dnsdist::logging::logTime(stream);
}
-#endif
- stream<<output<<std::endl;
+ if (dnsdist::logging::LoggingConfiguration::getStructuredLogging()) {
+ stream << dnsdist::logging::LoggingConfiguration::getStructuredLoggingLevelPrefix() << "=\"" << syslogLevelToStr(level) << "\" ";
+ stream << "msg=" << std::quoted(output) << std::endl;
+ }
+ else {
+ stream << output << std::endl;
+ }
+#else
+ stream << output << std::endl;
+#endif
}
-template<typename... Args>
-void verboselog(const char* s, Args... args)
+template <typename... Args>
+void verboselog(const char* formatStr, const Args&... args)
{
#ifdef DNSDIST
- if (g_verboseStream) {
- genlog(*g_verboseStream, LOG_DEBUG, false, s, args...);
+ if (auto& stream = dnsdist::logging::LoggingConfiguration::getVerboseStream()) {
+ genlog(*stream, LOG_DEBUG, true, formatStr, args...);
}
else {
#endif /* DNSDIST */
- genlog(std::cout, LOG_DEBUG, g_syslog, s, args...);
+ genlog(std::cout, LOG_DEBUG, false, formatStr, args...);
#ifdef DNSDIST
}
#endif /* DNSDIST */
}
-#define vinfolog if (g_verbose) verboselog
+#define vinfolog \
+ if (g_verbose) \
+ verboselog
-template<typename... Args>
-void infolog(const char* s, Args... args)
+template <typename... Args>
+void infolog(const char* formatStr, const Args&... args)
{
- genlog(std::cout, LOG_INFO, g_syslog, s, args...);
+ genlog(std::cout, LOG_INFO, false, formatStr, args...);
}
-template<typename... Args>
-void warnlog(const char* s, Args... args)
+template <typename... Args>
+void warnlog(const char* formatStr, const Args&... args)
{
- genlog(std::cout, LOG_WARNING, g_syslog, s, args...);
+ genlog(std::cout, LOG_WARNING, false, formatStr, args...);
}
-template<typename... Args>
-void errlog(const char* s, Args... args)
+template <typename... Args>
+void errlog(const char* formatStr, const Args&... args)
{
- genlog(std::cout, LOG_ERR, g_syslog, s, args...);
+ genlog(std::cout, LOG_ERR, false, formatStr, args...);
}
#else // RECURSOR
-
#define g_verbose 0
+#define vinfolog \
+ if (g_verbose) \
+ infolog
-inline void dolog(Logger::Urgency u, const char* s)
-{
- g_log << u << s << std::endl;
-}
-
-inline void dolog(const char* s)
-{
- g_log << s << std::endl;
-}
-
-template<typename T, typename... Args>
-void dolog(Logger::Urgency u, const char* s, T value, Args... args)
-{
- g_log << u;
- while (*s) {
- if (*s == '%') {
- if (*(s + 1) == '%') {
- ++s;
- }
- else {
- g_log << value;
- s += 2;
- dolog(s, args...);
- return;
- }
- }
- g_log << *s++;
- }
-}
-
-#define vinfolog if(g_verbose)infolog
-
-template<typename... Args>
-void infolog(const char* s, Args... args)
+template <typename... Args>
+void infolog(const char* formatStr, const Args&... args)
{
- dolog(Logger::Info, s, args...);
+ g_log << Logger::Info;
+ dolog(g_log, formatStr, args...);
}
-template<typename... Args>
-void warnlog(const char* s, Args... args)
+template <typename... Args>
+void warnlog(const char* formatStr, const Args&... args)
{
- dolog(Logger::Warning, s, args...);
+ g_log << Logger::Warning;
+ dolog(g_log, formatStr, args...);
}
-template<typename... Args>
-void errlog(const char* s, Args... args)
+template <typename... Args>
+void errlog(const char* formatStr, const Args&... args)
{
- dolog(Logger::Error, s, args...);
+ g_log << Logger::Error;
+ dolog(g_log, formatStr, args...);
}
#endif
return "Failed to parse zone as valid DNS name";
}
- ComboAddress master_ip;
- bool override_master = false;
+ ComboAddress primary_ip;
+ bool override_primary = false;
if (parts.size() == 3) {
try {
- master_ip = ComboAddress{parts[2], 53};
+ primary_ip = ComboAddress{parts[2], 53};
} catch (...) {
return "Invalid primary address";
}
- override_master = true;
+ override_primary = true;
}
DomainInfo di;
return " Zone '" + domain.toString() + "' unknown";
}
- if (override_master) {
- di.masters.clear();
- di.masters.push_back(master_ip);
+ if (override_primary) {
+ di.primaries.clear();
+ di.primaries.push_back(primary_ip);
}
- if (!override_master && (!di.isSecondaryType() || di.masters.empty()))
+ if (!override_primary && (!di.isSecondaryType() || di.primaries.empty()))
return "Zone '" + domain.toString() + "' is not a secondary/consumer zone (or has no primary defined)";
- shuffle(di.masters.begin(), di.masters.end(), pdns::dns_random_engine());
- const auto& master = di.masters.front();
- Communicator.addSuckRequest(domain, master, SuckRequest::PdnsControl, override_master);
- g_log << Logger::Warning << "Retrieval request for zone '" << domain << "' from primary '" << master << "' received from operator" << endl;
- return "Added retrieval request for '" + domain.toLogString() + "' from primary " + master.toLogString();
+ shuffle(di.primaries.begin(), di.primaries.end(), pdns::dns_random_engine());
+ const auto& primary = di.primaries.front();
+ Communicator.addSuckRequest(domain, primary, SuckRequest::PdnsControl, override_primary);
+ g_log << Logger::Warning << "Retrieval request for zone '" << domain << "' from primary '" << primary << "' received from operator" << endl;
+ return "Added retrieval request for '" + domain.toLogString() + "' from primary " + primary.toLogString();
}
string DLNotifyHostHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
extern bool PKCS11ModuleSlotLogin(const std::string& module, const string& tokenId, const std::string& pin);
#endif
-string DLTokenLogin(const vector<string>& parts, Utility::pid_t /* ppid */)
+string DLTokenLogin([[maybe_unused]] const vector<string>& parts, [[maybe_unused]] Utility::pid_t /* ppid */)
{
#ifndef HAVE_P11KIT1
return "PKCS#11 support not compiled in";
void DynListener::registerFunc(const string &name, g_funk_t *gf, const string &usage, const string &args)
{
g_funkwithusage_t e = {gf, args, usage};
- s_funcdb[name]=e;
+ s_funcdb[name] = std::move(e);
}
void DynListener::registerRestFunc(g_funk_t *gf)
try {
signal(SIGPIPE,SIG_IGN);
- for(int n=0;;++n) {
+ for(;;) {
string line=getLine();
boost::trim_right(line);
string command = commands[0];
shared_ptr<DynMessenger> D;
if(::arg()["remote-address"].empty())
- D=shared_ptr<DynMessenger>(new DynMessenger(socketname));
+ D = std::make_shared<DynMessenger>(socketname);
else {
uint16_t port;
try {
exit(99);
}
- D=shared_ptr<DynMessenger>(new DynMessenger(ComboAddress(::arg()["remote-address"], port), ::arg()["secret"]));
+ D = std::make_shared<DynMessenger>(ComboAddress(::arg()["remote-address"], port), ::arg()["secret"]);
}
string message;
struct sockaddr_un d_remote; // our remote address
- DynMessenger(const DynMessenger &); // NOT IMPLEMENTED
-
public:
// CREATORS
int timeout_sec = 7,
int timeout_usec = 0); //!< Create a DynMessenger sending to this file
+ DynMessenger(const DynMessenger&) = delete; // NOT IMPLEMENTED
+ DynMessenger& operator=(const DynMessenger&) = delete; // NOT IMPLEMENTED
+
~DynMessenger();
// ACCESSORS
}
}
-bool EDNSCookiesOpt::isValid(const string& secret, const ComboAddress& source) const
+bool EDNSCookiesOpt::isValid([[maybe_unused]] const string& secret, [[maybe_unused]] const ComboAddress& source) const
{
#ifdef HAVE_CRYPTO_SHORTHASH
if (server.length() != 16 || client.length() != 8) {
return rfc1982LessThan(ts + 1800, now);
}
-bool EDNSCookiesOpt::makeServerCookie(const string& secret, const ComboAddress& source)
+bool EDNSCookiesOpt::makeServerCookie([[maybe_unused]] const string& secret, [[maybe_unused]] const ComboAddress& source)
{
#ifdef HAVE_CRYPTO_SHORTHASH
static_assert(EDNSCookieSecretSize == crypto_shorthash_KEYBYTES * 2, "The EDNSCookieSecretSize is not twice crypto_shorthash_KEYBYTES");
static const size_t EDNSCookieSecretSize = 32;
static const size_t EDNSCookieOptSize = 24;
- EDNSCookiesOpt(){};
+ EDNSCookiesOpt() = default;
EDNSCookiesOpt(const std::string& option);
EDNSCookiesOpt(const char* option, unsigned int len);
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <limits>
+
+#include "ednsextendederror.hh"
+
+static bool getEDNSExtendedErrorOptFromStringView(const std::string_view& option, EDNSExtendedError& eee)
+{
+ if (option.size() < sizeof(uint16_t)) {
+ return false;
+ }
+ eee.infoCode = static_cast<uint8_t>(option.at(0)) * 256 + static_cast<uint8_t>(option.at(1));
+
+ if (option.size() > sizeof(uint16_t)) {
+ eee.extraText = std::string(&option.at(sizeof(uint16_t)), option.size() - sizeof(uint16_t));
+ }
+
+ return true;
+}
+
+bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee)
+{
+ return getEDNSExtendedErrorOptFromStringView(std::string_view(option), eee);
+}
+
+bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee)
+{
+ return getEDNSExtendedErrorOptFromStringView(std::string_view(option, len), eee);
+}
+
+string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee)
+{
+ if (eee.extraText.size() > static_cast<size_t>(std::numeric_limits<uint16_t>::max() - 2)) {
+ throw std::runtime_error("Trying to create an EDNS Extended Error option with an extra text of size " + std::to_string(eee.extraText.size()));
+ }
+
+ string ret;
+ ret.reserve(sizeof(uint16_t) + eee.extraText.size());
+ ret.resize(sizeof(uint16_t));
+
+ ret[0] = static_cast<char>(static_cast<uint16_t>(eee.infoCode) / 256);
+ ret[1] = static_cast<char>(static_cast<uint16_t>(eee.infoCode) % 256);
+ ret.append(eee.extraText);
+
+ return ret;
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#include "namespaces.hh"
+
+struct EDNSExtendedError
+{
+ enum class code : uint16_t
+ {
+ Other = 0,
+ UnsupportedDNSKEYAlgorithm = 1,
+ UnsupportedDSDigestType = 2,
+ StaleAnswer = 3,
+ ForgedAnswer = 4,
+ DNSSECIndeterminate = 5,
+ DNSSECBogus = 6,
+ SignatureExpired = 7,
+ SignatureNotYetValid = 8,
+ DNSKEYMissing = 9,
+ RRSIGsMissing = 10,
+ NoZoneKeyBitSet = 11,
+ NSECMissing = 12,
+ CachedError = 13,
+ NotReady = 14,
+ Blocked = 15,
+ Censored = 16,
+ Filtered = 17,
+ Prohibited = 18,
+ StaleNXDOMAINAnswer = 19,
+ NotAuthoritative = 20,
+ NotSupported = 21,
+ NoReachableAuthority = 22,
+ NetworkError = 23,
+ InvalidData = 24,
+ SignatureExpiredBeforeValid = 25,
+ TooEarly = 26,
+ UnsupportedNSEC3IterationsValue = 27,
+ UnableToConformToPolicy = 28,
+ Synthesized = 29,
+ };
+ uint16_t infoCode;
+ std::string extraText;
+};
+
+bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee);
+bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee);
+string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee);
#include "ednssubnet.hh"
#include "dns.hh"
-namespace {
- struct EDNSSubnetOptsWire
- {
- uint16_t family;
- uint8_t sourceMask;
- uint8_t scopeMask;
- } GCCPACKATTRIBUTE; // BRRRRR
+namespace
+{
+struct EDNSSubnetOptsWire
+{
+ uint16_t family;
+ uint8_t sourceMask;
+ uint8_t scopeMask;
+} GCCPACKATTRIBUTE; // BRRRRR
}
bool getEDNSSubnetOptsFromString(const string& options, EDNSSubnetOpts* eso)
{
- //cerr<<"options.size:"<<options.size()<<endl;
+ // cerr<<"options.size:"<<options.size()<<endl;
return getEDNSSubnetOptsFromString(options.c_str(), options.length(), eso);
}
bool getEDNSSubnetOptsFromString(const char* options, unsigned int len, EDNSSubnetOpts* eso)
{
- EDNSSubnetOptsWire esow;
- static_assert (sizeof(esow) == 4, "sizeof(EDNSSubnetOptsWire) must be 4 bytes");
- if(len < sizeof(esow))
+ EDNSSubnetOptsWire esow{};
+ static_assert(sizeof(esow) == 4, "sizeof(EDNSSubnetOptsWire) must be 4 bytes");
+ if (len < sizeof(esow)) {
return false;
+ }
memcpy(&esow, options, sizeof(esow));
esow.family = ntohs(esow.family);
- //cerr<<"Family when parsing from string: "<<esow.family<<endl;
+ // cerr<<"Family when parsing from string: "<<esow.family<<endl;
ComboAddress address;
- unsigned int octetsin = esow.sourceMask > 0 ? (((esow.sourceMask - 1)>> 3)+1) : 0;
- //cerr<<"octetsin:"<<octetsin<<endl;
- if(esow.family == 1) {
- if(len != sizeof(esow)+octetsin)
+ unsigned int octetsin = esow.sourceMask > 0 ? (((esow.sourceMask - 1) >> 3) + 1) : 0;
+ // cerr<<"octetsin:"<<octetsin<<endl;
+ if (esow.family == 1) {
+ if (len != sizeof(esow) + octetsin) {
return false;
- if(octetsin > sizeof(address.sin4.sin_addr.s_addr))
+ }
+ if (octetsin > sizeof(address.sin4.sin_addr.s_addr)) {
return false;
+ }
address.reset();
address.sin4.sin_family = AF_INET;
- if(octetsin > 0)
- memcpy(&address.sin4.sin_addr.s_addr, options+sizeof(esow), octetsin);
- } else if(esow.family == 2) {
- if(len != sizeof(esow)+octetsin)
+ if (octetsin > 0) {
+ memcpy(&address.sin4.sin_addr.s_addr, options + sizeof(esow), octetsin); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ }
+ }
+ else if (esow.family == 2) {
+ if (len != sizeof(esow) + octetsin) {
return false;
- if(octetsin > sizeof(address.sin6.sin6_addr.s6_addr))
+ }
+ if (octetsin > sizeof(address.sin6.sin6_addr.s6_addr)) {
return false;
+ }
address.reset();
address.sin4.sin_family = AF_INET6;
- if(octetsin > 0)
- memcpy(&address.sin6.sin6_addr.s6_addr, options+sizeof(esow), octetsin);
+ if (octetsin > 0) {
+ memcpy(&address.sin6.sin6_addr.s6_addr, options + sizeof(esow), octetsin); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ }
}
- else
+ else {
return false;
- //cerr<<"Source address: "<<address.toString()<<", mask: "<<(int)esow.sourceMask<<endl;
+ }
eso->source = Netmask(address, esow.sourceMask);
/* 'address' has more bits set (potentially) than scopeMask. This leads to odd looking netmasks that promise
more precision than they have. For this reason we truncate the address to scopeMask bits */
-
+
address.truncate(esow.scopeMask); // truncate will not throw for odd scopeMasks
eso->scope = Netmask(address, esow.scopeMask);
string makeEDNSSubnetOptsString(const EDNSSubnetOpts& eso)
{
string ret;
- EDNSSubnetOptsWire esow;
+ EDNSSubnetOptsWire esow{};
uint16_t family = htons(eso.source.getNetwork().sin4.sin_family == AF_INET ? 1 : 2);
esow.family = family;
esow.sourceMask = eso.source.getBits();
esow.scopeMask = eso.scope.getBits();
- ret.assign((const char*)&esow, sizeof(esow));
- int octetsout = ((esow.sourceMask - 1)>> 3)+1;
+ ret.assign((const char*)&esow, sizeof(esow)); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+ int octetsout = ((esow.sourceMask - 1) >> 3) + 1;
- ComboAddress src=eso.source.getNetwork();
+ ComboAddress src = eso.source.getNetwork();
src.truncate(esow.sourceMask);
- if(family == htons(1))
- ret.append((const char*) &src.sin4.sin_addr.s_addr, octetsout);
- else
- ret.append((const char*) &src.sin6.sin6_addr.s6_addr, octetsout);
+ if (family == htons(1)) {
+ ret.append((const char*)&src.sin4.sin_addr.s_addr, octetsout); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+ }
+ else {
+ ret.append((const char*)&src.sin6.sin6_addr.s6_addr, octetsout); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+ }
return ret;
}
-
struct EDNSSubnetOpts
{
- Netmask source;
- Netmask scope;
+ Netmask source;
+ Netmask scope;
};
bool getEDNSSubnetOptsFromString(const string& options, EDNSSubnetOpts* eso);
{
public:
EpollFDMultiplexer(unsigned int maxEventsHint);
- ~EpollFDMultiplexer()
+ ~EpollFDMultiplexer() override
{
if (d_epollfd >= 0) {
close(d_epollfd);
#include "dolog.hh"
#endif
-#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap"
-
#ifdef HAVE_FSTRM
-FrameStreamLogger::FrameStreamLogger(const int family, const std::string& address, bool connect,
- const std::unordered_map<string,unsigned>& options): d_family(family), d_address(address)
-{
- fstrm_res res;
+static const std::string DNSTAP_CONTENT_TYPE = "protobuf:dnstap.Dnstap";
+FrameStreamLogger::FrameStreamLogger(const int family, std::string address, bool connect, const std::unordered_map<string, unsigned>& options) :
+ d_family(family), d_address(std::move(address))
+{
try {
d_fwopt = fstrm_writer_options_init();
- if (!d_fwopt) {
+ if (d_fwopt == nullptr) {
throw std::runtime_error("FrameStreamLogger: fstrm_writer_options_init failed.");
}
- res = fstrm_writer_options_add_content_type(d_fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
+ auto res = fstrm_writer_options_add_content_type(d_fwopt, DNSTAP_CONTENT_TYPE.c_str(), DNSTAP_CONTENT_TYPE.size());
if (res != fstrm_res_success) {
throw std::runtime_error("FrameStreamLogger: fstrm_writer_options_add_content_type failed: " + std::to_string(res));
}
if (d_family == AF_UNIX) {
- struct sockaddr_un local;
- if (makeUNsockaddr(d_address, &local)) {
+ struct sockaddr_un local
+ {
+ };
+ if (makeUNsockaddr(d_address, &local) != 0) {
throw std::runtime_error("FrameStreamLogger: Unable to use '" + d_address + "', it is not a valid UNIX socket path.");
}
d_uwopt = fstrm_unix_writer_options_init();
- if (!d_uwopt) {
+ if (d_uwopt == nullptr) {
throw std::runtime_error("FrameStreamLogger: fstrm_unix_writer_options_init failed.");
}
fstrm_unix_writer_options_set_socket_path(d_uwopt, d_address.c_str());
d_writer = fstrm_unix_writer_init(d_uwopt, d_fwopt);
- if (!d_writer) {
+ if (d_writer == nullptr) {
throw std::runtime_error("FrameStreamLogger: fstrm_unix_writer_init() failed.");
}
- #ifdef HAVE_FSTRM_TCP_WRITER_INIT
- } else if (family == AF_INET) {
+#ifdef HAVE_FSTRM_TCP_WRITER_INIT
+ }
+ else if (family == AF_INET || family == AF_INET6) {
d_twopt = fstrm_tcp_writer_options_init();
- if (!d_twopt) {
+ if (d_twopt == nullptr) {
throw std::runtime_error("FrameStreamLogger: fstrm_tcp_writer_options_init failed.");
}
try {
- ComboAddress ca(d_address);
+ ComboAddress inetAddress(d_address);
// void return, no error checking.
- fstrm_tcp_writer_options_set_socket_address(d_twopt, ca.toString().c_str());
- fstrm_tcp_writer_options_set_socket_port(d_twopt, std::to_string(ca.getPort()).c_str());
- } catch (PDNSException &e) {
+ fstrm_tcp_writer_options_set_socket_address(d_twopt, inetAddress.toString().c_str());
+ fstrm_tcp_writer_options_set_socket_port(d_twopt, std::to_string(inetAddress.getPort()).c_str());
+ }
+ catch (PDNSException& e) {
throw std::runtime_error("FrameStreamLogger: Unable to use '" + d_address + "': " + e.reason);
}
d_writer = fstrm_tcp_writer_init(d_twopt, d_fwopt);
- if (!d_writer) {
+ if (d_writer == nullptr) {
throw std::runtime_error("FrameStreamLogger: fstrm_tcp_writer_init() failed.");
}
- #endif
- } else {
+#endif
+ }
+ else {
throw std::runtime_error("FrameStreamLogger: family " + std::to_string(family) + " not supported");
}
d_iothropt = fstrm_iothr_options_init();
- if (!d_iothropt) {
+ if (d_iothropt == nullptr) {
throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_init() failed.");
}
throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_queue_model failed: " + std::to_string(res));
}
- const struct {
+ struct setters
+ {
const std::string name;
- fstrm_res (*function)(struct fstrm_iothr_options *, const unsigned int);
- } list[] = {
- { "bufferHint", fstrm_iothr_options_set_buffer_hint },
- { "flushTimeout", fstrm_iothr_options_set_flush_timeout },
- { "inputQueueSize", fstrm_iothr_options_set_input_queue_size },
- { "outputQueueSize", fstrm_iothr_options_set_output_queue_size },
- { "queueNotifyThreshold", fstrm_iothr_options_set_queue_notify_threshold },
- { "setReopenInterval", fstrm_iothr_options_set_reopen_interval }
+ fstrm_res (*function)(struct fstrm_iothr_options*, const unsigned int);
};
-
- for (const auto& i : list) {
- if (options.find(i.name) != options.end() && options.at(i.name)) {
- fstrm_res r = i.function(d_iothropt, options.at(i.name));
- if (r != fstrm_res_success) {
- throw std::runtime_error("FrameStreamLogger: setting " + string(i.name) + " failed: " + std::to_string(r));
+ const std::array<struct setters, 6> list = {{{"bufferHint", fstrm_iothr_options_set_buffer_hint},
+ {"flushTimeout", fstrm_iothr_options_set_flush_timeout},
+ {"inputQueueSize", fstrm_iothr_options_set_input_queue_size},
+ {"outputQueueSize", fstrm_iothr_options_set_output_queue_size},
+ {"queueNotifyThreshold", fstrm_iothr_options_set_queue_notify_threshold},
+ {"setReopenInterval", fstrm_iothr_options_set_reopen_interval}}};
+
+ for (const auto& entry : list) {
+ if (auto option = options.find(entry.name); option != options.end() && option->second != 0) {
+ auto result = entry.function(d_iothropt, option->second);
+ if (result != fstrm_res_success) {
+ throw std::runtime_error("FrameStreamLogger: setting " + string(entry.name) + " failed: " + std::to_string(result));
}
}
}
if (connect) {
d_iothr = fstrm_iothr_init(d_iothropt, &d_writer);
- if (!d_iothr) {
+ if (d_iothr == nullptr) {
throw std::runtime_error("FrameStreamLogger: fstrm_iothr_init() failed.");
}
d_ioqueue = fstrm_iothr_get_input_queue(d_iothr);
- if (!d_ioqueue) {
+ if (d_ioqueue == nullptr) {
throw std::runtime_error("FrameStreamLogger: fstrm_iothr_get_input_queue() failed.");
}
}
- } catch (std::runtime_error &e) {
+ }
+ catch (std::runtime_error& e) {
this->cleanup();
throw;
}
RemoteLoggerInterface::Result FrameStreamLogger::queueData(const std::string& data)
{
- if (!d_ioqueue || !d_iothr) {
+ if ((d_ioqueue == nullptr) || d_iothr == nullptr) {
++d_permanentFailures;
return Result::OtherError;
}
- uint8_t *frame = (uint8_t*)malloc(data.length());
- if (!frame) {
+ uint8_t* frame = (uint8_t*)malloc(data.length()); // NOLINT: it's the API
+ if (frame == nullptr) {
++d_queueFullDrops; // XXX separate count?
return Result::TooLarge;
}
memcpy(frame, data.c_str(), data.length());
- fstrm_res res;
- res = fstrm_iothr_submit(d_iothr, d_ioqueue, frame, data.length(), fstrm_free_wrapper, nullptr);
+ auto res = fstrm_iothr_submit(d_iothr, d_ioqueue, frame, data.length(), fstrm_free_wrapper, nullptr);
if (res == fstrm_res_success) {
// Frame successfully queued.
++d_framesSent;
+ // do not call free here
return Result::Queued;
- } else if (res == fstrm_res_again) {
- free(frame);
+ }
+ if (res == fstrm_res_again) {
+ free(frame); // NOLINT: it's the API
++d_queueFullDrops;
return Result::PipeFull;
- } else {
- // Permanent failure.
- free(frame);
- ++d_permanentFailures;
- return Result::OtherError;
}
+ // Permanent failure.
+ free(frame); // NOLINT: it's the API
+ ++d_permanentFailures;
+ return Result::OtherError;
}
#endif /* HAVE_FSTRM */
#include <fstrm/tcp_writer.h>
#endif
-class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable
+class FrameStreamLogger : public RemoteLoggerInterface
{
public:
- FrameStreamLogger(int family, const std::string& address, bool connect, const std::unordered_map<string,unsigned>& options = std::unordered_map<string,unsigned>());
- ~FrameStreamLogger();
+ FrameStreamLogger(int family, std::string address, bool connect, const std::unordered_map<string, unsigned>& options = std::unordered_map<string, unsigned>());
+ FrameStreamLogger(const FrameStreamLogger&) = delete;
+ FrameStreamLogger(FrameStreamLogger&&) = delete;
+ FrameStreamLogger& operator=(const FrameStreamLogger&) = delete;
+ FrameStreamLogger& operator=(FrameStreamLogger&&) = delete;
+ ~FrameStreamLogger() override;
[[nodiscard]] RemoteLoggerInterface::Result queueData(const std::string& data) override;
[[nodiscard]] std::string address() const override
{
return "dnstap";
}
-
+
[[nodiscard]] std::string toString() override
{
return "FrameStreamLogger to " + d_address + " (" + std::to_string(d_framesSent) + " frames sent, " + std::to_string(d_queueFullDrops) + " dropped, " + std::to_string(d_permanentFailures) + " permanent failures)";
return Stats{.d_queued = d_framesSent,
.d_pipeFull = d_queueFullDrops,
.d_tooLarge = 0,
- .d_otherError = d_permanentFailures
- };
+ .d_otherError = d_permanentFailures};
}
private:
-
const int d_family;
const std::string d_address;
- struct fstrm_iothr_queue *d_ioqueue{nullptr};
- struct fstrm_writer_options *d_fwopt{nullptr};
- struct fstrm_unix_writer_options *d_uwopt{nullptr};
+ struct fstrm_iothr_queue* d_ioqueue{nullptr};
+ struct fstrm_writer_options* d_fwopt{nullptr};
+ struct fstrm_unix_writer_options* d_uwopt{nullptr};
#ifdef HAVE_FSTRM_TCP_WRITER_INIT
- struct fstrm_tcp_writer_options *d_twopt{nullptr};
+ struct fstrm_tcp_writer_options* d_twopt{nullptr};
#endif
- struct fstrm_writer *d_writer{nullptr};
- struct fstrm_iothr_options *d_iothropt{nullptr};
- struct fstrm_iothr *d_iothr{nullptr};
+ struct fstrm_writer* d_writer{nullptr};
+ struct fstrm_iothr_options* d_iothropt{nullptr};
+ struct fstrm_iothr* d_iothr{nullptr};
std::atomic<uint64_t> d_framesSent{0};
std::atomic<uint64_t> d_queueFullDrops{0};
std::atomic<uint64_t> d_permanentFailures{0};
};
#else
-class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable {};
+class FrameStreamLogger : public RemoteLoggerInterface
+{
+ FrameStreamLogger(const FrameStreamLogger&) = delete;
+ FrameStreamLogger(FrameStreamLogger&&) = delete;
+ FrameStreamLogger& operator=(const FrameStreamLogger&) = delete;
+ FrameStreamLogger& operator=(FrameStreamLogger&&) = delete;
+};
#endif /* HAVE_FSTRM */
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
static bool initialized = false;
if (!initialized) {
try {
MOADNSParser moaQuery(true, reinterpret_cast<const char*>(data), size);
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
}
- catch(const PDNSException& e) {
+ catch (const PDNSException& e) {
}
try {
MOADNSParser moaAnswer(false, reinterpret_cast<const char*>(data), size);
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
}
- catch(const PDNSException& e) {
+ catch (const PDNSException& e) {
}
return 0;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
if (size > std::numeric_limits<uint16_t>::max() || size < sizeof(dnsheader)) {
return 0;
/* auth's version */
try {
- static const std::unordered_set<uint16_t> optionsToIgnore{ EDNSOptionCode::COOKIE };
+ static const std::unordered_set<uint16_t> optionsToIgnore{EDNSOptionCode::COOKIE};
PacketCache::canHashPacket(input, optionsToIgnore);
DNSName qname(input.data(), input.size(), sizeof(dnsheader), false);
PacketCache::queryMatches(input, input, qname, optionsToIgnore);
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
}
- catch(const PDNSException& e) {
+ catch (const PDNSException& e) {
}
/* recursor's version */
try {
- static const std::unordered_set<uint16_t> optionsToIgnore{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
+ static const std::unordered_set<uint16_t> optionsToIgnore{EDNSOptionCode::COOKIE, EDNSOptionCode::ECS};
PacketCache::canHashPacket(input, optionsToIgnore);
DNSName qname(input.data(), input.size(), sizeof(dnsheader), false);
PacketCache::queryMatches(input, input, qname, optionsToIgnore);
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
}
- catch(const PDNSException& e) {
+ catch (const PDNSException& e) {
}
return 0;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
std::vector<ProxyProtocolValue> values;
ComboAddress source;
ComboAddress destination;
bool proxy = false;
- bool tcp = false;
+ bool tcp = false;
try {
parseProxyHeader(std::string(reinterpret_cast<const char*>(data), size), proxy, source, destination, tcp, values);
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
}
- catch(const PDNSException& e) {
+ catch (const PDNSException& e) {
}
return 0;
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-
+#include <cstdint>
#include <yahttp/yahttp.hpp>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
static bool initialized = false;
if (!initialized) {
while (zpt.get(drr)) {
}
}
- catch(const std::exception& e) {
+ catch (const std::exception& e) {
}
- catch(const PDNSException& e) {
+ catch (const PDNSException& e) {
}
return 0;
#else
#include <sys/time.h>
+#include <cstddef>
-int gettime(struct timespec *tp, bool needRealTime)
+int gettime(struct timespec *tp, bool /* needRealTime */)
{
struct timeval tv;
#ifndef ENABLE_GSS_TSIG
-std::tuple<size_t, size_t, size_t> GssContext::getCounts() { return std::make_tuple<size_t, size_t, size_t>(0, 0, 0); }
+std::tuple<size_t, size_t, size_t> GssContext::getCounts() { return std::tuple<size_t, size_t, size_t>(0, 0, 0); }
bool GssContext::supported() { return false; }
GssContext::GssContext() :
d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
if (!cred->valid()) {
throw PDNSException("Invalid credential " + cred->d_nameS);
}
- d_cred = cred;
+ d_cred = std::move(cred);
}
~GssSecContext()
std::tuple<size_t, size_t, size_t> GssContext::getCounts()
{
- return std::make_tuple(s_gss_init_creds.lock()->size(), s_gss_accept_creds.lock()->size(), s_gss_sec_context.lock()->size());
+ return {s_gss_init_creds.lock()->size(), s_gss_accept_creds.lock()->size(), s_gss_sec_context.lock()->size()};
}
void GssContext::processError(const std::string& method, OM_uint32 maj, OM_uint32 min)
}
return false;
}
- mac = tmp_mac;
+ mac = std::move(tmp_mac);
return true;
}
#pragma once
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#if __clang_major__ >= 15
+#pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
+#endif
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
+#pragma GCC diagnostic pop
#include <vector>
#include <fstream>
break;
sum += c.second;
bincount += c.second;
-
+
acc(c.first/1000.0, ba::weight=c.second);
for(unsigned int i=0; i < c.second; ++i)
cumulstats(c.first/1000.0, ba::weight=1); // "weighted" does not work for median
if(sum > percentiles.front() * totcumul / 100.0) {
ret.push_back({100.0-percentiles.front(), (double)c.first/1000.0, ba::mean(acc), ba::median(acc), sqrt(ba::variance(acc)), bincount, ba::mean(cumulstats), ba::median(cumulstats)});
-
+
percentiles.pop_front();
acc=decltype(acc)();
bincount=0;
# 'log-histogram' using 1:7 with linespoints title 'Cumulative median latency')"<<"\n";
out<<"# slow-percentile usec-latency-mean usec-latency-max usec-latency-median usec-latency-stddev usec-latency-cumul usec-latency-median-cumul num-queries\n";
-
-
+
+
for(const auto& e : vec) {
out<<e.percentile<<" "<<e.latAverage<<" "<<e.latLimit<<" "<<e.latMedian<<" "<<e.latStddev<<" "<<e.cumulLatAverage<<" "<<e.cumulLatMedian<<" "<<e.count<<"\n";
}
struct AtomicBucket
{
// We need the constructors in this case, since atomics have a disabled copy constructor.
- AtomicBucket() {}
+ AtomicBucket() = default;
AtomicBucket(std::string name, uint64_t boundary, uint64_t val) :
d_name(std::move(name)), d_boundary(boundary), d_count(val) {}
AtomicBucket(const AtomicBucket& rhs) :
#include "iputils.hh"
+#include <fstream>
#include <sys/socket.h>
#include <boost/format.hpp>
-#if HAVE_GETIFADDRS
+#ifdef HAVE_GETIFADDRS
#include <ifaddrs.h>
#endif
/** these functions provide a very lightweight wrapper to the Berkeley sockets API. Errors -> exceptions! */
-static void RuntimeError(std::string&& error)
+static void RuntimeError(const std::string& error)
{
- throw runtime_error(std::move(error));
+ throw runtime_error(error);
}
-static void NetworkErr(std::string&& error)
+static void NetworkErr(const std::string& error)
{
- throw NetworkError(std::move(error));
+ throw NetworkError(error);
}
int SSocket(int family, int type, int flags)
}
}
+void setSocketForcePMTU([[maybe_unused]] int sockfd, [[maybe_unused]] int family)
+{
+ if (family == AF_INET) {
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
+ /* IP_PMTUDISC_DO enables Path MTU discovery and prevents fragmentation */
+ SSetsockopt(sockfd, IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DO);
+#elif defined(IP_DONTFRAG)
+ /* at least this prevents fragmentation */
+ SSetsockopt(sockfd, IPPROTO_IP, IP_DONTFRAG, 1);
+#endif /* defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) */
+ }
+ else {
+#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO)
+ /* IPV6_PMTUDISC_DO enables Path MTU discovery and prevents fragmentation */
+ SSetsockopt(sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, IPV6_PMTUDISC_DO);
+#elif defined(IPV6_DONTFRAG)
+ /* at least this prevents fragmentation */
+ SSetsockopt(sockfd, IPPROTO_IPV6, IPV6_DONTFRAG, 1);
+#endif /* defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) */
+ }
+}
bool setReusePort(int sockfd)
{
setSocketBuffer(fd, SO_SNDBUF, size);
}
+#ifdef __linux__
+static uint32_t raiseSocketBufferToMax(int socket, int optname, const std::string& readMaxFromFile)
+{
+ std::ifstream ifs(readMaxFromFile);
+ if (ifs) {
+ std::string line;
+ if (getline(ifs, line)) {
+ auto max = pdns::checked_stoi<uint32_t>(line);
+ setSocketBuffer(socket, optname, max);
+ return max;
+ }
+ }
+ return 0;
+}
+#endif
+
+uint32_t raiseSocketReceiveBufferToMax([[maybe_unused]] int socket)
+{
+#ifdef __linux__
+ return raiseSocketBufferToMax(socket, SO_RCVBUF, "/proc/sys/net/core/rmem_max");
+#else
+ return 0;
+#endif
+}
+
+uint32_t raiseSocketSendBufferToMax([[maybe_unused]] int socket)
+{
+#ifdef __linux__
+ return raiseSocketBufferToMax(socket, SO_SNDBUF, "/proc/sys/net/core/wmem_max");
+#else
+ return 0;
+#endif
+}
+
std::set<std::string> getListOfNetworkInterfaces()
{
std::set<std::string> result;
-#if HAVE_GETIFADDRS
+#ifdef HAVE_GETIFADDRS
struct ifaddrs *ifaddr;
if (getifaddrs(&ifaddr) == -1) {
return result;
return result;
}
+#ifdef HAVE_GETIFADDRS
std::vector<ComboAddress> getListOfAddressesOfNetworkInterface(const std::string& itf)
{
std::vector<ComboAddress> result;
-#if HAVE_GETIFADDRS
- struct ifaddrs *ifaddr;
+ struct ifaddrs *ifaddr = nullptr;
if (getifaddrs(&ifaddr) == -1) {
return result;
}
}
freeifaddrs(ifaddr);
-#endif
return result;
}
+#else
+std::vector<ComboAddress> getListOfAddressesOfNetworkInterface(const std::string& /* itf */)
+{
+ std::vector<ComboAddress> result;
+ return result;
+}
+#endif // HAVE_GETIFADDRS
-#if HAVE_GETIFADDRS
+#ifdef HAVE_GETIFADDRS
static uint8_t convertNetmaskToBits(const uint8_t* mask, socklen_t len)
{
if (mask == nullptr || len > 16) {
}
#endif /* HAVE_GETIFADDRS */
+#ifdef HAVE_GETIFADDRS
std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& itf)
{
std::vector<Netmask> result;
-#if HAVE_GETIFADDRS
- struct ifaddrs *ifaddr;
+ struct ifaddrs *ifaddr = nullptr;
if (getifaddrs(&ifaddr) == -1) {
return result;
}
}
freeifaddrs(ifaddr);
-#endif
return result;
}
+#else
+std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& /* itf */)
+{
+ std::vector<Netmask> result;
+ return result;
+}
+#endif // HAVE_GETIFADDRS
return rhs.operator<(*this);
}
+ struct addressPortOnlyHash
+ {
+ uint32_t operator()(const ComboAddress& ca) const
+ {
+ const unsigned char* start = nullptr;
+ if (ca.sin4.sin_family == AF_INET) {
+ start = reinterpret_cast<const unsigned char*>(&ca.sin4.sin_addr.s_addr);
+ auto tmp = burtle(start, 4, 0);
+ return burtle(reinterpret_cast<const uint8_t*>(&ca.sin4.sin_port), 2, tmp);
+ }
+ {
+ start = reinterpret_cast<const unsigned char*>(&ca.sin6.sin6_addr.s6_addr);
+ auto tmp = burtle(start, 16, 0);
+ return burtle(reinterpret_cast<const unsigned char*>(&ca.sin6.sin6_port), 2, tmp);
+ }
+ }
+ };
+
struct addressOnlyHash
{
uint32_t operator()(const ComboAddress& ca) const
sin4.sin_port = 0;
if(makeIPv4sockaddr(str, &sin4)) {
sin6.sin6_family = AF_INET6;
- if(makeIPv6sockaddr(str, &sin6) < 0)
+ if(makeIPv6sockaddr(str, &sin6) < 0) {
throw PDNSException("Unable to convert presentation address '"+ str +"'");
+ }
}
if(!sin4.sin_port) // 'str' overrides port!
return toStringWithPortExcept(53);
}
+ [[nodiscard]] string toStructuredLogString() const
+ {
+ return toStringWithPort();
+ }
+
string toByteString() const
{
if (isIPv4()) {
void truncate(unsigned int bits) noexcept;
- uint16_t getPort() const
+ uint16_t getNetworkOrderPort() const noexcept
{
- return ntohs(sin4.sin_port);
+ return sin4.sin_port;
+ }
+ uint16_t getPort() const noexcept
+ {
+ return ntohs(getNetworkOrderPort());
}
-
void setPort(uint16_t port)
{
sin4.sin_port = htons(port);
Netmask(const ComboAddress& network, uint8_t bits=0xff): d_network(network)
{
d_network.sin4.sin_port = 0;
- setBits(network.isIPv4() ? std::min(bits, static_cast<uint8_t>(32)) : std::min(bits, static_cast<uint8_t>(128)));
+ setBits(bits);
}
Netmask(const sockaddr_in* network, uint8_t bits = 0xff): d_network(network)
{
d_network.sin4.sin_port = 0;
- setBits(std::min(bits, static_cast<uint8_t>(32)));
+ setBits(bits);
}
Netmask(const sockaddr_in6* network, uint8_t bits = 0xff): d_network(network)
{
d_network.sin4.sin_port = 0;
- setBits(std::min(bits, static_cast<uint8_t>(128)));
+ setBits(bits);
}
void setBits(uint8_t value)
{
- d_bits = value;
+ d_bits = d_network.isIPv4() ? std::min(value, static_cast<uint8_t>(32U)) : std::min(value, static_cast<uint8_t>(128U));
if (d_bits < 32) {
d_mask = ~(0xFFFFFFFF >> d_bits);
}
//<! Returns "best match" for key_type, which might not be value
- const node_type* lookup(const key_type& value) const {
+ [[nodiscard]] node_type* lookup(const key_type& value) const {
uint8_t max_bits = value.getBits();
return lookupImpl(value, max_bits);
}
//<! Perform best match lookup for value, using at most max_bits
- const node_type* lookup(const ComboAddress& value, int max_bits = 128) const {
+ [[nodiscard]] node_type* lookup(const ComboAddress& value, int max_bits = 128) const {
uint8_t addr_bits = value.getBits();
if (max_bits < 0 || max_bits > addr_bits) {
max_bits = addr_bits;
}
//<! checks whether the container is empty.
- bool empty() const {
+ [[nodiscard]] bool empty() const {
return (d_size == 0);
}
}
//<! swaps the contents with another NetmaskTree
- void swap(NetmaskTree& rhs) {
+ void swap(NetmaskTree& rhs) noexcept
+ {
std::swap(d_root, rhs.d_root);
std::swap(d_left, rhs.d_left);
std::swap(d_size, rhs.d_size);
private:
- const node_type* lookupImpl(const key_type& value, uint8_t max_bits) const {
+ [[nodiscard]] node_type* lookupImpl(const key_type& value, uint8_t max_bits) const {
TreeNode *node = nullptr;
if (value.isIPv4())
class NetmaskGroup
{
public:
- NetmaskGroup() noexcept {
- }
+ NetmaskGroup() noexcept = default;
//! If this IP address is matched by any of the classes within
tree.erase(nm);
}
+ void deleteMasks(const NetmaskGroup& group)
+ {
+ for (const auto& entry : group.tree) {
+ deleteMask(entry.first);
+ }
+ }
+
void deleteMask(const std::string& ip)
{
if (!ip.empty())
return str.str();
}
- void toStringVector(vector<string>* vec) const
+ std::vector<std::string> toStringVector() const
{
- for(auto iter = tree.begin(); iter != tree.end(); ++iter) {
- vec->push_back((iter->second ? "" : "!") + iter->first.toString());
+ std::vector<std::string> out;
+ out.reserve(tree.size());
+ for (const auto& entry : tree) {
+ out.push_back((entry.second ? "" : "!") + entry.first.toString());
}
+ return out;
}
void toMasks(const string &ips)
d_addr.sin4.sin_port = 0; // this guarantees d_network compares identical
}
- AddressAndPortRange(ComboAddress ca, uint8_t addrMask, uint8_t portMask = 0): d_addr(std::move(ca)), d_addrMask(addrMask), d_portMask(portMask)
+ AddressAndPortRange(ComboAddress ca, uint8_t addrMask, uint8_t portMask = 0) :
+ d_addr(ca), d_addrMask(addrMask), d_portMask(portMask)
{
if (!d_addr.isIPv4()) {
d_portMask = 0;
int SListen(int sockfd, int limit);
int SSetsockopt(int sockfd, int level, int opname, int value);
void setSocketIgnorePMTU(int sockfd, int family);
+void setSocketForcePMTU(int sockfd, int family);
bool setReusePort(int sockfd);
#if defined(IP_PKTINFO)
void setSocketBuffer(int fd, int optname, uint32_t size);
void setSocketReceiveBuffer(int fd, uint32_t size);
void setSocketSendBuffer(int fd, uint32_t size);
+uint32_t raiseSocketReceiveBufferToMax(int socket);
+uint32_t raiseSocketSendBufferToMax(int socket);
}
// Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
+ // NOLINTNEXTLINE(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddress& primary, const DNSName& zone, const DNSRecord& oursr,
uint16_t xfrTimeout, bool totalTimeout,
const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes)
s.connect(primary, xfrTimeout);
time_t elapsed = timeoutChecker();
+ // coverity[store_truncates_time_t]
s.writenWithTimeout(msg.data(), msg.size(), xfrTimeout - elapsed);
// CURRENT PRIMARY SOA
const unsigned int expectedSOAForIXFR = 3;
unsigned int primarySOACount = 0;
+ std::string state;
for (;;) {
// IXFR or AXFR style end reached? We don't want to process trailing data after the closing SOA
if (style == AXFR && primarySOACount == expectedSOAForAXFR) {
+ state = "AXFRdone";
break;
}
- else if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
+ if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
+ state = "IXFRdone";
break;
}
elapsed = timeoutChecker();
- if (s.readWithTimeout(reinterpret_cast<char*>(&len), sizeof(len), static_cast<int>(xfrTimeout - elapsed)) != sizeof(len)) {
+ try {
+ const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
+ const struct timeval idleTime = remainingTime;
+ readn2WithTimeout(s.getHandle(), &len, sizeof(len), idleTime, remainingTime, false);
+ }
+ catch (const runtime_error& ex) {
+ state = ex.what();
break;
}
len = ntohs(len);
if (len == 0) {
+ state = "zeroLen";
break;
}
+ // Currently no more break statements after this
if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len) {
throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toLogString()+"' from primary "+primary.toStringWithPort());
reply.resize(len);
elapsed = timeoutChecker();
- const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
+ const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
const struct timeval idleTime = remainingTime;
- readn2WithTimeout(s.getHandle(), &reply.at(0), len, idleTime, remainingTime, false);
+ readn2WithTimeout(s.getHandle(), reply.data(), len, idleTime, remainingTime, false);
receivedBytes += len;
MOADNSParser mdp(false, reply);
// we are up to date
return ret;
}
- primarySOA = sr;
+ primarySOA = std::move(sr);
++primarySOACount;
} else if (r.first.d_type == QType::SOA) {
auto sr = getRR<SOARecordContent>(r.first);
if(r.first.d_type == QType::OPT)
continue;
- throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).toString()+") in non-answer section ("+std::to_string(r.first.d_place)+")in IXFR response for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort());
+ throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).toString()+") in non-answer section ("+std::to_string(r.first.d_place)+") in IXFR response for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort());
}
r.first.d_name.makeUsRelative(zone);
switch (style) {
case IXFR:
if (primarySOACount != expectedSOAForIXFR) {
- throw std::runtime_error("Incomplete IXFR transfer for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
+ throw std::runtime_error("Incomplete IXFR transfer (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
}
break;
case AXFR:
if (primarySOACount != expectedSOAForAXFR){
- throw std::runtime_error("Incomplete AXFR style transfer for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
+ throw std::runtime_error("Incomplete AXFR style transfer (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
}
break;
case Unknown:
- throw std::runtime_error("Incomplete XFR for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
+ throw std::runtime_error("Incomplete XFR (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
break;
}
if (!domainStats.empty()) {
stats<<"# HELP "<<prefix<<"soa_serial The SOA serial number of a domain"<<std::endl;
stats<<"# TYPE "<<prefix<<"soa_serial gauge"<<std::endl;
- stats<<"# HELP "<<prefix<<"soa_checks_total Number of times a SOA check at the master was attempted"<<std::endl;
+ stats << "# HELP " << prefix << "soa_checks_total Number of times a SOA check at the primary was attempted" << std::endl;
stats<<"# TYPE "<<prefix<<"soa_checks_total counter"<<std::endl;
- stats<<"# HELP "<<prefix<<"soa_checks_failed_total Number of times a SOA check at the master failed"<<std::endl;
+ stats << "# HELP " << prefix << "soa_checks_failed_total Number of times a SOA check at the primary failed" << std::endl;
stats<<"# TYPE "<<prefix<<"soa_checks_failed_total counter"<<std::endl;
stats<<"# HELP "<<prefix<<"soa_inqueries_total Number of times a SOA query was received"<<std::endl;
stats<<"# TYPE "<<prefix<<"soa_inqueries_total counter"<<std::endl;
stats<<prefix<<"ixfr_failures_total{domain=\""<<d.first<<"\"} "<<d.second.numIXFRFailures<<std::endl;
}
+ if (!notimpStats.empty()) {
+ stats<<"# HELP "<<prefix<<"notimp An unimplemented opcode"<<std::endl;
+ stats<<"# TYPE "<<prefix<<"notimp counter"<<std::endl;
+ }
+
+ for (std::size_t i = 0; i < notimpStats.size() ; i++) {
+ auto val = notimpStats.at(i).load();
+
+ if (val > 0) {
+ stats<<prefix<<"notimp{opcode=\""<<Opcode::to_s(i)<<"\"} "<<val<<std::endl;
+ }
+ }
+
stats<<"# HELP "<<prefix<<"unknown_domain_inqueries_total Number of queries received for domains unknown to us"<<std::endl;
stats<<"# TYPE "<<prefix<<"unknown_domain_inqueries_total counter"<<std::endl;
stats<<prefix<<"unknown_domain_inqueries_total "<<progStats.unknownDomainInQueries<<std::endl;
#include <map>
#include <string>
+#include "dns.hh"
#include "dnsname.hh"
#include "pdnsexception.hh"
progStats.unknownDomainInQueries += 1;
}
+ void incrementNotImplemented(uint8_t opcode)
+ {
+ notimpStats.at(opcode) ++;
+ }
+
private:
class perDomainStat {
public:
};
std::map<DNSName, perDomainStat> domainStats;
+ std::array<std::atomic<uint64_t>, 16> notimpStats{};
programStats progStats;
std::map<DNSName, perDomainStat>::iterator getRegisteredDomain(const DNSName& d) {
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "dns.hh"
+#include "dnsparser.hh"
+#include <stdexcept>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <dirent.h>
#include <queue>
#include <condition_variable>
+#include <thread>
+#include <chrono>
#include "ixfr.hh"
#include "ixfrutils.hh"
#include "axfr-retriever.hh"
#include "misc.hh"
#include "iputils.hh"
#include "lock.hh"
+#include "communicator.hh"
+#include "query-local-address.hh"
#include "logger.hh"
#include "ixfrdist-stats.hh"
#include "ixfrdist-web.hh"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
#include <yaml-cpp/yaml.h>
+#pragma GCC diagnostic pop
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
+#include "auth-zonecache.hh"
/* BEGIN Needed because of deeper dependencies */
#include "arguments.hh"
#include "statbag.hh"
StatBag S;
+// NOLINTNEXTLINE(readability-identifier-length)
+AuthPacketCache PC;
+// NOLINTNEXTLINE(readability-identifier-length)
+AuthQueryCache QC;
+AuthZoneCache g_zoneCache;
ArgvMap &arg()
{
// Why a struct? This way we can add more options to a domain in the future
struct ixfrdistdomain_t {
- set<ComboAddress> masters; // A set so we can do multiple master addresses in the future
+ set<ComboAddress> primaries; // A set so we can do multiple primary addresses in the future
+ std::set<ComboAddress> notify; // Set of addresses to forward NOTIFY to
+ uint32_t maxSOARefresh{0}; // Cap SOA refresh value to the given value in seconds
};
// This contains the configuration for each domain
// Map domains and their data
static LockGuarded<std::map<DNSName, std::shared_ptr<ixfrinfo_t>>> g_soas;
+// Queue of received NOTIFYs, already verified against their primary IPs
+// Lazily implemented as a set
+static LockGuarded<std::set<DNSName>> g_notifiesReceived;
+
+// Queue of outgoing NOTIFY
+static LockGuarded<NotificationQueue> g_notificationQueue;
+
// Condition variable for TCP handling
static std::condition_variable g_tcpHandlerCV;
static std::queue<pair<int, ComboAddress>> g_tcpRequestFDs;
static bool g_exiting = false;
-static NetmaskGroup g_acl;
+static NetmaskGroup g_acl; // networks that can QUERY us
+static NetmaskGroup g_notifySources; // networks (well, IPs) that can NOTIFY us
static bool g_compress = false;
static ixfrdistStats g_stats;
static void cleanUpDomain(const DNSName& domain, const uint16_t& keep, const string& workdir) {
string dir = workdir + "/" + domain.toString();
- DIR *dp;
- dp = opendir(dir.c_str());
- if (dp == nullptr) {
- return;
- }
vector<uint32_t> zoneVersions;
- struct dirent *d;
- while ((d = readdir(dp)) != nullptr) {
- if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) {
- continue;
+ auto directoryError = pdns::visit_directory(dir, [&zoneVersions]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+ if (name != "." && name != "..") {
+ try {
+ auto version = pdns::checked_stoi<uint32_t>(std::string(name));
+ zoneVersions.push_back(version);
+ }
+ catch (...) {
+ }
}
- zoneVersions.push_back(std::stoi(d->d_name));
+ return true;
+ });
+
+ if (directoryError) {
+ return;
}
- closedir(dp);
+
g_log<<Logger::Info<<"Found "<<zoneVersions.size()<<" versions of "<<domain<<", asked to keep "<<keep<<", ";
if (zoneVersions.size() <= keep) {
g_log<<Logger::Info<<"not cleaning up"<<endl;
// FIXME: also report zone size?
}
-static void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) {
+static void sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t notificationId)
+{
+ std::vector<std::string> meta;
+ std::vector<uint8_t> packet;
+ DNSPacketWriter packetWriter(packet, domain, QType::SOA, 1, Opcode::Notify);
+ packetWriter.getHeader()->id = notificationId;
+ packetWriter.getHeader()->aa = true;
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (sendto(sock, packet.data(), packet.size(), 0, reinterpret_cast<const struct sockaddr*>(&remote), remote.getSocklen()) < 0) {
+ throw std::runtime_error("Unable to send notify to " + remote.toStringWithPort() + ": " + stringerror());
+ }
+}
+
+static void communicatorReceiveNotificationAnswers(const int sock4, const int sock6)
+{
+ std::set<int> fds = {sock4};
+ if (sock6 > 0) {
+ fds.insert(sock6);
+ }
+ ComboAddress from;
+ std::array<char, 1500> buffer{};
+ int sock{-1};
+
+ // receive incoming notification answers on the nonblocking sockets and take them off the list
+ while (waitForMultiData(fds, 0, 0, &sock) > 0) {
+ Utility::socklen_t fromlen = sizeof(from);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ const auto size = recvfrom(sock, buffer.data(), buffer.size(), 0, reinterpret_cast<struct sockaddr*>(&from), &fromlen);
+ if (size < 0) {
+ break;
+ }
+ DNSPacket packet(true);
+ packet.setRemote(&from);
+
+ if (packet.parse(buffer.data(), (size_t)size) < 0) {
+ g_log << Logger::Warning << "Unable to parse SOA notification answer from " << packet.getRemote() << endl;
+ continue;
+ }
+
+ if (packet.d.rcode != 0) {
+ g_log << Logger::Warning << "Received unsuccessful notification report for '" << packet.qdomain << "' from " << from.toStringWithPort() << ", error: " << RCode::to_s(packet.d.rcode) << endl;
+ }
+
+ if (g_notificationQueue.lock()->removeIf(from, packet.d.id, packet.qdomain)) {
+ g_log << Logger::Notice << "Removed from notification list: '" << packet.qdomain << "' to " << from.toStringWithPort() << " " << (packet.d.rcode != 0 ? RCode::to_s(packet.d.rcode) : "(was acknowledged)") << endl;
+ }
+ else {
+ g_log << Logger::Warning << "Received spurious notify answer for '" << packet.qdomain << "' from " << from.toStringWithPort() << endl;
+ }
+ }
+}
+
+static void communicatorSendNotifications(const int sock4, const int sock6)
+{
+ DNSName domain;
+ string destinationIp;
+ uint16_t notificationId = 0;
+ bool purged{false};
+
+ while (g_notificationQueue.lock()->getOne(domain, destinationIp, ¬ificationId, purged)) {
+ if (!purged) {
+ ComboAddress remote(destinationIp, 53); // default to 53
+ if (remote.sin4.sin_family == AF_INET) {
+ sendNotification(sock4, domain, remote, notificationId);
+ } else if (sock6 > 0) {
+ sendNotification(sock6, domain, remote, notificationId);
+ } else {
+ g_log << Logger::Warning << "Unable to notify " << destinationIp << " for " << domain << " as v6 support is not enabled" << std::endl;
+ }
+ } else {
+ g_log << Logger::Warning << "Notification for " << domain << " to " << destinationIp << " failed after retries" << std::endl;
+ }
+ }
+}
+
+static void communicatorThread()
+{
+ setThreadName("ixfrdist/communicator");
+ auto sock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true);
+ auto sock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true);
+
+ if (sock4 < 0) {
+ throw std::runtime_error("Unable to create local query socket");
+ }
+ // sock6 can be negative if there is no v6 support, but this is handled later while sending notifications
+
+ while (true) {
+ if (g_exiting) {
+ g_log << Logger::Notice << "Communicator thread stopped" << std::endl;
+ break;
+ }
+ communicatorReceiveNotificationAnswers(sock4, sock6);
+ communicatorSendNotifications(sock4, sock6);
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ if (sock4 >= 0) {
+ closesocket(sock4);
+ }
+ if (sock6 >= 0) {
+ closesocket(sock6);
+ }
+}
+
+static void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) { // NOLINT(readability-function-cognitive-complexity) 13400 https://github.com/PowerDNS/pdns/issues/13400 Habbie: ixfrdist: reduce complexity
setThreadName("ixfrdist/update");
std::map<DNSName, time_t> lastCheck;
}
auto& zoneLastCheck = lastCheck[domain];
- if ((current_soa != nullptr && now - zoneLastCheck < current_soa->d_st.refresh) || // Only check if we have waited `refresh` seconds
- (current_soa == nullptr && now - zoneLastCheck < soaRetry)) { // Or if we could not get an update at all still, every 30 seconds
+ uint32_t refresh = soaRetry; // default if we don't get an update at all
+ if (current_soa != nullptr) {
+ // Check every `refresh` seconds as advertised in the SOA record
+ refresh = current_soa->d_st.refresh;
+ if (domainConfig.second.maxSOARefresh > 0) {
+ // Cap refresh value to the configured one if any
+ refresh = std::min(refresh, domainConfig.second.maxSOARefresh);
+ }
+ }
+
+
+ if (now - zoneLastCheck < refresh && g_notifiesReceived.lock()->erase(domain) == 0) {
continue;
}
- // TODO Keep track of 'down' masters
- set<ComboAddress>::const_iterator it(domainConfig.second.masters.begin());
- std::advance(it, dns_random(domainConfig.second.masters.size()));
- ComboAddress master = *it;
+ // TODO Keep track of 'down' primaries
+ set<ComboAddress>::const_iterator it(domainConfig.second.primaries.begin());
+ std::advance(it, dns_random(domainConfig.second.primaries.size()));
+ ComboAddress primary = *it;
string dir = workdir + "/" + domain.toString();
- g_log<<Logger::Info<<"Attempting to retrieve SOA Serial update for '"<<domain<<"' from '"<<master.toStringWithPort()<<"'"<<endl;
+ g_log << Logger::Info << "Attempting to retrieve SOA Serial update for '" << domain << "' from '" << primary.toStringWithPort() << "'" << endl;
shared_ptr<const SOARecordContent> sr;
try {
zoneLastCheck = now;
g_stats.incrementSOAChecks(domain);
- auto newSerial = getSerialFromMaster(master, domain, sr); // TODO TSIG
+ auto newSerial = getSerialFromPrimary(primary, domain, sr); // TODO TSIG
if(current_soa != nullptr) {
- g_log<<Logger::Info<<"Got SOA Serial for "<<domain<<" from "<<master.toStringWithPort()<<": "<< newSerial<<", had Serial: "<<current_soa->d_st.serial;
+ g_log << Logger::Info << "Got SOA Serial for " << domain << " from " << primary.toStringWithPort() << ": " << newSerial << ", had Serial: " << current_soa->d_st.serial;
if (newSerial == current_soa->d_st.serial) {
g_log<<Logger::Info<<", not updating."<<endl;
continue;
g_log<<Logger::Info<<", will update."<<endl;
}
} catch (runtime_error &e) {
- g_log<<Logger::Warning<<"Unable to get SOA serial update for '"<<domain<<"' from master "<<master.toStringWithPort()<<": "<<e.what()<<endl;
+ g_log << Logger::Warning << "Unable to get SOA serial update for '" << domain << "' from primary " << primary.toStringWithPort() << ": " << e.what() << endl;
g_stats.incrementSOAChecksFailed(domain);
continue;
}
// Now get the full zone!
g_log<<Logger::Info<<"Attempting to receive full zonedata for '"<<domain<<"'"<<endl;
- ComboAddress local = master.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
+ ComboAddress local = primary.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
TSIGTriplet tt;
// The *new* SOA
uint32_t soaTTL = 0;
records_t records;
try {
- AXFRRetriever axfr(master, domain, tt, &local);
+ AXFRRetriever axfr(primary, domain, tt, &local);
uint32_t nrecords=0;
Resolver::res_t nop;
vector<DNSRecord> chunk;
g_log<<Logger::Debug<<"Zone "<<domain<<" previously contained "<<(oldZoneInfo ? oldZoneInfo->latestAXFR.size() : 0)<<" entries, "<<records.size()<<" now"<<endl;
ixfrInfo->latestAXFR = std::move(records);
- ixfrInfo->soa = soa;
+ ixfrInfo->soa = std::move(soa);
ixfrInfo->soaTTL = soaTTL;
updateCurrentZoneInfo(domain, ixfrInfo);
} catch (PDNSException &e) {
} /* while (true) */
} /* updateThread */
-static bool checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
- vector<string> info_msg;
+enum class ResponseType {
+ Unknown,
+ ValidQuery,
+ RefusedOpcode,
+ RefusedQuery,
+ EmptyNoError
+};
- g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" query from "<<saddr.toStringWithPort()<<endl;
+static ResponseType maybeHandleNotify(const MOADNSParser& mdp, const ComboAddress& saddr, const string& logPrefix="") {
+ if (mdp.d_header.opcode != Opcode::Notify) { // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) opcode is 4 bits, this is not a dangerous conversion
+ return ResponseType::Unknown;
+ }
+
+ g_log<<Logger::Info<<logPrefix<<"NOTIFY for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" "<< Opcode::to_s(mdp.d_header.opcode) <<" from "<<saddr.toStringWithPort()<<endl;
- if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
- info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR})");
+ auto found = g_domainConfigs.find(mdp.d_qname);
+ if (found == g_domainConfigs.end()) {
+ g_log<<Logger::Info<<("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for notification")<<endl;
+ return ResponseType::RefusedQuery;
}
- if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
- info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR,AXFR})");
+ auto primaries = found->second.primaries;
+
+ bool primaryFound = false;
+
+ for (const auto& primary : primaries) {
+ if (ComboAddress::addressOnlyEqual()(saddr, primary)) {
+ primaryFound = true;
+ break;
+ }
}
- {
- if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) {
- info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
+ if (primaryFound) {
+ g_notifiesReceived.lock()->insert(mdp.d_qname);
+
+ if (!found->second.notify.empty()) {
+ for (const auto& address : found->second.notify) {
+ g_log << Logger::Debug << logPrefix << "Queuing notification for " << mdp.d_qname << " to " << address.toStringWithPort() << std::endl;
+ g_notificationQueue.lock()->add(mdp.d_qname, address);
+ }
}
- else {
- const auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
- if (zoneInfo == nullptr) {
- info_msg.push_back("Domain has not been transferred yet");
+ return ResponseType::EmptyNoError;
+ }
+
+ return ResponseType::RefusedQuery;
+}
+
+static ResponseType checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
+ vector<string> info_msg;
+
+ auto ret = ResponseType::ValidQuery;
+
+ g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" query from "<<saddr.toStringWithPort()<<endl;
+
+ if (mdp.d_header.opcode != Opcode::Query) { // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) opcode is 4 bits, this is not a dangerous conversion
+ info_msg.push_back("Opcode is unsupported (" + Opcode::to_s(mdp.d_header.opcode) + "), expected QUERY"); // note that we also emit this for a NOTIFY from a wrong source
+ ret = ResponseType::RefusedOpcode;
+ }
+ else {
+ if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
+ info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR})");
+ ret = ResponseType::RefusedQuery;
+ }
+
+ if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
+ info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR,AXFR})");
+ ret = ResponseType::RefusedQuery;
+ }
+
+ {
+ if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) {
+ info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
+ ret = ResponseType::RefusedQuery;
+ }
+ else {
+ const auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
+ if (zoneInfo == nullptr) {
+ info_msg.emplace_back("Domain has not been transferred yet");
+ ret = ResponseType::RefusedQuery;
+ }
}
}
}
- if (!info_msg.empty()) {
- g_log<<Logger::Warning<<logPrefix<<"Refusing "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" query from "<<saddr.toStringWithPort();
+ if (!info_msg.empty()) { // which means ret is not SOA
+ g_log<<Logger::Warning<<logPrefix<<"Refusing "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" "<< Opcode::to_s(mdp.d_header.opcode) <<" from "<<saddr.toStringWithPort();
g_log<<Logger::Warning<<": ";
bool first = true;
for (const auto& s : info_msg) {
g_log<<Logger::Warning<<s;
}
g_log<<Logger::Warning<<endl;
- return false;
+ // fall through to return below
}
+ return ret;
+}
+
+/*
+ * Returns a vector<uint8_t> that represents the full empty NOERROR response.
+ * QNAME is read from mdp.
+ */
+static bool makeEmptyNoErrorPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
+ DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
+ pw.getHeader()->opcode = mdp.d_header.opcode;
+ pw.getHeader()->id = mdp.d_header.id;
+ pw.getHeader()->rd = mdp.d_header.rd;
+ pw.getHeader()->qr = 1;
+ pw.getHeader()->aa = 1;
+
+ pw.commit();
+
return true;
}
}
DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
+ pw.getHeader()->opcode = mdp.d_header.opcode;
pw.getHeader()->id = mdp.d_header.id;
pw.getHeader()->rd = mdp.d_header.rd;
pw.getHeader()->qr = 1;
+ pw.getHeader()->aa = 1;
pw.startRecord(mdp.d_qname, QType::SOA, zoneInfo->soaTTL);
zoneInfo->soa->toPacket(pw);
*/
static bool makeRefusedPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
+ pw.getHeader()->opcode = mdp.d_header.opcode;
pw.getHeader()->id = mdp.d_header.id;
pw.getHeader()->rd = mdp.d_header.rd;
pw.getHeader()->qr = 1;
return true;
}
+/*
+ * Returns a vector<uint8_t> that represents the full NOTIMP response to a
+ * query. QNAME and type are read from mdp.
+ */
+static bool makeNotimpPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
+ DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
+ pw.getHeader()->opcode = mdp.d_header.opcode;
+ pw.getHeader()->id = mdp.d_header.id;
+ pw.getHeader()->rd = mdp.d_header.rd;
+ pw.getHeader()->qr = 1;
+ pw.getHeader()->rcode = RCode::NotImp;
+
+ return true;
+}
+
static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<const SOARecordContent>& soa, uint32_t soaTTL) {
vector<uint8_t> packet;
DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
return true;
}
-static bool allowedByACL(const ComboAddress& addr) {
+static bool allowedByACL(const ComboAddress& addr, bool forNotify = false) {
+ if (forNotify) {
+ return g_notifySources.match(addr);
+ }
+
return g_acl.match(addr);
}
-static void handleUDPRequest(int fd, boost::any&) {
+static void handleUDPRequest(int fd, boost::any& /*unused*/)
+try
+{
// TODO make the buffer-size configurable
char buf[4096];
ComboAddress saddr;
return;
}
- if (!allowedByACL(saddr)) {
- g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
+ if (!allowedByACL(saddr, true) && !allowedByACL(saddr, false)) {
+ g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" did not match any valid query or NOTIFY source, dropping"<<endl;
return;
}
- MOADNSParser mdp(true, string(buf, res));
+ MOADNSParser mdp(true, string(&buf[0], static_cast<size_t>(res)));
vector<uint8_t> packet;
- if (checkQuery(mdp, saddr)) {
+
+ ResponseType respt = ResponseType::Unknown;
+
+ if (allowedByACL(saddr, true)) {
+ respt = maybeHandleNotify(mdp, saddr);
+ }
+ else if (!allowedByACL(saddr)) {
+ g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
+ return;
+ }
+
+ if (respt == ResponseType::Unknown) {
+ // query was not handled yet (so not a valid NOTIFY)
+ respt = checkQuery(mdp, saddr);
+ }
+ if (respt == ResponseType::ValidQuery) {
/* RFC 1995 Section 2
* Transport of a query may be by either UDP or TCP. If an IXFR query
* is via UDP, the IXFR server may attempt to reply using UDP if the
*/
g_stats.incrementSOAinQueries(mdp.d_qname); // FIXME: this also counts IXFR queries (but the response is the same as to a SOA query)
makeSOAPacket(mdp, packet);
- } else {
+ } else if (respt == ResponseType::EmptyNoError) {
+ makeEmptyNoErrorPacket(mdp, packet);
+ } else if (respt == ResponseType::RefusedQuery) {
g_stats.incrementUnknownDomainInQueries(mdp.d_qname);
makeRefusedPacket(mdp, packet);
+ } else if (respt == ResponseType::RefusedOpcode) {
+ g_stats.incrementNotImplemented(mdp.d_header.opcode);
+ makeNotimpPacket(mdp, packet);
}
if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*) &saddr, fromlen) < 0) {
}
return;
}
+catch(std::exception& e) {
+ return;
+}
+
static void handleTCPRequest(int fd, boost::any&) {
ComboAddress saddr;
return;
}
- if (!allowedByACL(saddr)) {
+ // we allow the connection if this is a legit client or a legit NOTIFY source
+ // need to check per-operation later
+ if (!allowedByACL(saddr) && !allowedByACL(saddr, true)) {
g_log<<Logger::Warning<<"TCP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
close(cfd);
return;
try {
MOADNSParser mdp(true, string(buf, res));
- if (!checkQuery(mdp, saddr, false, prefix)) {
+ ResponseType respt = ResponseType::Unknown;
+
+ // this code is duplicated from the UDP path
+ if (allowedByACL(saddr, true)) {
+ respt = maybeHandleNotify(mdp, saddr);
+ }
+ else if (!allowedByACL(saddr)) {
close(cfd);
continue;
}
- if (mdp.d_qtype == QType::SOA) {
- vector<uint8_t> packet;
+ if (respt == ResponseType::Unknown) {
+ respt = checkQuery(mdp, saddr, false, prefix);
+ }
+
+ if (respt != ResponseType::ValidQuery && respt != ResponseType::EmptyNoError) { // on TCP, we currently do not bother with sending useful errors
+ close(cfd);
+ continue;
+ }
+
+ vector<uint8_t> packet;
+
+ if (respt == ResponseType::EmptyNoError) {
+ bool ret = makeEmptyNoErrorPacket(mdp, packet);
+ if (!ret) {
+ close(cfd);
+ continue;
+ }
+ sendPacketOverTCP(cfd, packet);
+ }
+ else if (mdp.d_qtype == QType::SOA) {
bool ret = makeSOAPacket(mdp, packet);
if (!ret) {
close(cfd);
}
try {
if (!domain["master"]) {
- g_log<<Logger::Error<<"Domain '"<<domain["domain"].as<string>()<<"' has no master configured!"<<endl;
+ g_log << Logger::Error << "Domain '" << domain["domain"].as<string>() << "' has no primary configured!" << endl;
retval = false;
continue;
}
domain["master"].as<ComboAddress>();
+
+ auto notifySource = domain["master"].as<ComboAddress>();
+
+ g_notifySources.addMask(notifySource);
} catch (const runtime_error &e) {
- g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"' master address: "<<e.what()<<endl;
+ g_log << Logger::Error << "Unable to read domain '" << domain["domain"].as<string>() << "' primary address: " << e.what() << endl;
retval = false;
}
+ if (domain["max-soa-refresh"]) {
+ try {
+ domain["max-soa-refresh"].as<uint32_t>();
+ } catch (const runtime_error &e) {
+ g_log<<Logger::Error<<"Unable to read 'max-soa-refresh' value for domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
+ }
+ }
}
} else {
g_log<<Logger::Error<<"No domains configured"<<endl;
return retval;
}
-int main(int argc, char** argv) {
- g_log.setLoglevel(Logger::Notice);
- g_log.toConsole(Logger::Notice);
- g_log.setPrefixed(true);
- g_log.disableSyslog(true);
- g_log.setTimestamps(false);
+struct IXFRDistConfiguration
+{
+ set<int> listeningSockets;
+ NetmaskGroup wsACL;
+ ComboAddress wsAddr;
+ std::string wsLogLevel{"normal"};
+ std::string workDir;
+ const struct passwd* userInfo{nullptr};
+ uint32_t axfrMaxRecords{0};
+ uint16_t keep{0};
+ uint16_t axfrTimeout{0};
+ uint16_t failedSOARetry{0};
+ uint16_t tcpInThreads{0};
+ uid_t uid{0};
+ gid_t gid{0};
+ bool shouldExit{false};
+};
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity)
+static std::optional<IXFRDistConfiguration> parseConfiguration(int argc, char** argv, FDMultiplexer& fdm)
+{
+ IXFRDistConfiguration configuration;
po::variables_map g_vm;
+ std::string configPath;
+
try {
po::options_description desc("IXFR distribution tool");
desc.add_options()
if (g_vm.count("help") > 0) {
usage(desc);
- return EXIT_SUCCESS;
+ configuration.shouldExit = true;
+ return configuration;
}
if (g_vm.count("version") > 0) {
cout<<"ixfrdist "<<VERSION<<endl;
- return EXIT_SUCCESS;
+ configuration.shouldExit = true;
+ return configuration;
}
- } catch (po::error &e) {
+
+ configPath = g_vm["config"].as<string>();
+ }
+ catch (const po::error &e) {
g_log<<Logger::Error<<e.what()<<". See `ixfrdist --help` for valid options"<<endl;
- return(EXIT_FAILURE);
+ return std::nullopt;
+ }
+ catch (const std::exception& exp) {
+ g_log<<Logger::Error<<exp.what()<<". See `ixfrdist --help` for valid options"<<endl;
+ return std::nullopt;
}
bool had_error = false;
g_log<<Logger::Notice<<"IXFR distributor version "<<VERSION<<" starting up!"<<endl;
- YAML::Node config;
- if (!parseAndCheckConfig(g_vm["config"].as<string>(), config)) {
- // parseAndCheckConfig already logged whatever was wrong
- return EXIT_FAILURE;
- }
+ try {
+ YAML::Node config;
+ if (!parseAndCheckConfig(configPath, config)) {
+ // parseAndCheckConfig already logged whatever was wrong
+ return std::nullopt;
+ }
- /* From hereon out, we known that all the values in config are valid. */
+ /* From hereon out, we known that all the values in config are valid. */
- for (auto const &domain : config["domains"]) {
- set<ComboAddress> s;
- s.insert(domain["master"].as<ComboAddress>());
- g_domainConfigs[domain["domain"].as<DNSName>()].masters = s;
- g_stats.registerDomain(domain["domain"].as<DNSName>());
- }
+ for (auto const &domain : config["domains"]) {
+ set<ComboAddress> s;
+ s.insert(domain["master"].as<ComboAddress>());
+ g_domainConfigs[domain["domain"].as<DNSName>()].primaries = s;
+ if (domain["max-soa-refresh"].IsDefined()) {
+ g_domainConfigs[domain["domain"].as<DNSName>()].maxSOARefresh = domain["max-soa-refresh"].as<uint32_t>();
+ }
+ if (domain["notify"].IsDefined()) {
+ auto& listset = g_domainConfigs[domain["domain"].as<DNSName>()].notify;
+ if (domain["notify"].IsScalar()) {
+ auto remote = domain["notify"].as<std::string>();
+ try {
+ listset.emplace(remote, 53);
+ }
+ catch (PDNSException& e) {
+ g_log << Logger::Error << "Unparseable IP in notify directive " << remote << ". Error: " << e.reason << endl;
+ }
+ } else if (domain["notify"].IsSequence()) {
+ for (const auto& entry: domain["notify"]) {
+ auto remote = entry.as<std::string>();
+ try {
+ listset.emplace(remote, 53);
+ }
+ catch (PDNSException& e) {
+ g_log << Logger::Error << "Unparseable IP in notify directive " << remote << ". Error: " << e.reason << endl;
+ }
+ }
+ }
+ }
+ g_stats.registerDomain(domain["domain"].as<DNSName>());
+ }
+
+ for (const auto &addr : config["acl"].as<vector<string>>()) {
+ try {
+ g_acl.addMask(addr);
+ }
+ catch (const std::exception& exp) {
+ g_log<<Logger::Error<<exp.what()<<endl;
+ had_error = true;
+ }
+ catch (const NetmaskException &e) {
+ g_log<<Logger::Error<<e.reason<<endl;
+ had_error = true;
+ }
+ }
- for (const auto &addr : config["acl"].as<vector<string>>()) {
try {
- g_acl.addMask(addr);
- } catch (const NetmaskException &e) {
- g_log<<Logger::Error<<e.reason<<endl;
- had_error = true;
+ g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
+ }
+ catch (const std::exception& exp) {
+ g_log<<Logger::Error<<"Error printing ACL: "<<exp.what()<<endl;
}
- }
- g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
- if (config["compress"]) {
- g_compress = config["compress"].as<bool>();
- if (g_compress) {
- g_log<<Logger::Notice<<"Record compression is enabled."<<endl;
+ g_log<<Logger::Notice<<"NOTIFY accepted from "<<g_notifySources.toString()<<"."<<endl;
+
+ if (config["compress"].IsDefined()) {
+ g_compress = config["compress"].as<bool>();
+ if (g_compress) {
+ g_log<<Logger::Notice<<"Record compression is enabled."<<endl;
+ }
}
- }
- FDMultiplexer* fdm = FDMultiplexer::getMultiplexerSilent();
- if (fdm == nullptr) {
- g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
- return EXIT_FAILURE;
- }
+ for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
+ for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
+ try {
+ int s = SSocket(addr.sin4.sin_family, stype, 0);
+ setNonBlocking(s);
+ setReuseAddr(s);
+ if (addr.isIPv6()) {
+ int one = 1;
+ (void)setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
+ }
- set<int> allSockets;
- for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
- for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
- try {
- int s = SSocket(addr.sin4.sin_family, stype, 0);
- setNonBlocking(s);
- setReuseAddr(s);
- SBind(s, addr);
- if (stype == SOCK_STREAM) {
- SListen(s, 30); // TODO make this configurable
+ SBind(s, addr);
+ if (stype == SOCK_STREAM) {
+ SListen(s, 30); // TODO make this configurable
+ }
+ fdm.addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
+ configuration.listeningSockets.insert(s);
}
- fdm->addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
- allSockets.insert(s);
- } catch(runtime_error &e) {
- g_log<<Logger::Error<<e.what()<<endl;
+ catch (const runtime_error& exp) {
+ g_log<<Logger::Error<<exp.what()<<endl;
+ had_error = true;
+ continue;
+ }
+ catch (const PDNSException& exp) {
+ g_log<<Logger::Error<<exp.reason<<endl;
+ had_error = true;
+ continue;
+ }
+ }
+ }
+
+ if (config["gid"].IsDefined()) {
+ auto gid = config["gid"].as<string>();
+ try {
+ configuration.gid = pdns::checked_stoi<gid_t>(gid);
+ }
+ catch (const std::exception& e) {
+ g_log<<Logger::Error<<"Can not parse gid "<<gid<<endl;
had_error = true;
- continue;
+ }
+ if (configuration.gid != 0) {
+ //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ const struct group *gr = getgrnam(gid.c_str());
+ if (gr == nullptr) {
+ g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
+ had_error = true;
+ } else {
+ configuration.gid = gr->gr_gid;
+ }
}
}
- }
- int newgid = 0;
+ if (config["webserver-address"].IsDefined()) {
+ configuration.wsAddr = config["webserver-address"].as<ComboAddress>();
- if (config["gid"]) {
- string gid = config["gid"].as<string>();
- if (!(newgid = atoi(gid.c_str()))) {
- struct group *gr = getgrnam(gid.c_str());
- if (gr == nullptr) {
- g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
+ try {
+ configuration.wsACL.addMask("127.0.0.0/8");
+ configuration.wsACL.addMask("::1/128");
+
+ if (config["webserver-acl"].IsDefined()) {
+ configuration.wsACL.clear();
+ for (const auto &acl : config["webserver-acl"].as<vector<Netmask>>()) {
+ configuration.wsACL.addMask(acl);
+ }
+ }
+ }
+ catch (const NetmaskException& ne) {
+ g_log<<Logger::Error<<"Could not set the webserver ACL: "<<ne.reason<<endl;
had_error = true;
- } else {
- newgid = gr->gr_gid;
+ }
+ catch (const std::exception& exp) {
+ g_log<<Logger::Error<<"Could not set the webserver ACL: "<<exp.what()<<endl;
+ had_error = true;
+ }
+
+ if (config["webserver-loglevel"]) {
+ configuration.wsLogLevel = config["webserver-loglevel"].as<string>();
}
}
- g_log<<Logger::Notice<<"Dropping effective group-id to "<<newgid<<endl;
- if (setgid(newgid) < 0) {
- g_log<<Logger::Error<<"Could not set group id to "<<newgid<<": "<<stringerror()<<endl;
- had_error = true;
+
+ if (config["uid"].IsDefined()) {
+ auto uid = config["uid"].as<string>();
+ try {
+ configuration.uid = pdns::checked_stoi<uid_t>(uid);
+ }
+ catch (const std::exception& e) {
+ g_log<<Logger::Error<<"Can not parse uid "<<uid<<endl;
+ had_error = true;
+ }
+ if (configuration.uid != 0) {
+ //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ const struct passwd *pw = getpwnam(uid.c_str());
+ if (pw == nullptr) {
+ g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
+ had_error = true;
+ } else {
+ configuration.uid = pw->pw_uid;
+ }
+ //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+ configuration.userInfo = getpwuid(configuration.uid);
+ }
}
+
+ configuration.workDir = config["work-dir"].as<string>();
+ configuration.keep = config["keep"].as<uint16_t>();
+ configuration.axfrTimeout = config["axfr-timeout"].as<uint16_t>();
+ configuration.failedSOARetry = config["failed-soa-retry"].as<uint16_t>();
+ configuration.axfrMaxRecords = config["axfr-max-records"].as<uint32_t>();
+ configuration.tcpInThreads = config["tcp-in-threads"].as<uint16_t>();
+
+ if (had_error) {
+ return std::nullopt;
+ }
+ return configuration;
+ }
+ catch (const YAML::Exception& exp) {
+ had_error = true;
+ g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl;
+ return std::nullopt;
}
+}
- if (config["webserver-address"]) {
- NetmaskGroup wsACL;
- wsACL.addMask("127.0.0.0/8");
- wsACL.addMask("::1/128");
-
- if (config["webserver-acl"]) {
- wsACL.clear();
- for (const auto &acl : config["webserver-acl"].as<vector<Netmask>>()) {
- wsACL.addMask(acl);
- }
+int main(int argc, char** argv) {
+ bool had_error = false;
+ std::optional<IXFRDistConfiguration> configuration{std::nullopt};
+ std::unique_ptr<FDMultiplexer> fdm{nullptr};
+
+ try {
+ g_log.setLoglevel(Logger::Notice);
+ g_log.toConsole(Logger::Notice);
+ g_log.setPrefixed(true);
+ g_log.disableSyslog(true);
+ g_log.setTimestamps(false);
+
+ fdm = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+ if (!fdm) {
+ g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
+ return EXIT_FAILURE;
}
- string loglevel = "normal";
- if (config["webserver-loglevel"]) {
- loglevel = config["webserver-loglevel"].as<string>();
+ configuration = parseConfiguration(argc, argv, *fdm);
+ if (!configuration) {
+ // We have already sent the errors to stderr, just die
+ return EXIT_FAILURE;
}
- // Launch the webserver!
- try {
- std::thread(&IXFRDistWebServer::go, IXFRDistWebServer(config["webserver-address"].as<ComboAddress>(), wsACL, loglevel)).detach();
- } catch (const PDNSException &e) {
- g_log<<Logger::Error<<"Unable to start webserver: "<<e.reason<<endl;
- had_error = true;
+ if (configuration->shouldExit) {
+ return EXIT_SUCCESS;
}
}
+ catch (const YAML::Exception& exp) {
+ had_error = true;
+ g_log<<Logger::Error<<"Got an exception while processing our configuration: "<<exp.msg<<endl;
+ }
- int newuid = 0;
-
- if (config["uid"]) {
- string uid = config["uid"].as<string>();
- if (!(newuid = atoi(uid.c_str()))) {
- struct passwd *pw = getpwnam(uid.c_str());
- if (pw == nullptr) {
- g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
+ try {
+ if (configuration->gid != 0) {
+ g_log<<Logger::Notice<<"Dropping effective group-id to "<<configuration->gid<<endl;
+ if (setgid(configuration->gid) < 0) {
+ g_log<<Logger::Error<<"Could not set group id to "<<configuration->gid<<": "<<stringerror()<<endl;
had_error = true;
- } else {
- newuid = pw->pw_uid;
}
}
- struct passwd *pw = getpwuid(newuid);
- if (pw == nullptr) {
- if (setgroups(0, nullptr) < 0) {
- g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
+ // It all starts here
+ signal(SIGTERM, handleSignal);
+ signal(SIGINT, handleSignal);
+ //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
+ signal(SIGPIPE, SIG_IGN);
+
+ // Launch the webserver!
+ try {
+ std::thread(&IXFRDistWebServer::go, IXFRDistWebServer(configuration->wsAddr, configuration->wsACL, configuration->wsLogLevel)).detach();
+ }
+ catch (const std::exception& exp) {
+ g_log<<Logger::Error<<"Unable to start webserver: "<<exp.what()<<endl;
+ had_error = true;
+ }
+ catch (const PDNSException &e) {
+ g_log<<Logger::Error<<"Unable to start webserver: "<<e.reason<<endl;
+ had_error = true;
+ }
+
+ if (configuration->uid != 0) {
+ g_log<<Logger::Notice<<"Dropping effective user-id to "<<configuration->uid<<endl;
+ if (setuid(configuration->uid) < 0) {
+ g_log<<Logger::Error<<"Could not set user id to "<<configuration->uid<<": "<<stringerror()<<endl;
had_error = true;
}
- } else {
- if (initgroups(pw->pw_name, newgid) < 0) {
- g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
- had_error = true;
+ if (configuration->userInfo == nullptr) {
+ if (setgroups(0, nullptr) < 0) {
+ g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
+ had_error = true;
+ }
+ } else {
+ if (initgroups(configuration->userInfo->pw_name, configuration->gid) < 0) {
+ g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
+ had_error = true;
+ }
}
}
- g_log<<Logger::Notice<<"Dropping effective user-id to "<<newuid<<endl;
- if (setuid(newuid) < 0) {
- g_log<<Logger::Error<<"Could not set user id to "<<newuid<<": "<<stringerror()<<endl;
- had_error = true;
+ if (had_error) {
+ return EXIT_FAILURE;
}
}
-
- if (had_error) {
- // We have already sent the errors to stderr, just die
- return EXIT_FAILURE;
+ catch (const YAML::Exception& exp) {
+ had_error = true;
+ g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl;
}
- // It all starts here
- signal(SIGTERM, handleSignal);
- signal(SIGINT, handleSignal);
- signal(SIGPIPE, SIG_IGN);
-
- // Init the things we need
- reportAllTypes();
-
- dns_random_init();
-
- std::thread ut(updateThread,
- config["work-dir"].as<string>(),
- config["keep"].as<uint16_t>(),
- config["axfr-timeout"].as<uint16_t>(),
- config["failed-soa-retry"].as<uint16_t>(),
- config["axfr-max-records"].as<uint32_t>());
-
- vector<std::thread> tcpHandlers;
- tcpHandlers.reserve(config["tcp-in-threads"].as<uint16_t>());
- for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
- tcpHandlers.push_back(std::thread(tcpWorker, i));
- }
+ try {
+ // Init the things we need
+ reportAllTypes();
+
+ std::thread ut(updateThread,
+ configuration->workDir,
+ configuration->keep,
+ configuration->axfrTimeout,
+ configuration->failedSOARetry,
+ configuration->axfrMaxRecords);
+ std::thread communicator(communicatorThread);
+
+ vector<std::thread> tcpHandlers;
+ tcpHandlers.reserve(configuration->tcpInThreads);
+ for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
+ tcpHandlers.push_back(std::thread(tcpWorker, i));
+ }
- struct timeval now;
- for(;;) {
- gettimeofday(&now, 0);
- fdm->run(&now);
- if (g_exiting) {
- g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
- for (const int& fd : allSockets) {
- try {
- closesocket(fd);
- } catch(PDNSException &e) {
- g_log<<Logger::Error<<e.reason<<endl;
+ struct timeval now;
+ for (;;) {
+ gettimeofday(&now, 0);
+ fdm->run(&now);
+ if (g_exiting) {
+ g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
+ for (const int& fd : configuration->listeningSockets) {
+ try {
+ closesocket(fd);
+ } catch (const PDNSException &e) {
+ g_log<<Logger::Error<<e.reason<<endl;
+ }
}
+ break;
}
- break;
}
+
+ g_log<<Logger::Debug<<"Waiting for all threads to stop"<<endl;
+ g_tcpHandlerCV.notify_all();
+ ut.join();
+ communicator.join();
+ for (auto &t : tcpHandlers) {
+ t.join();
+ }
+ g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
}
- g_log<<Logger::Debug<<"Waiting for all threads to stop"<<endl;
- g_tcpHandlerCV.notify_all();
- ut.join();
- for (auto &t : tcpHandlers) {
- t.join();
+ catch (const YAML::Exception& exp) {
+ had_error = true;
+ g_log<<Logger::Error<<"Got an exception: "<<exp.msg<<endl;
}
- g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
- return EXIT_SUCCESS;
+
+ return had_error ? EXIT_FAILURE : EXIT_SUCCESS;
}
# When no port is specified, 53 is used. When specifying ports for IPv6, use the
# "bracket" notation:
#
+# You can optionally cap the refresh time of the SOA using 'max-soa-refresh' (seconds)
+# Otherwise, or if set to 0, the retreived SOA refresh time will be used
+# You can also send NOTIFY packets for the given domain to given destinations using `notify`
+#
# domains:
# - domain: example.com
# master: 192.0.2.15
+# max-soa-refresh: 180
+# notify: [192.0.3.1, 192.0.3.2:5301]
# - domain: rpz.example
# master: [2001:DB8:a34:543::53]:5353
#
#include <cinttypes>
#include <dirent.h>
#include <cerrno>
+#include <sys/stat.h>
+
#include "ixfrutils.hh"
#include "sstuff.hh"
#include "dnssecinfra.hh"
#include "zoneparser-tng.hh"
#include "dnsparser.hh"
-uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt, const uint16_t timeout)
+uint32_t getSerialFromPrimary(const ComboAddress& primary, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt, const uint16_t timeout)
{
vector<uint8_t> packet;
DNSPacketWriter pw(packet, zone, QType::SOA);
addTSIG(pw, trc, tt.name, tt.secret, "", false);
}
- Socket s(master.sin4.sin_family, SOCK_DGRAM);
- s.connect(master);
+ Socket s(primary.sin4.sin_family, SOCK_DGRAM);
+ s.connect(primary);
string msg((const char*)&packet[0], packet.size());
s.writen(msg);
uint32_t getSerialFromDir(const std::string& dir)
{
- uint32_t ret=0;
- DIR* dirhdl=opendir(dir.c_str());
- if(!dirhdl)
- throw runtime_error("Could not open IXFR directory '" + dir + "': " + stringerror());
- struct dirent *entry;
-
- while((entry = readdir(dirhdl))) {
- uint32_t num = atoi(entry->d_name);
- if(std::to_string(num) == entry->d_name)
- ret = max(num, ret);
+ uint32_t ret = 0;
+ auto directoryError = pdns::visit_directory(dir, [&ret]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+ try {
+ auto version = pdns::checked_stoi<uint32_t>(std::string(name));
+ if (std::to_string(version) == name) {
+ ret = std::max(version, ret);
+ }
+ }
+ catch (...) {
+ }
+ return true;
+ });
+
+ if (directoryError) {
+ throw runtime_error("Could not open IXFR directory '" + dir + "': " + *directoryError);
}
- closedir(dirhdl);
+
return ret;
}
{
DNSRecord soa;
auto serial = getSerialFromRecords(records, soa);
- string fname=directory +"/"+std::to_string(serial);
- auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen((fname+".partial").c_str(), "w"), fclose);
- if (!fp) {
+ string fname = directory + "/" + std::to_string(serial);
+ /* ensure that the partial zone file will only be accessible by the current user, not even
+ by other users in the same group, and certainly not by other users. */
+ umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
+ auto filePtr = pdns::UniqueFilePtr(fopen((fname+".partial").c_str(), "w"));
+ if (!filePtr) {
throw runtime_error("Unable to open file '"+fname+".partial' for writing: "+stringerror());
}
records_t soarecord;
soarecord.insert(soa);
- if (fprintf(fp.get(), "$ORIGIN %s\n", zone.toString().c_str()) < 0) {
+ if (fprintf(filePtr.get(), "$ORIGIN %s\n", zone.toString().c_str()) < 0) {
string error = "Error writing to zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + stringerror();
- fp.reset();
+ filePtr.reset();
unlink((fname+".partial").c_str());
throw std::runtime_error(error);
}
try {
- writeRecords(fp.get(), soarecord);
- writeRecords(fp.get(), records);
- writeRecords(fp.get(), soarecord);
+ writeRecords(filePtr.get(), soarecord);
+ writeRecords(filePtr.get(), records);
+ writeRecords(filePtr.get(), soarecord);
} catch (runtime_error &e) {
- fp.reset();
+ filePtr.reset();
unlink((fname+".partial").c_str());
throw runtime_error("Error closing zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + e.what());
}
- if (fclose(fp.release()) != 0) {
+ if (fclose(filePtr.release()) != 0) {
string error = "Error closing zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + stringerror();
unlink((fname+".partial").c_str());
throw std::runtime_error(error);
> /* indexed_by */
> /* multi_index_container */ records_t;
-uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt = TSIGTriplet(), const uint16_t timeout = 2);
+uint32_t getSerialFromPrimary(const ComboAddress& primary, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt = TSIGTriplet(), const uint16_t timeout = 2);
uint32_t getSerialFromDir(const std::string& dir);
uint32_t getSerialFromRecords(const records_t& records, DNSRecord& soaret);
void writeZoneToDisk(const records_t& records, const DNSName& zone, const std::string& directory);
/* goal in life:
in directory/zone-name we leave files with their name the serial number
- at startup, retrieve current SOA SERIAL for domain from master server
+ at startup, retrieve current SOA SERIAL for domain from primary server
compare with what the best is we have in our directory, IXFR from that.
Store result in memory, read that best zone in memory, apply deltas, write it out.
Next up, loop this every REFRESH seconds */
DNSName zone(argv[4]);
- ComboAddress master(argv[2], atoi(argv[3]));
+ ComboAddress primary(argv[2], atoi(argv[3]));
string directory(argv[5]);
records_t records;
}
catch(std::exception& e) {
cout<<"Could not load zone from disk: "<<e.what()<<endl;
- cout<<"Retrieving latest from master "<<master.toStringWithPort()<<endl;
- ComboAddress local = master.sin4.sin_family == AF_INET ? ComboAddress("0.0.0.0") : ComboAddress("::");
- AXFRRetriever axfr(master, zone, tt, &local);
+ cout << "Retrieving latest from primary " << primary.toStringWithPort() << endl;
+ ComboAddress local = primary.sin4.sin_family == AF_INET ? ComboAddress("0.0.0.0") : ComboAddress("::");
+ AXFRRetriever axfr(primary, zone, tt, &local);
unsigned int nrecords=0;
Resolver::res_t nop;
vector<DNSRecord> chunk;
cout<<"Checking for update, our serial number is "<<ourSerial<<".. ";
cout.flush();
shared_ptr<const SOARecordContent> sr;
- uint32_t serial = getSerialFromMaster(master, zone, sr, tt);
+ uint32_t serial = getSerialFromPrimary(primary, zone, sr, tt);
if(ourSerial == serial) {
- time_t sleepTime = sr ? sr->d_st.refresh : 60;
+ unsigned int sleepTime = sr ? sr->d_st.refresh : 60;
cout<<"still up to date, their serial is "<<serial<<", sleeping "<<sleepTime<<" seconds"<<endl;
sleep(sleepTime);
continue;
}
cout<<"got new serial: "<<serial<<", initiating IXFR!"<<endl;
- auto deltas = getIXFRDeltas(master, zone, ourSoa, 20, false, tt);
+ auto deltas = getIXFRDeltas(primary, zone, ourSoa, 20, false, tt);
cout<<"Got "<<deltas.size()<<" deltas, applying.."<<endl;
for(const auto& delta : deltas) {
using json11::Json;
-int intFromJson(const Json& container, const std::string& key)
+static inline int intFromJsonInternal(const Json& container, const std::string& key, const bool have_default, const int default_value)
{
- auto val = container[key];
+ const auto& val = container[key];
if (val.is_number()) {
return val.int_value();
- } else if (val.is_string()) {
- return std::stoi(val.string_value());
- } else {
- throw JsonException("Key '" + string(key) + "' not an Integer or not present");
}
-}
-int intFromJson(const Json& container, const std::string& key, const int default_value)
-{
- auto val = container[key];
- if (val.is_number()) {
- return val.int_value();
- } else if (val.is_string()) {
+ if (val.is_string()) {
try {
return std::stoi(val.string_value());
} catch (std::out_of_range&) {
- throw JsonException("Value for key '" + string(key) + "' is out of range");
+ throw JsonException("Key '" + string(key) + "' is out of range");
}
- } else {
- // TODO: check if value really isn't present
+ }
+
+ if (have_default) {
return default_value;
}
+ throw JsonException("Key '" + string(key) + "' not an Integer or not present");
}
-double doubleFromJson(const Json& container, const std::string& key)
+int intFromJson(const Json& container, const std::string& key)
{
- auto val = container[key];
+ return intFromJsonInternal(container, key, false, 0);
+}
+
+int intFromJson(const Json& container, const std::string& key, const int default_value)
+{
+ return intFromJsonInternal(container, key, true, default_value);
+}
+
+static inline unsigned int uintFromJsonInternal(const Json& container, const std::string& key, const bool have_default, const unsigned int default_value)
+{
+ int intval = intFromJsonInternal(container, key, have_default, static_cast<int>(default_value));
+ if (intval >= 0) {
+ return intval;
+ }
+ throw JsonException("Key '" + string(key) + "' is not a positive Integer");
+}
+
+unsigned int uintFromJson(const Json& container, const std::string& key)
+{
+ return uintFromJsonInternal(container, key, false, 0);
+}
+
+unsigned int uintFromJson(const Json& container, const std::string& key, const unsigned int default_value)
+{
+ return uintFromJsonInternal(container, key, true, default_value);
+}
+
+static inline double doubleFromJsonInternal(const Json& container, const std::string& key, const bool have_default, const double default_value)
+{
+ const auto& val = container[key];
if (val.is_number()) {
return val.number_value();
- } else if (val.is_string()) {
+ }
+
+ if (val.is_string()) {
try {
return std::stod(val.string_value());
} catch (std::out_of_range&) {
throw JsonException("Value for key '" + string(key) + "' is out of range");
}
- } else {
- throw JsonException("Key '" + string(key) + "' not an Integer or not present");
}
+
+ if (have_default) {
+ return default_value;
+ }
+ throw JsonException("Key '" + string(key) + "' not an Integer or not present");
+}
+
+double doubleFromJson(const Json& container, const std::string& key)
+{
+ return doubleFromJsonInternal(container, key, false, 0);
}
double doubleFromJson(const Json& container, const std::string& key, const double default_value)
{
- auto val = container[key];
- if (val.is_number()) {
- return val.number_value();
- } else if (val.is_string()) {
- return std::stod(val.string_value());
- } else {
- // TODO: check if value really isn't present
- return default_value;
- }
+ return doubleFromJsonInternal(container, key, true, default_value);
}
string stringFromJson(const Json& container, const std::string &key)
{
- const Json val = container[key];
+ const auto& val = container[key];
if (val.is_string()) {
return val.string_value();
- } else {
- throw JsonException("Key '" + string(key) + "' not present or not a String");
}
+ throw JsonException("Key '" + string(key) + "' not present or not a String");
}
-bool boolFromJson(const Json& container, const std::string& key)
+static inline bool boolFromJsonInternal(const Json& container, const std::string& key, const bool have_default, const bool default_value)
{
- auto val = container[key];
+ const auto& val = container[key];
if (val.is_bool()) {
return val.bool_value();
}
+ if (have_default) {
+ return default_value;
+ }
throw JsonException("Key '" + string(key) + "' not present or not a Bool");
}
+bool boolFromJson(const Json& container, const std::string& key)
+{
+ return boolFromJsonInternal(container, key, false, false);
+}
+
bool boolFromJson(const Json& container, const std::string& key, const bool default_value)
{
- auto val = container[key];
- if (val.is_bool()) {
- return val.bool_value();
- }
- return default_value;
+ return boolFromJsonInternal(container, key, true, default_value);
}
int intFromJson(const json11::Json& container, const std::string& key);
int intFromJson(const json11::Json& container, const std::string& key, const int default_value);
+unsigned int uintFromJson(const json11::Json& container, const std::string& key);
+unsigned int uintFromJson(const json11::Json& container, const std::string& key, const unsigned int default_value);
double doubleFromJson(const json11::Json& container, const std::string& key);
double doubleFromJson(const json11::Json& container, const std::string& key, const double default_value);
std::string stringFromJson(const json11::Json& container, const std::string &key);
"default": {
"certifi": {
"hashes": [
- "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
- "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
+ "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
+ "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
],
"index": "pypi",
- "version": "==2022.12.7"
+ "markers": "python_version >= '3.6'",
+ "version": "==2023.7.22"
},
"charset-normalizer": {
"hashes": [
},
"urllib3": {
"hashes": [
- "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc",
- "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"
+ "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07",
+ "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"
],
+ "index": "pypi",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.26.13"
+ "version": "==1.26.18"
}
},
"develop": {}
#include <openssl/core.h>
#include <openssl/core_names.h>
#include <openssl/evp.h>
+#else
+#include <openssl/hmac.h>
#endif
#ifdef HAVE_LIBSODIUM
#endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */
#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
-std::pair<bool, std::string> libssl_load_engine(const std::string& engineName, const std::optional<std::string>& defaultString)
+std::pair<bool, std::string> libssl_load_engine([[maybe_unused]] const std::string& engineName, [[maybe_unused]] const std::optional<std::string>& defaultString)
{
#ifdef OPENSSL_NO_ENGINE
return { false, "OpenSSL has been built without engine support" };
return 1;
}
-static long libssl_server_name_callback(SSL* ssl, int* al, void* arg)
+static int libssl_server_name_callback(SSL* ssl, int* /* alert */, void* /* arg */)
{
- (void) al;
- (void) arg;
-
- if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) {
+ if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name) != nullptr) {
return SSL_TLSEXT_ERR_OK;
}
{
const EVP_MD* rmd = EVP_sha256();
- auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(certFile.c_str(), "r"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fopen(certFile.c_str(), "r"));
+ if (!filePtr) {
throw std::runtime_error("Unable to open '" + certFile + "' when loading the certificate to generate an OCSP response");
}
- auto cert = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
+ auto cert = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(filePtr.get(), nullptr, nullptr, nullptr), X509_free);
- fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(caCert.c_str(), "r"), fclose);
- if (!fp) {
+ filePtr = pdns::UniqueFilePtr(fopen(caCert.c_str(), "r"));
+ if (!filePtr) {
throw std::runtime_error("Unable to open '" + caCert + "' when loading the issuer certificate to generate an OCSP response");
}
- auto issuer = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
- fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(caKey.c_str(), "r"), fclose);
- if (!fp) {
+ auto issuer = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(filePtr.get(), nullptr, nullptr, nullptr), X509_free);
+ filePtr = pdns::UniqueFilePtr(fopen(caKey.c_str(), "r"));
+ if (!filePtr) {
throw std::runtime_error("Unable to open '" + caKey + "' when loading the issuer key to generate an OCSP response");
}
- auto issuerKey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(PEM_read_PrivateKey(fp.get(), nullptr, nullptr, nullptr), EVP_PKEY_free);
- fp.reset();
+ auto issuerKey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(PEM_read_PrivateKey(filePtr.get(), nullptr, nullptr, nullptr), EVP_PKEY_free);
+ filePtr.reset();
auto bs = std::unique_ptr<OCSP_BASICRESP, void(*)(OCSP_BASICRESP*)>(OCSP_BASICRESP_new(), OCSP_BASICRESP_free);
auto thisupd = std::unique_ptr<ASN1_TIME, void(*)(ASN1_TIME*)>(X509_gmtime_adj(nullptr, 0), ASN1_TIME_free);
d_ticketKeys.write_lock()->set_capacity(capacity);
}
-OpenSSLTLSTicketKeysRing::~OpenSSLTLSTicketKeysRing()
-{
-}
+OpenSSLTLSTicketKeysRing::~OpenSSLTLSTicketKeysRing() = default;
-void OpenSSLTLSTicketKeysRing::addKey(std::shared_ptr<OpenSSLTLSTicketKey> newKey)
+void OpenSSLTLSTicketKeysRing::addKey(std::shared_ptr<OpenSSLTLSTicketKey>&& newKey)
{
- d_ticketKeys.write_lock()->push_front(newKey);
+ d_ticketKeys.write_lock()->push_front(std::move(newKey));
}
std::shared_ptr<OpenSSLTLSTicketKey> OpenSSLTLSTicketKeysRing::getEncryptionKey()
try {
do {
auto newKey = std::make_shared<OpenSSLTLSTicketKey>(file);
- addKey(newKey);
+ addKey(std::move(newKey));
keyLoaded = true;
}
while (!file.fail());
void OpenSSLTLSTicketKeysRing::rotateTicketsKey(time_t /* now */)
{
auto newKey = std::make_shared<OpenSSLTLSTicketKey>();
- addKey(newKey);
+ addKey(std::move(newKey));
}
OpenSSLTLSTicketKey::OpenSSLTLSTicketKey()
#if OPENSSL_VERSION_MAJOR >= 3
using ParamsBuilder = std::unique_ptr<OSSL_PARAM_BLD, decltype(&OSSL_PARAM_BLD_free)>;
+ using Params = std::unique_ptr<OSSL_PARAM, decltype(&OSSL_PARAM_free)>;
auto params_build = ParamsBuilder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
if (params_build == nullptr) {
return -1;
}
- auto* params = OSSL_PARAM_BLD_to_param(params_build.get());
+ auto params = Params(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);
if (params == nullptr) {
return -1;
}
- if (EVP_MAC_CTX_set_params(hctx, params) == 0) {
+ if (EVP_MAC_CTX_set_params(hctx, params.get()) == 0) {
return -1;
}
{
#if OPENSSL_VERSION_MAJOR >= 3
using ParamsBuilder = std::unique_ptr<OSSL_PARAM_BLD, decltype(&OSSL_PARAM_BLD_free)>;
+ using Params = std::unique_ptr<OSSL_PARAM, decltype(&OSSL_PARAM_free)>;
auto params_build = ParamsBuilder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
if (params_build == nullptr) {
return false;
}
- auto* params = OSSL_PARAM_BLD_to_param(params_build.get());
+ auto params = Params(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);
if (params == nullptr) {
return false;
}
- if (EVP_MAC_CTX_set_params(hctx, params) == 0) {
+ if (EVP_MAC_CTX_set_params(hctx, params.get()) == 0) {
return false;
}
/* load certificate and private key */
for (const auto& pair : config.d_certKeyPairs) {
if (!pair.d_key) {
-#if defined(HAVE_SSL_CTX_USE_CERT_AND_KEY) && HAVE_SSL_CTX_USE_CERT_AND_KEY == 1
+#if defined(HAVE_SSL_CTX_USE_CERT_AND_KEY)
// If no separate key is given, treat it as a pkcs12 file
- auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(pair.d_cert.c_str(), "r"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fopen(pair.d_cert.c_str(), "r"));
+ if (!filePtr) {
throw std::runtime_error("Unable to open file " + pair.d_cert);
}
- auto p12 = std::unique_ptr<PKCS12, void(*)(PKCS12*)>(d2i_PKCS12_fp(fp.get(), nullptr), PKCS12_free);
+ auto p12 = std::unique_ptr<PKCS12, void(*)(PKCS12*)>(d2i_PKCS12_fp(filePtr.get(), nullptr), PKCS12_free);
if (!p12) {
throw std::runtime_error("Unable to open PKCS12 file " + pair.d_cert);
}
#ifndef DISABLE_OCSP_STAPLING
if (!config.d_ocspFiles.empty()) {
try {
- ocspResponses = libssl_load_ocsp_responses(config.d_ocspFiles, keyTypes, warnings);
+ ocspResponses = libssl_load_ocsp_responses(config.d_ocspFiles, std::move(keyTypes), warnings);
}
catch(const std::exception& e) {
throw std::runtime_error("Unable to load OCSP responses: " + std::string(e.what()));
}
#endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
- return std::make_pair(std::move(ctx), std::move(warnings));
+ return {std::move(ctx), std::move(warnings)};
}
#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
return;
}
- auto fp = reinterpret_cast<FILE*>(SSL_CTX_get_ex_data(sslCtx, s_keyLogIndex));
- if (fp == nullptr) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): OpenSSL's API
+ auto* filePtr = reinterpret_cast<FILE*>(SSL_CTX_get_ex_data(sslCtx, s_keyLogIndex));
+ if (filePtr == nullptr) {
return;
}
- fprintf(fp, "%s\n", line);
- fflush(fp);
+ fprintf(filePtr, "%s\n", line);
+ fflush(filePtr);
}
#endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
-std::unique_ptr<FILE, int(*)(FILE*)> libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile)
+pdns::UniqueFilePtr libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile)
{
#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
- int fd = open(logFile.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0600);
- if (fd == -1) {
- unixDie("Error opening TLS log file '" + logFile + "'");
+ auto filePtr = pdns::openFileForWriting(logFile, 0600, false, true);
+ if (!filePtr) {
+ auto error = errno;
+ throw std::runtime_error("Error opening file " + logFile + " for writing: " + stringerror(error));
}
- auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(fd, "a"), fclose);
- if (!fp) {
- int error = errno; // close might clobber errno
- close(fd);
- throw std::runtime_error("Error opening TLS log file '" + logFile + "': " + stringerror(error));
- }
-
- SSL_CTX_set_ex_data(ctx.get(), s_keyLogIndex, fp.get());
+ SSL_CTX_set_ex_data(ctx.get(), s_keyLogIndex, filePtr.get());
SSL_CTX_set_keylog_callback(ctx.get(), &libssl_key_log_file_callback);
-
- return fp;
+ return filePtr;
#else
- return std::unique_ptr<FILE, int(*)(FILE*)>(nullptr, fclose);
+ return pdns::UniqueFilePtr(nullptr);
#endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
}
#include "config.h"
#include "circular_buffer.hh"
#include "lock.hh"
+#include "misc.hh"
enum class LibsslTLSVersion : uint8_t { Unknown, TLS10, TLS11, TLS12, TLS13 };
std::optional<std::string> d_key;
std::optional<std::string> d_password;
explicit TLSCertKeyPair(const std::string& cert, std::optional<std::string> key = std::nullopt, std::optional<std::string> password = std::nullopt):
- d_cert(cert), d_key(key), d_password(password) {
+ d_cert(cert), d_key(std::move(key)), d_password(std::move(password)) {
}
};
bool d_asyncMode{false};
/* enable kTLS mode, if supported */
bool d_ktls{false};
+ /* set read ahead mode, if supported */
+ bool d_readAhead{true};
};
struct TLSErrorCounters
public:
OpenSSLTLSTicketKeysRing(size_t capacity);
~OpenSSLTLSTicketKeysRing();
- void addKey(std::shared_ptr<OpenSSLTLSTicketKey> newKey);
std::shared_ptr<OpenSSLTLSTicketKey> getEncryptionKey();
std::shared_ptr<OpenSSLTLSTicketKey> getDecryptionKey(unsigned char name[TLS_TICKETS_KEY_NAME_SIZE], bool& activeKey);
size_t getKeysCount();
void rotateTicketsKey(time_t now);
private:
+ void addKey(std::shared_ptr<OpenSSLTLSTicketKey>&& newKey);
+
SharedLockGuarded<boost::circular_buffer<std::shared_ptr<OpenSSLTLSTicketKey> > > d_ticketKeys;
};
std::pair<std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>, std::vector<std::string>> libssl_init_server_context(const TLSConfig& config,
std::map<int, std::string>& ocspResponses);
-std::unique_ptr<FILE, int(*)(FILE*)> libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile);
+pdns::UniqueFilePtr libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile);
/* called in a client context, if the client advertised more than one ALPN values and the server returned more than one as well, to select the one to use. */
#ifndef DISABLE_NPN
class ReadWriteLock
{
public:
- ReadWriteLock()
- {
- }
+ ReadWriteLock() = default;
ReadWriteLock(const ReadWriteLock& rhs) = delete;
ReadWriteLock(ReadWriteLock&& rhs) = delete;
ReadLock(const ReadLock& rhs) = delete;
ReadLock& operator=(const ReadLock& rhs) = delete;
- ReadLock(ReadLock&& rhs) : d_lock(std::move(rhs.d_lock))
+ ReadLock(ReadLock&& rhs) noexcept :
+ d_lock(std::move(rhs.d_lock))
{
}
WriteLock(const WriteLock& rhs) = delete;
WriteLock& operator=(const WriteLock& rhs) = delete;
- WriteLock(WriteLock&& rhs) : d_lock(std::move(rhs.d_lock))
+ WriteLock(WriteLock&& rhs) noexcept :
+ d_lock(std::move(rhs.d_lock))
{
}
{
}
- explicit LockGuarded()
- {
- }
+ explicit LockGuarded() = default;
LockGuardedTryHolder<T> try_lock()
{
{
}
- explicit SharedLockGuarded()
- {
- }
+ explicit SharedLockGuarded() = default;
SharedLockGuardedTryHolder<T> try_write_lock()
{
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <ostream>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#include <iomanip>
#include <mutex>
#include "logger.hh"
bool mustAccount(false);
#endif
if (u <= consoleUrgency) {
- char buffer[50] = "";
+ std::array<char, 50> buffer{};
+ buffer[0] = '\0';
if (d_timestamps) {
struct tm tm;
time_t t;
time(&t);
localtime_r(&t, &tm);
- if (strftime(buffer, sizeof(buffer), "%b %d %H:%M:%S ", &tm) == 0) {
+ if (strftime(buffer.data(), buffer.size(), "%b %d %H:%M:%S ", &tm) == 0) {
buffer[0] = '\0';
}
}
- string prefix;
+ string severity;
if (d_prefixed) {
switch (u) {
case All:
- prefix = "[all] ";
+ severity = "All";
break;
case Alert:
- prefix = "[ALERT] ";
+ severity = "Alert";
break;
case Critical:
- prefix = "[CRITICAL] ";
+ severity = "Critical";
break;
case Error:
- prefix = "[ERROR] ";
+ severity = "Error";
break;
case Warning:
- prefix = "[WARNING] ";
+ severity = "Warning";
break;
case Notice:
- prefix = "[NOTICE] ";
+ severity = "Notice";
break;
case Info:
- prefix = "[INFO] ";
+ severity = "Info";
break;
case Debug:
- prefix = "[DEBUG] ";
+ severity = "Debug";
break;
case None:
- prefix = "[none] ";
+ severity = "None";
break;
}
}
- static std::mutex m;
- std::lock_guard<std::mutex> l(m); // the C++-2011 spec says we need this, and OSX actually does
- clog << string(buffer) + prefix + msg << endl;
+ static std::mutex mutex;
+ std::lock_guard<std::mutex> lock(mutex); // the C++-2011 spec says we need this, and OSX actually does
+
+ // To avoid issuing multiple syscalls, we write the complete line to clog with a single << call.
+ // For that we need a buffer allocated, we might want to use writev(2) one day to avoid that.
+ ostringstream line;
+ line << buffer.data();
+ if (d_prefixed) {
+ line << "msg=" << std::quoted(msg) << " prio=" << std::quoted(severity) << endl;
+ }
+ else {
+ line << msg << endl;
+ }
+ clog << line.str() << std::flush;
#ifndef RECURSOR
mustAccount = true;
#endif
bool opened;
bool d_disableSyslog;
bool d_timestamps{true};
- bool d_prefixed{false};
+ bool d_prefixed{false}; // this used to prefix the loglevel, but now causes formatting like structured logging
};
Logger& getLogger();
{
string prefix;
timeval start;
- // variant cannot hold references
- std::variant<Logger*, ostringstream*> v;
+ // variant cannot hold references directly, use a wrapper
+ std::variant<std::reference_wrapper<Logger>, std::reference_wrapper<ostringstream>> v;
};
using OptLog = std::optional<LogVariant>;
void addTraceTS(const timeval& start, ostringstream& str);
-#define VLOG(log, x) \
- if (log) { \
- if (std::holds_alternative<Logger*>((log)->v)) { \
- *std::get<Logger*>(log->v) << Logger::Warning << (log)->prefix << x; \
- } \
- else if (std::holds_alternative<ostringstream*>((log)->v)) { \
- addTraceTS((log)->start, *std::get<ostringstream*>((log)->v)); \
- *std::get<ostringstream*>((log)->v) << (log)->prefix << x; \
- } \
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#define VLOG(log, x) \
+ if (log) { \
+ if (std::holds_alternative<std::reference_wrapper<Logger>>((log)->v)) { \
+ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
+ std::get<std::reference_wrapper<Logger>>((log)->v).get() << Logger::Warning << (log)->prefix << x; \
+ } \
+ else if (std::holds_alternative<std::reference_wrapper<ostringstream>>((log)->v)) { \
+ addTraceTS((log)->start, std::get<std::reference_wrapper<ostringstream>>((log)->v).get()); \
+ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
+ std::get<std::reference_wrapper<ostringstream>>((log)->v).get() << (log)->prefix << x; \
+ } \
}
-#define VLOG_NO_PREFIX(log, x) \
- if (log) { \
- if (std::holds_alternative<Logger*>((log)->v)) { \
- *std::get<Logger*>(log->v) << Logger::Warning << x; \
- } \
- else if (std::holds_alternative<ostringstream*>((log)->v)) { \
- addTraceTS((log)->start, *std::get<ostringstream*>((log)->v)); \
- *std::get<ostringstream*>((log)->v) << x; \
- } \
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#define VLOG_NO_PREFIX(log, x) \
+ if (log) { \
+ if (std::holds_alternative<std::reference_wrapper<Logger>>((log)->v)) { \
+ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
+ std::get<std::reference_wrapper<Logger>>((log)->v).get() << Logger::Warning << x; \
+ } \
+ else if (std::holds_alternative<std::reference_wrapper<ostringstream>>((log)->v)) { \
+ addTraceTS((log)->start, std::get<std::reference_wrapper<ostringstream>>((log)->v).get()); \
+ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
+ std::get<std::reference_wrapper<ostringstream>>((log)->v).get() << x; \
+ } \
}
{
};
-// Same mechanism for t.toLogString()
+// Same mechanism for t.toLogString() and t.toStructuredLogString()
template <typename T, typename = void>
struct is_toLogString_available : std::false_type
{
{
};
+template <typename T, typename = void>
+struct is_toStructuredLogString_available : std::false_type
+{
+};
+
+template <typename T>
+struct is_toStructuredLogString_available<T, std::void_t<decltype(std::declval<T>().toStructuredLogString())>> : std::true_type
+{
+};
+
template <typename T, typename = void>
struct is_toString_available : std::false_type
{
if constexpr (std::is_same_v<T, std::string>) {
return _t;
}
+ else if constexpr (is_toStructuredLogString_available<T>::value) {
+ return _t.toStructuredLogString();
+ }
else if constexpr (is_toLogString_available<T>::value) {
return _t.toLogString();
}
// SLOG(g_log<<Logger::Warning<<"Unable to parse configuration file '"<<configname<<"'"<<endl,
// startupLog->error("No such file", "Unable to parse configuration file", "config_file", Logging::Loggable(configname));
//
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SLOG(oldStyle, slogCall) \
do { \
if (g_slogStructured) { \
else { \
oldStyle; \
} \
- } while (0);
+ } while (0)
#else // No structured logging (e.g. auth)
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SLOG(oldStyle, slogCall) \
do { \
oldStyle; \
- } while (0);
+ } while (0)
#endif // RECURSOR
d_lw->registerFunction<DNSPacket, Netmask()>("getRealRemote", [](DNSPacket &p) { return p.getRealRemote(); });
d_lw->registerFunction<DNSPacket, ComboAddress()>("getLocal", [](DNSPacket &p) { return p.getLocal(); });
d_lw->registerFunction<DNSPacket, unsigned int()>("getRemotePort", [](DNSPacket &p) { return p.getInnerRemote().getPort(); });
- d_lw->registerFunction<DNSPacket, std::tuple<const std::string, unsigned int>()>("getQuestion", [](DNSPacket &p) { return std::make_tuple(p.qdomain.toString(), static_cast<unsigned int>(p.qtype.getCode())); });
+ d_lw->registerFunction<DNSPacket, std::tuple<const std::string, unsigned int>()>("getQuestion", [](DNSPacket &p) { return std::tuple(p.qdomain.toString(), static_cast<unsigned int>(p.qtype.getCode())); });
d_lw->registerFunction<DNSPacket, void(bool)>("setA", [](DNSPacket &p, bool a) { return p.setA(a); });
d_lw->registerFunction<DNSPacket, void(unsigned int)>("setID", [](DNSPacket &p, unsigned int id) { return p.setID(static_cast<uint16_t>(id)); });
d_lw->registerFunction<DNSPacket, void(bool)>("setRA", [](DNSPacket &p, bool ra) { return p.setRA(ra); });
}
bool AuthLua4::axfrfilter(const ComboAddress& remote, const DNSName& zone, const DNSResourceRecord& in, vector<DNSResourceRecord>& out) {
- luacall_axfr_filter_t::result_type ret;
- int rcode;
-
- if (d_axfr_filter == nullptr) return false;
+ if (!d_axfr_filter) {
+ return false;
+ }
- ret = d_axfr_filter(remote, zone, in);
- rcode = std::get<0>(ret);
+ const auto& [rcode, rows] = d_axfr_filter(remote, zone, in);
if (rcode < 0) {
// no modification, handle normally
return false;
else
throw PDNSException("Cannot understand return code "+std::to_string(rcode)+" in axfr filter response");
- const auto& rows = std::get<1>(ret);
-
try {
for(const auto& row: rows) {
DNSResourceRecord rec;
return nullptr;
}
-AuthLua4::~AuthLua4() { }
+AuthLua4::~AuthLua4() = default;
std::unique_ptr<DNSPacket> prequery(const DNSPacket& p);
- ~AuthLua4(); // this is so unique_ptr works with an incomplete type
+ ~AuthLua4() override; // this is so unique_ptr works with an incomplete type
protected:
- virtual void postPrepareContext() override;
- virtual void postLoad() override;
+ void postPrepareContext() override;
+ void postLoad() override;
+
private:
struct UpdatePolicyQuery {
DNSName qname;
#include "ext/luawrapper/include/LuaContext.hpp"
#include "dns_random.hh"
-BaseLua4::BaseLua4() {
-}
+BaseLua4::BaseLua4() = default;
void BaseLua4::loadFile(const std::string& fname)
{
d_lw->registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match);
// DNSRecord
- d_lw->writeFunction("newDR", [](const DNSName &name, const std::string &type, unsigned int ttl, const std::string &content, int place){ QType qtype; qtype = type; auto dr = DNSRecord(); dr.d_name = name; dr.d_type = qtype.getCode(); dr.d_ttl = ttl; dr.setContent(shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(dr.d_type, QClass::IN, content))); dr.d_place = static_cast<DNSResourceRecord::Place>(place); return dr; });
+ d_lw->writeFunction("newDR", [](const DNSName& name, const std::string& type, unsigned int ttl, const std::string& content, int place) { QType qtype; qtype = type; auto dr = DNSRecord(); dr.d_name = name; dr.d_type = qtype.getCode(); dr.d_ttl = ttl; dr.setContent(shared_ptr<DNSRecordContent>(DNSRecordContent::make(dr.d_type, QClass::IN, content))); dr.d_place = static_cast<DNSResourceRecord::Place>(place); return dr; });
d_lw->registerMember("name", &DNSRecord::d_name);
d_lw->registerMember("type", &DNSRecord::d_type);
d_lw->registerMember("ttl", &DNSRecord::d_ttl);
ret=aaaarec->getCA(53);
return ret;
});
- d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.setContent(shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(dr.d_type, 1, newContent))); });
+ d_lw->registerFunction<void (DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.setContent(shared_ptr<DNSRecordContent>(DNSRecordContent::make(dr.d_type, 1, newContent))); });
// pdnsload
d_lw->writeFunction("pdnslog", [](const std::string& msg, boost::optional<int> loglevel) {
SLOG(g_log << (Logger::Urgency)loglevel.get_value_or(Logger::Warning) << msg<<endl,
g_slog->withName("lua")->info(static_cast<Logr::Priority>(loglevel.get_value_or(Logr::Warning)), msg));
});
- d_lw->writeFunction("pdnsrandom", [](boost::optional<uint32_t> maximum) { return dns_random(maximum.get_value_or(0xffffffff)); });
+ d_lw->writeFunction("pdnsrandom", [](boost::optional<uint32_t> maximum) {
+ return maximum ? dns_random(*maximum) : dns_random_uint32();
+ });
// certain constants
postLoad();
}
-BaseLua4::~BaseLua4() { }
+BaseLua4::~BaseLua4() = default;
#include <thread>
#include <future>
#include <boost/format.hpp>
+#include <boost/uuid/string_generator.hpp>
#include <utility>
#include <algorithm>
#include <random>
+#include "qtype.hh"
+#include <tuple>
#include "version.hh"
#include "ext/luawrapper/include/LuaContext.hpp"
#include "lock.hh"
#include "sstuff.hh"
#include "minicurl.hh"
#include "ueberbackend.hh"
-#include "dnsrecords.hh"
#include "dns_random.hh"
#include "auth-main.hh"
#include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
for(const auto& m : rhs.opts)
rhsoopts[m.first]=m.second;
- return std::make_tuple(rem, url, oopts) <
- std::make_tuple(rhs.rem, rhs.url, rhsoopts);
+ return std::tuple(rem, url, oopts) <
+ std::tuple(rhs.rem, rhs.url, rhsoopts);
}
};
struct CheckState
{
d_checkerThreadStarted.clear();
}
- ~IsUpOracle()
- {
- }
+ ~IsUpOracle() = default;
bool isUp(const ComboAddress& remote, const opts_t& opts);
bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
bool isUp(const CheckDesc& cd);
}
template <typename T>
-static T pickWeightedHashed(const ComboAddress& bestwho, vector< pair<int, T> >& items)
+static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
{
if (items.empty()) {
throw std::invalid_argument("The items list cannot be empty");
return p->second;
}
+template <typename T>
+static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
+{
+ if (items.empty()) {
+ throw std::invalid_argument("The items list cannot be empty");
+ }
+ size_t sum=0;
+ vector< pair<int, T> > pick;
+ pick.reserve(items.size());
+
+ for(auto& i : items) {
+ sum += i.first;
+ pick.push_back({sum, i.second});
+ }
+
+ if (sum == 0) {
+ throw std::invalid_argument("The sum of items cannot be zero");
+ }
+
+ size_t r = dnsname.hash() % sum;
+ auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
+ return p->second;
+}
+
template <typename T>
static vector<T> pickRandomSample(int n, const vector<T>& items)
{
return ret;
}
+static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
+{
+ static LockGuarded<UeberBackend> s_ub;
+
+ {
+ auto ueback = s_ub.lock();
+ return ueback->getAuth(name, qtype, soaData);
+ }
+}
+
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
{
string selector=defaultValue;
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
-static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, std::function<bool(const ComboAddress&, const opts_t&)> upcheckf, uint16_t port = 0) {
+/*
+ * Holds computed hashes for a given entry
+ */
+struct EntryHashesHolder
+{
+ std::atomic<size_t> weight;
+ std::string entry;
+ SharedLockGuarded<std::vector<unsigned int>> hashes;
+ std::atomic<time_t> lastUsed;
+
+ EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
+ }
+
+ bool hashesComputed() {
+ return weight == hashes.read_lock()->size();
+ }
+ void hash() {
+ auto locked = hashes.write_lock();
+ locked->clear();
+ locked->reserve(weight);
+ size_t count = 0;
+ while (count < weight) {
+ auto value = boost::str(boost::format("%s-%d") % entry % count);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
+ locked->push_back(whash);
+ ++count;
+ }
+ std::sort(locked->begin(), locked->end());
+ }
+};
+
+using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
+
+static SharedLockGuarded<std::map<
+ zone_hashes_key_t, // zoneid qname entry
+ std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
+ >>
+s_zone_hashes;
+
+static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
+
+/**
+ * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
+ */
+static void cleanZoneHashes()
+{
+ auto now = time(nullptr);
+ if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
+ return ;
+ }
+ s_lastConsistentHashesCleanup = now;
+ std::vector<zone_hashes_key_t> toDelete{};
+ {
+ auto locked = s_zone_hashes.read_lock();
+ auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
+
+ for (const auto& [key, entry]: *locked) {
+ if (entry->lastUsed > someTimeAgo) {
+ toDelete.push_back(key);
+ }
+ }
+ }
+ if (!toDelete.empty()) {
+ auto wlocked = s_zone_hashes.write_lock();
+ for (const auto& key : toDelete) {
+ wlocked->erase(key);
+ }
+ }
+}
+
+static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
+{
+ std::vector<std::shared_ptr<EntryHashesHolder>> result{};
+ std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
+
+ {
+ time_t now = time(nullptr);
+ auto locked = s_zone_hashes.read_lock();
+
+ for (const auto& [weight, entry]: items) {
+ auto key = std::make_tuple(zoneId, queryName, entry);
+ if (locked->count(key) == 0) {
+ newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
+ } else {
+ locked->at(key)->weight = weight;
+ locked->at(key)->lastUsed = now;
+ result.push_back(locked->at(key));
+ }
+ }
+ }
+ if (!newEntries.empty()) {
+ auto wlocked = s_zone_hashes.write_lock();
+
+ for (auto& [key, entry]: newEntries) {
+ result.push_back(entry);
+ (*wlocked)[key] = std::move(entry);
+ }
+ }
+
+ return result;
+}
+
+static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
+{
+ const auto& zoneId = s_lua_record_ctx->zoneid;
+ const auto queryName = s_lua_record_ctx->qname.toString();
+ unsigned int sel = std::numeric_limits<unsigned int>::max();
+ unsigned int min = std::numeric_limits<unsigned int>::max();
+
+ boost::optional<std::string> ret;
+ boost::optional<std::string> first;
+
+ cleanZoneHashes();
+
+ auto entries = getCHashedEntries(zoneId, queryName, items);
+
+ ComboAddress::addressOnlyHash addrOnlyHash;
+ auto qhash = addrOnlyHash(bestwho);
+ for (const auto& entry : entries) {
+ if (!entry->hashesComputed()) {
+ entry->hash();
+ }
+ {
+ const auto hashes = entry->hashes.read_lock();
+ if (!hashes->empty()) {
+ if (min > *(hashes->begin())) {
+ min = *(hashes->begin());
+ first = entry->entry;
+ }
+
+ auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
+ if (hash_it != hashes->end()) {
+ if (*hash_it < sel) {
+ sel = *hash_it;
+ ret = entry->entry;
+ }
+ }
+ }
+ }
+ }
+ if (ret != boost::none) {
+ return *ret;
+ }
+ if (first != boost::none) {
+ return *first;
+ }
+ return {};
+}
+
+static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, const std::function<bool(const ComboAddress&, const opts_t&)>& upcheckf, uint16_t port = 0)
+{
vector<vector<ComboAddress> > candidates;
opts_t opts;
if(options)
return convComboAddressListToString(res);
}
-static void setupLuaRecords(LuaContext& lua)
+static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
{
lua.writeFunction("latlon", []() {
double lat = 0, lon = 0;
} catch (const PDNSException &e) {
return allZerosIP;
}
- } else if (parts.size() >= 1) {
+ } else if (!parts.empty()) {
+ auto& input = parts.at(0);
+
+ // allow a word without - in front, as long as it does not contain anything that could be a number
+ size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
+ if (nonhexprefix > 0) {
+ input = input.substr(nonhexprefix);
+ }
+
// either hex string, or 12-13-14-15
vector<string> ip_parts;
- stringtok(ip_parts, parts[0], "-");
+
+ stringtok(ip_parts, input, "-");
unsigned int x1, x2, x3, x4;
if (ip_parts.size() >= 4) {
// 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
string ret;
- for (size_t n=4; n > 0; n--) {
- auto octet = ip_parts[ip_parts.size() - n];
+ for (size_t index=4; index > 0; index--) {
+ auto octet = ip_parts[ip_parts.size() - index];
try {
auto octetVal = std::stol(octet);
if (octetVal >= 0 && octetVal <= 255) {
- ret += ip_parts.at(ip_parts.size() - n) + ".";
+ ret += ip_parts.at(ip_parts.size() - index) + ".";
} else {
return allZerosIP;
}
}
ret.resize(ret.size() - 1); // remove trailing dot after last octet
return ret;
- } else if(parts[0].length() >= 8 && sscanf(parts[0].c_str()+(parts[0].length()-8), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
- return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
+ }
+ if(input.length() >= 8) {
+ auto last8 = input.substr(input.length()-8);
+ if(sscanf(last8.c_str(), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
+ return std::to_string(x1) + "." + std::to_string(x2) + "." + std::to_string(x3) + "." + std::to_string(x4);
+ }
}
}
return allZerosIP;
return std::string("unknown");
});
- lua.writeFunction("filterForward", [](string address, NetmaskGroup& nmg, boost::optional<string> fallback) {
+ lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> {
ComboAddress ca(address);
if (nmg.match(ComboAddress(address))) {
- return address;
+ return {address};
} else {
if (fallback) {
- return *fallback;
+ if (fallback->empty()) {
+ // if fallback is an empty string, return an empty array
+ return {};
+ }
+ return {*fallback};
}
if (ca.isIPv4()) {
- return string("0.0.0.0");
+ return {string("0.0.0.0")};
} else {
- return string("::");
+ return {string("::")};
}
}
});
lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
vector< pair<int, string> > items;
+ items.reserve(ips.size());
+ for (auto& entry : ips) {
+ items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
+ }
+
+ return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
+ });
+
+ /*
+ * Based on the hash of the record name, return an IP address from the list
+ * supplied, as weighted by the various `weight` parameters
+ * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
+ */
+ lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
+ vector< pair<int, string> > items;
+
items.reserve(ips.size());
for(auto& i : ips)
+ {
items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
+ }
- return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
+ return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
});
+ /*
+ * Based on the hash of `bestwho`, returns an IP address from the list
+ * supplied, as weighted by the various `weight` parameters and distributed consistently
+ * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
+ */
+ lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
+ std::vector<std::pair<int, std::string>> items;
+
+ items.reserve(ips.size());
+ for (const auto& entry : ips) {
+ items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
+ }
+ return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
+ });
lua.writeFunction("pickclosest", [](const iplist_t& ips) {
vector<ComboAddress> conv = convComboAddressList(ips);
return result;
});
+ lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
+ DNSName rec;
+ vector<string> ret;
+ try {
+ rec = DNSName(record);
+ }
+ catch (const std::exception& e) {
+ g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
+ return ret;
+ }
+ try {
+ SOAData soaData;
+
+ if (!getAuth(rec, qtype, &soaData)) {
+ return ret;
+ }
+
+ vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
+ for (const auto& drec : drs) {
+ ret.push_back(drec.dr.getContent()->getZoneRepresentation());
+ }
+ }
+ catch (std::exception& e) {
+ g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
+ }
+ return ret;
+ });
+
lua.writeFunction("include", [&lua](string record) {
DNSName rec;
try {
lua.writeVariable("zone", zone);
lua.writeVariable("zoneid", zoneid);
lua.writeVariable("who", dnsp.getInnerRemote());
+ lua.writeVariable("localwho", dnsp.getLocal());
lua.writeVariable("dh", (dnsheader*)&dnsp.d);
lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
lua.writeVariable("tcp", dnsp.d_tcp);
for(const auto& content_it: contents) {
if(qtype==QType::TXT)
- ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, '"'+content_it+'"' ));
+ ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
else
- ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, content_it ));
+ ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
}
} catch(std::exception &e) {
g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
#include <boost/format.hpp>
#include "iputils.hh"
#include "dnsparser.hh"
+#include "dns_random.hh"
#include <pwd.h>
#include <grp.h>
#include <climits>
}
}
set<int>::const_iterator it(pollinFDs.begin());
- advance(it, random() % pollinFDs.size());
+ advance(it, dns_random(pollinFDs.size()));
*fdOut = *it;
return 1;
}
// returns -1 in case of error, 0 if no data is available, 1 if there is. In the first two cases, errno is set
-int waitFor2Data(int fd1, int fd2, int seconds, int useconds, int*fd)
+int waitFor2Data(int fd1, int fd2, int seconds, int useconds, int* fdPtr)
{
- int ret;
-
- struct pollfd pfds[2];
- memset(&pfds[0], 0, 2*sizeof(struct pollfd));
+ std::array<pollfd,2> pfds{};
+ memset(pfds.data(), 0, pfds.size() * sizeof(struct pollfd));
pfds[0].fd = fd1;
pfds[1].fd = fd2;
pfds[0].events= pfds[1].events = POLLIN;
- int nsocks = 1 + (fd2 >= 0); // fd2 can optionally be -1
+ int nsocks = 1 + static_cast<int>(fd2 >= 0); // fd2 can optionally be -1
- if(seconds >= 0)
- ret = poll(pfds, nsocks, seconds * 1000 + useconds/1000);
- else
- ret = poll(pfds, nsocks, -1);
- if(!ret || ret < 0)
+ int ret{};
+ if (seconds >= 0) {
+ ret = poll(pfds.data(), nsocks, seconds * 1000 + useconds / 1000);
+ }
+ else {
+ ret = poll(pfds.data(), nsocks, -1);
+ }
+ if (ret <= 0) {
return ret;
+ }
- if((pfds[0].revents & POLLIN) && !(pfds[1].revents & POLLIN))
- *fd = pfds[0].fd;
- else if((pfds[1].revents & POLLIN) && !(pfds[0].revents & POLLIN))
- *fd = pfds[1].fd;
+ if ((pfds[0].revents & POLLIN) != 0 && (pfds[1].revents & POLLIN) == 0) {
+ *fdPtr = pfds[0].fd;
+ }
+ else if ((pfds[1].revents & POLLIN) != 0 && (pfds[0].revents & POLLIN) == 0) {
+ *fdPtr = pfds[1].fd;
+ }
else if(ret == 2) {
- *fd = pfds[random()%2].fd;
+ *fdPtr = pfds.at(dns_random_uint32() % 2).fd;
+ }
+ else {
+ *fdPtr = -1; // should never happen
}
- else
- *fd = -1; // should never happen
return 1;
}
void cleanSlashes(string &str)
{
- string::const_iterator i;
string out;
- for(i=str.begin();i!=str.end();++i) {
- if(*i=='/' && i!=str.begin() && *(i-1)=='/')
- continue;
- out.append(1,*i);
+ bool keepNextSlash = true;
+ for (const auto& value : str) {
+ if (value == '/') {
+ if (keepNextSlash) {
+ keepNextSlash = false;
+ }
+ else {
+ continue;
+ }
+ }
+ else {
+ keepNextSlash = true;
+ }
+ out.append(1, value);
}
- str=out;
+ str = std::move(out);
}
-
bool IpToU32(const string &str, uint32_t *ip)
{
if(str.empty()) {
bool readFileIfThere(const char* fname, std::string* line)
{
line->clear();
- auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname, "r"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fopen(fname, "r"));
+ if (!filePtr) {
return false;
}
- return stringfgets(fp.get(), *line);
+ return stringfgets(filePtr.get(), *line);
}
Regex::Regex(const string &expr)
uint64_t getOpenFileDescriptors(const std::string&)
{
#ifdef __linux__
- DIR* dirhdl=opendir(("/proc/"+std::to_string(getpid())+"/fd/").c_str());
- if(!dirhdl)
- return 0;
-
- struct dirent *entry;
- int ret=0;
- while((entry = readdir(dirhdl))) {
+ uint64_t nbFileDescriptors = 0;
+ const auto dirName = "/proc/" + std::to_string(getpid()) + "/fd/";
+ auto directoryError = pdns::visit_directory(dirName, [&nbFileDescriptors]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
uint32_t num;
try {
- pdns::checked_stoi_into(num, entry->d_name);
+ pdns::checked_stoi_into(num, std::string(name));
+ if (std::to_string(num) == name) {
+ nbFileDescriptors++;
+ }
} catch (...) {
- continue; // was not a number.
+ // was not a number.
}
- if(std::to_string(num) == entry->d_name)
- ret++;
+ return true;
+ });
+ if (directoryError) {
+ return 0U;
}
- closedir(dirhdl);
- return ret;
-
+ return nbFileDescriptors;
#elif defined(__OpenBSD__)
// FreeBSD also has this in libopenbsd, but I don't know if that's available always
return getdtablecount();
#else
- return 0;
+ return 0U;
#endif
}
#endif /* !HAVE_SODIUM_MEMCMP */
#endif /* !HAVE_CRYPTO_MEMCMP */
}
+
+namespace pdns
+{
+struct CloseDirDeleter
+{
+ void operator()(DIR* dir) const noexcept {
+ closedir(dir);
+ }
+};
+
+std::optional<std::string> visit_directory(const std::string& directory, const std::function<bool(ino_t inodeNumber, const std::string_view& name)>& visitor)
+{
+ auto dirHandle = std::unique_ptr<DIR, CloseDirDeleter>(opendir(directory.c_str()));
+ if (!dirHandle) {
+ auto err = errno;
+ return std::string("Error opening directory '" + directory + "': " + stringerror(err));
+ }
+
+ bool keepGoing = true;
+ struct dirent* ent = nullptr;
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): readdir is thread-safe nowadays and readdir_r is deprecated
+ while (keepGoing && (ent = readdir(dirHandle.get())) != nullptr) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay: dirent API
+ auto name = std::string_view(ent->d_name, strlen(ent->d_name));
+ keepGoing = visitor(ent->d_ino, name);
+ }
+
+ return std::nullopt;
+}
+
+UniqueFilePtr openFileForWriting(const std::string& filePath, mode_t permissions, bool mustNotExist, bool appendIfExists)
+{
+ int flags = O_WRONLY | O_CREAT;
+ if (mustNotExist) {
+ flags |= O_EXCL;
+ }
+ else if (appendIfExists) {
+ flags |= O_APPEND;
+ }
+ int fileDesc = open(filePath.c_str(), flags, permissions);
+ if (fileDesc == -1) {
+ return {};
+ }
+ auto filePtr = pdns::UniqueFilePtr(fdopen(fileDesc, appendIfExists ? "a" : "w"));
+ if (!filePtr) {
+ auto error = errno;
+ close(fileDesc);
+ errno = error;
+ return {};
+ }
+ return filePtr;
+}
+
+}
typedef enum { TSIG_MD5, TSIG_SHA1, TSIG_SHA224, TSIG_SHA256, TSIG_SHA384, TSIG_SHA512, TSIG_GSS } TSIGHashEnum;
namespace pdns
{
-#if defined(HAVE_LIBCRYPTO)
/**
* \brief Retrieves the errno-based error message in a reentrant way.
*
*/
auto getMessageFromErrno(int errnum) -> std::string;
+#if defined(HAVE_LIBCRYPTO)
namespace OpenSSL
{
/**
template<typename T> bool rfc1982LessThan(T a, T b)
{
- static_assert(std::is_unsigned<T>::value, "rfc1982LessThan only works for unsigned types");
- typedef typename std::make_signed<T>::type signed_t;
- return static_cast<signed_t>(a - b) < 0;
+ static_assert(std::is_unsigned_v<T>, "rfc1982LessThan only works for unsigned types");
+ return std::make_signed_t<T>(a - b) < 0;
}
// fills container with ranges, so {posbegin,posend}
struct CIStringComparePOSIX
{
- bool operator() (const std::string& lhs, const std::string& rhs)
+ bool operator() (const std::string& lhs, const std::string& rhs) const
{
- std::string::const_iterator a,b;
const std::locale &loc = std::locale("POSIX");
- a=lhs.begin();b=rhs.begin();
- while(a!=lhs.end()) {
- if (b==rhs.end() || std::tolower(*b,loc)<std::tolower(*a,loc)) return false;
- else if (std::tolower(*a,loc)<std::tolower(*b,loc)) return true;
- ++a;++b;
+ auto lhsIter = lhs.begin();
+ auto rhsIter = rhs.begin();
+ while (lhsIter != lhs.end()) {
+ if (rhsIter == rhs.end() || std::tolower(*rhsIter,loc) < std::tolower(*lhsIter,loc)) {
+ return false;
+ }
+ if (std::tolower(*lhsIter,loc) < std::tolower(*rhsIter,loc)) {
+ return true;
+ }
+ ++lhsIter;++rhsIter;
}
- return (b!=rhs.end());
+ return rhsIter != rhs.end();
}
};
// I'm not very OCD, but I appreciate loglines like "processing 1 delta", "processing 2 deltas" :-)
template <typename Integer,
-typename std::enable_if_t<std::is_integral<Integer>::value, bool> = true>
+typename std::enable_if_t<std::is_integral_v<Integer>, bool> = true>
const char* addS(Integer siz, const char* singular = "", const char *plural = "s")
{
if (siz == 1) {
}
template <typename C,
-typename std::enable_if_t<std::is_class<C>::value, bool> = true>
+typename std::enable_if_t<std::is_class_v<C>, bool> = true>
const char* addS(const C& c, const char* singular = "", const char *plural = "s")
{
return addS(c.size(), singular, plural);
~FDWrapper()
{
- if (d_fd != -1) {
- close(d_fd);
- d_fd = -1;
- }
+ reset();
}
FDWrapper(FDWrapper&& rhs) noexcept : d_fd(rhs.d_fd)
return d_fd;
}
+ void reset()
+ {
+ if (d_fd != -1) {
+ ::close(d_fd);
+ d_fd = -1;
+ }
+ }
+
private:
int d_fd{-1};
};
+
+namespace pdns
+{
+[[nodiscard]] std::optional<std::string> visit_directory(const std::string& directory, const std::function<bool(ino_t inodeNumber, const std::string_view& name)>& visitor);
+
+struct FilePtrDeleter
+{
+ /* using a deleter instead of decltype(&fclose) has two big advantages:
+ - the deleter is included in the type and does not have to be passed
+ when creating a new object (easier to use, less memory usage, in theory
+ better inlining)
+ - we avoid the annoying "ignoring attributes on template argument ‘int (*)(FILE*)’"
+ warning from the compiler, which is there because fclose is tagged as __nonnull((1))
+ */
+ void operator()(FILE* filePtr) const noexcept {
+ fclose(filePtr);
+ }
+};
+
+using UniqueFilePtr = std::unique_ptr<FILE, FilePtrDeleter>;
+
+UniqueFilePtr openFileForWriting(const std::string& filePath, mode_t permissions, bool mustNotExist = true, bool appendIfExists = false);
+}
FDMultiplexer() :
d_inrun(false)
{}
- virtual ~FDMultiplexer()
- {}
+ virtual ~FDMultiplexer() = default;
// The maximum number of events processed in a single run, not the maximum of watched descriptors
static constexpr unsigned int s_maxevents = 1024;
}
/* do the addition _after_ so the entry is not added if there is an error */
- accountingAddFD(d_readCallbacks, fd, toDo, parameter, ttd);
+ accountingAddFD(d_readCallbacks, fd, std::move(toDo), parameter, ttd);
}
//! Add an fd to the write watch list - currently an fd can only be on one list at a time!
}
/* do the addition _after_ so the entry is not added if there is an error */
- accountingAddFD(d_writeCallbacks, fd, toDo, parameter, ttd);
+ accountingAddFD(d_writeCallbacks, fd, std::move(toDo), parameter, ttd);
}
//! Remove an fd from the read watch list. You can't call this function on an fd that is closed already!
{
accountingRemoveFD(d_writeCallbacks, fd);
this->alterFD(fd, EventKind::Write, EventKind::Read);
- accountingAddFD(d_readCallbacks, fd, toDo, parameter, ttd);
+ accountingAddFD(d_readCallbacks, fd, std::move(toDo), parameter, ttd);
}
void alterFDToWrite(int fd, callbackfunc_t toDo, const funcparam_t& parameter = funcparam_t(), const struct timeval* ttd = nullptr)
{
accountingRemoveFD(d_readCallbacks, fd);
this->alterFD(fd, EventKind::Read, EventKind::Write);
- accountingAddFD(d_writeCallbacks, fd, toDo, parameter, ttd);
+ accountingAddFD(d_writeCallbacks, fd, std::move(toDo), parameter, ttd);
}
std::vector<std::pair<int, funcparam_t>> getTimeouts(const struct timeval& tv, bool writes = false)
{
Callback cb;
cb.d_fd = fd;
- cb.d_callback = toDo;
+ cb.d_callback = std::move(toDo);
cb.d_parameter = parameter;
memset(&cb.d_ttd, 0, sizeof(cb.d_ttd));
if (ttd) {
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "mtasker.hh"
-#include "misc.hh"
-#include <stdio.h>
-#include <iostream>
-
-#ifdef PDNS_USE_VALGRIND
-#include <valgrind/valgrind.h>
-#endif /* PDNS_USE_VALGRIND */
-
-/** \page MTasker
- Simple system for implementing cooperative multitasking of functions, with
- support for waiting on events which can return values.
-
- \section copyright Copyright and License
- MTasker is (c) 2002 - 2009 by bert hubert. It is licensed to you under the terms of the GPL version 2.
-
- \section overview High level overview
- MTasker is designed to support very simple cooperative multitasking to facilitate writing
- code that would ordinarily require a statemachine, for which the author does not consider
- himself smart enough.
-
- This class does not perform any magic it only makes calls to makecontext() and swapcontext().
- Getting the details right however is complicated and MTasker does that for you.
-
- If preemptive multitasking or more advanced concepts such as semaphores, locks or mutexes
- are required, the use of POSIX threads is advised.
-
- MTasker is designed to offer the performance of statemachines while maintaining simple thread semantics. It is not
- a replacement for a full threading system.
-
- \section compatibility Compatibility
- MTasker is only guaranteed to work on Linux with glibc 2.2.5 and higher. It does not work on FreeBSD and notably,
- not on Red Hat 6.0. It may work on Solaris, please test.
-
- \section concepts Concepts
-
- There are two important concepts, the 'kernel' and the 'thread'. Each thread starts out as a function,
- which is passed to MTasker::makeThread(), together with a possible argument.
-
- This function is now free to do whatever it wants, but realise that MTasker implements cooperative
- multitasking, which means that the coder has the responsibility of not taking the CPU overly long.
- Other threads can only get the CPU if MTasker::yield() is called or if a thread sleeps to wait for an event,
- using the MTasker::waitEvent() method.
-
- \section kernel The Kernel
- The Kernel consists of functions that do housekeeping, but also of code that the client coder
- can call to report events. A minimal kernel loop looks like this:
- \code
- for(;;) {
- MT.schedule();
- if(MT.noProcesses()) // exit if no processes are left
- break;
- }
- \endcode
-
- The kernel typically starts from the main() function of your program. New threads are also
- created from the kernel. This can also happen before entering the main loop. To start a thread,
- the method MTasker::makeThread is provided.
-
- \section events Events
- By default, Events are recognized by an int and their value is also an int.
- This can be overridden by specifying the EventKey and EventVal template parameters.
-
- An event can be a keypress, but also a UDP packet, or a bit of data from a TCP socket. The
- sample code provided works with keypresses, but that is just a not very useful example.
-
- A thread can also wait for an event only for a limited time, and receive a timeout of that
- event did not occur within the specified timeframe.
-
- \section example A simple menu system
- \code
-MTasker<> MT;
-
-void menuHandler(void *p)
-{
- int num=(int)p;
- cout<<"Key handler for key "<<num<<" launched"<<endl;
-
- MT.waitEvent(num);
- cout<<"Key "<<num<<" was pressed!"<<endl;
-}
-
-
-int main()
-{
- char line[10];
-
- for(int i=0;i<10;++i)
- MT.makeThread(menuHandler,(void *)i);
-
- for(;;) {
- while(MT.schedule()); // do everything we can do
- if(MT.noProcesses()) // exit if no processes are left
- break;
-
- if(!fgets(line,sizeof(line),stdin))
- break;
-
- MT.sendEvent(*line-'0');
- }
-}
-\endcode
-
-\section example2 Canonical multitasking example
-This implements the canonical multitasking example, alternately printing an 'A' and a 'B'. The Linux kernel
- started this way too.
-\code
-void printer(void *p)
-{
- char c=(char)p;
- for(;;) {
- cout<<c<<endl;
- MT.yield();
- }
-
-}
-
-int main()
-{
- MT.makeThread(printer,(void*)'a');
- MT.makeThread(printer,(void*)'b');
-
- for(;;) {
- while(MT.schedule()); // do everything we can do
- if(MT.noProcesses()) // exit if no processes are left
- break;
- }
-}
-\endcode
-
-*/
-
-//! puts a thread to sleep waiting until a specified event arrives
-/** Threads can call waitEvent to register that they are waiting on an event with a certain key.
- If so desired, the event can carry data which is returned in val in case that is non-zero.
-
- Furthermore, a timeout can be specified in seconds.
-
- Only one thread can be waiting on a key, results of trying to have more threads
- waiting on the same key are undefined.
-
- \param key Event key to wait for. Needs to match up to a key reported to sendEvent
- \param val If non-zero, the value of the event will be stored in *val
- \param timeout If non-zero, number of seconds to wait for an event.
-
- \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer
-*/
-
-template<class EventKey, class EventVal, class Cmp>int MTasker<EventKey,EventVal,Cmp>::waitEvent(EventKey &key, EventVal *val, unsigned int timeoutMsec, const struct timeval* now)
-{
- if(d_waiters.count(key)) { // there was already an exact same waiter
- return -1;
- }
-
- Waiter w;
- w.context=std::make_shared<pdns_ucontext_t>();
- w.ttd.tv_sec = 0; w.ttd.tv_usec = 0;
- if(timeoutMsec) {
- struct timeval increment;
- increment.tv_sec = timeoutMsec / 1000;
- increment.tv_usec = 1000 * (timeoutMsec % 1000);
- if(now)
- w.ttd = increment + *now;
- else {
- struct timeval realnow;
- gettimeofday(&realnow, 0);
- w.ttd = increment + realnow;
- }
- }
-
- w.tid=d_tid;
- w.key=key;
-
- d_waiters.insert(w);
-#ifdef MTASKERTIMING
- unsigned int diff=d_threads[d_tid].dt.ndiff()/1000;
- d_threads[d_tid].totTime+=diff;
-#endif
- notifyStackSwitchToKernel();
- pdns_swapcontext(*d_waiters.find(key)->context,d_kernel); // 'A' will return here when 'key' has arrived, hands over control to kernel first
- notifyStackSwitchDone();
-#ifdef MTASKERTIMING
- d_threads[d_tid].dt.start();
-#endif
- if(val && d_waitstatus==Answer)
- *val=d_waitval;
- d_tid=w.tid;
- if((char*)&w < d_threads[d_tid].highestStackSeen) {
- d_threads[d_tid].highestStackSeen = (char*)&w;
- }
- key=d_eventkey;
- return d_waitstatus;
-}
-
-//! yields control to the kernel or other threads
-/** Hands over control to the kernel, allowing other processes to run, or events to arrive */
-
-template<class Key, class Val, class Cmp>void MTasker<Key,Val,Cmp>::yield()
-{
- d_runQueue.push(d_tid);
- notifyStackSwitchToKernel();
- pdns_swapcontext(*d_threads[d_tid].context ,d_kernel); // give control to the kernel
- notifyStackSwitchDone();
-}
-
-//! reports that an event took place for which threads may be waiting
-/** From the kernel loop, sendEvent can be called to report that something occurred for which there may be waiters.
- \param key Key of the event for which threads may be waiting
- \param val If non-zero, pointer to the content of the event
- \return Returns -1 in case of error, 0 if there were no waiters, 1 if a thread was woken up.
-
- WARNING: when passing val as zero, d_waitval is undefined, and hence waitEvent will return undefined!
-*/
-template<class EventKey, class EventVal, class Cmp>int MTasker<EventKey,EventVal,Cmp>::sendEvent(const EventKey& key, const EventVal* val)
-{
- typename waiters_t::iterator waiter=d_waiters.find(key);
-
- if(waiter == d_waiters.end()) {
- //cerr<<"Event sent nobody was waiting for! " <<key << endl;
- return 0;
- }
- d_waitstatus=Answer;
- if(val)
- d_waitval=*val;
-
- d_tid=waiter->tid; // set tid
- d_eventkey=waiter->key; // pass waitEvent the exact key it was woken for
- auto userspace=std::move(waiter->context);
- d_waiters.erase(waiter); // removes the waitpoint
- notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
- pdns_swapcontext(d_kernel,*userspace); // swaps back to the above point 'A'
- notifyStackSwitchDone();
- return 1;
-}
-
-template<class Key, class Val, class Cmp> std::shared_ptr<pdns_ucontext_t> MTasker<Key,Val,Cmp>::getUContext()
-{
- auto uc = std::make_shared<pdns_ucontext_t>();
- if (d_cachedStacks.empty()) {
- uc->uc_stack.resize(d_stacksize + 1);
- }
- else {
- uc->uc_stack = std::move(d_cachedStacks.top());
- d_cachedStacks.pop();
- }
-
- uc->uc_link = &d_kernel; // come back to kernel after dying
-
-#ifdef PDNS_USE_VALGRIND
- uc->valgrind_id = VALGRIND_STACK_REGISTER(&uc->uc_stack[0],
- &uc->uc_stack[uc->uc_stack.size()-1]);
-#endif /* PDNS_USE_VALGRIND */
-
- return uc;
-}
-
-//! launches a new thread
-/** The kernel can call this to make a new thread, which starts at the function start and gets passed the val void pointer.
- \param start Pointer to the function which will form the start of the thread
- \param val A void pointer that can be used to pass data to the thread
-*/
-template<class Key, class Val, class Cmp>void MTasker<Key,Val,Cmp>::makeThread(tfunc_t *start, void* val)
-{
- auto uc = getUContext();
-
- ++d_threadsCount;
- auto& thread = d_threads[d_maxtid];
- auto mt = this;
- // we will get a better approximation when the task is executed, but that prevents notifying a stack at nullptr
- // on the first invocation
- d_threads[d_maxtid].startOfStack = &uc->uc_stack[uc->uc_stack.size()-1];
- thread.start = [start, val, mt]() {
- char dummy;
- mt->d_threads[mt->d_tid].startOfStack = mt->d_threads[mt->d_tid].highestStackSeen = &dummy;
- auto const tid = mt->d_tid;
- start (val);
- mt->d_zombiesQueue.push(tid);
- };
- pdns_makecontext (*uc, thread.start);
-
- thread.context = std::move(uc);
- d_runQueue.push(d_maxtid++); // will run at next schedule invocation
-}
-
-
-//! needs to be called periodically so threads can run and housekeeping can be performed
-/** The kernel should call this function every once in a while. It makes sense
- to call this function if you:
- - reported an event
- - called makeThread
- - want to have threads running waitEvent() to get a timeout if enough time passed
-
- \return Returns if there is more work scheduled and recalling schedule now would be useful
-
-*/
-template<class Key, class Val, class Cmp>bool MTasker<Key,Val,Cmp>::schedule(const struct timeval* now)
-{
- if(!d_runQueue.empty()) {
- d_tid=d_runQueue.front();
-#ifdef MTASKERTIMING
- d_threads[d_tid].dt.start();
-#endif
- notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
- pdns_swapcontext(d_kernel, *d_threads[d_tid].context);
- notifyStackSwitchDone();
-
- d_runQueue.pop();
- return true;
- }
- if (!d_zombiesQueue.empty()) {
- auto zombi = d_zombiesQueue.front();
- if (d_cachedStacks.size() < d_maxCachedStacks) {
- auto thread = d_threads.find(zombi);
- if (thread != d_threads.end()) {
- d_cachedStacks.push(std::move(thread->second.context->uc_stack));
- }
- d_threads.erase(thread);
- }
- else {
- d_threads.erase(zombi);
- }
- --d_threadsCount;
- d_zombiesQueue.pop();
- return true;
- }
- if(!d_waiters.empty()) {
- struct timeval rnow;
- if(!now)
- gettimeofday(&rnow, 0);
- else
- rnow = *now;
-
- typedef typename waiters_t::template index<KeyTag>::type waiters_by_ttd_index_t;
- // waiters_by_ttd_index_t& ttdindex=d_waiters.template get<KeyTag>();
- waiters_by_ttd_index_t& ttdindex=boost::multi_index::get<KeyTag>(d_waiters);
-
- for(typename waiters_by_ttd_index_t::iterator i=ttdindex.begin(); i != ttdindex.end(); ) {
- if(i->ttd.tv_sec && i->ttd < rnow) {
- d_waitstatus=TimeOut;
- d_eventkey=i->key; // pass waitEvent the exact key it was woken for
- auto uc = i->context;
- d_tid = i->tid;
- ttdindex.erase(i++); // removes the waitpoint
-
- notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
- pdns_swapcontext(d_kernel, *uc); // swaps back to the above point 'A'
- notifyStackSwitchDone();
- }
- else if(i->ttd.tv_sec)
- break;
- else
- ++i;
- }
- }
- return false;
-}
-
-//! returns true if there are no processes
-/** Call this to check if no processes are running anymore
- \return true if no processes are left
- */
-template<class Key, class Val, class Cmp>bool MTasker<Key,Val,Cmp>::noProcesses() const
-{
- return d_threadsCount == 0;
-}
-
-//! returns the number of processes running
-/** Call this to perhaps limit activities if too many threads are running
- \return number of processes running
- */
-template<class Key, class Val, class Cmp>unsigned int MTasker<Key,Val,Cmp>::numProcesses() const
-{
- return d_threadsCount;
-}
-
-//! gives access to the list of Events threads are waiting for
-/** The kernel can call this to get a list of Events threads are waiting for. This is very useful
- to setup 'select' or 'poll' or 'aio' events needed to satisfy these requests.
- getEvents clears the events parameter before filling it.
-
- \param events Vector which is to be filled with keys threads are waiting for
-*/
-template<class Key, class Val, class Cmp>void MTasker<Key,Val,Cmp>::getEvents(std::vector<Key>& events)
-{
- events.clear();
- for(typename waiters_t::const_iterator i=d_waiters.begin();i!=d_waiters.end();++i) {
- events.push_back(i->first);
- }
-}
-
-//! Returns the current Thread ID (tid)
-/** Processes can call this to get a numerical representation of their current thread ID.
- This can be useful for logging purposes.
-*/
-template<class Key, class Val, class Cmp>int MTasker<Key,Val,Cmp>::getTid() const
-{
- return d_tid;
-}
-
-//! Returns the maximum stack usage so far of this MThread
-template<class Key, class Val, class Cmp>uint64_t MTasker<Key,Val,Cmp>::getMaxStackUsage()
-{
- return d_threads[d_tid].startOfStack - d_threads[d_tid].highestStackSeen;
-}
-
-//! Returns the maximum stack usage so far of this MThread
-template<class Key, class Val, class Cmp>unsigned int MTasker<Key,Val,Cmp>::getUsec()
-{
-#ifdef MTASKERTIMING
- return d_threads[d_tid].totTime + d_threads[d_tid].dt.ndiff()/1000;
-#else
- return 0;
-#endif
-}
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-#include <cstdint>
-#include <ctime>
-#include <queue>
-#include <map>
-#include <memory>
-#include <stack>
-#include <vector>
-
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index/key_extractors.hpp>
-#include "namespaces.hh"
-#include "misc.hh"
-#include "mtasker_context.hh"
-
-using namespace ::boost::multi_index;
-
-// #define MTASKERTIMING 1
-
-//! The main MTasker class
-/** The main MTasker class. See the main page for more information.
- \tparam EventKey Type of the key with which events are to be identified. Defaults to int.
- \tparam EventVal Type of the content or value of an event. Defaults to int. Cannot be set to void.
- \note The EventKey needs to have an operator< defined because it is used as the key of an associative array
-*/
-
-template<class EventKey=int, class EventVal=int, class Cmp = std::less<EventKey>> class MTasker
-{
-private:
- pdns_ucontext_t d_kernel;
- std::queue<int> d_runQueue;
- std::queue<int> d_zombiesQueue;
-
- struct ThreadInfo
- {
- std::shared_ptr<pdns_ucontext_t> context;
- std::function<void(void)> start;
- const char* startOfStack;
- const char* highestStackSeen;
-#ifdef MTASKERTIMING
- CPUTime dt;
- unsigned int totTime;
-#endif
- };
-
- using pdns_mtasker_stack_t = std::vector<char, lazy_allocator<char>>;
- using mthreads_t = std::map<int, ThreadInfo>;
-
- mthreads_t d_threads;
- std::stack<pdns_mtasker_stack_t> d_cachedStacks;
- size_t d_stacksize;
- size_t d_threadsCount{0};
- size_t d_maxCachedStacks{0};
- int d_tid{0};
- int d_maxtid{0};
-
- EventVal d_waitval;
- enum waitstatusenum : int8_t {Error=-1,TimeOut=0,Answer} d_waitstatus;
-
-public:
- struct Waiter
- {
- EventKey key;
- std::shared_ptr<pdns_ucontext_t> context;
- struct timeval ttd;
- int tid;
- };
- struct KeyTag {};
-
- typedef multi_index_container<
- Waiter,
- indexed_by <
- ordered_unique<member<Waiter,EventKey,&Waiter::key>, Cmp>,
- ordered_non_unique<tag<KeyTag>, member<Waiter,struct timeval,&Waiter::ttd> >
- >
- > waiters_t;
-
- waiters_t d_waiters;
-
- void initMainStackBounds()
- {
-#ifdef HAVE_FIBER_SANITIZER
-
-#ifdef HAVE_PTHREAD_GETATTR_NP
- pthread_attr_t attr;
- pthread_attr_init(&attr);
- pthread_getattr_np(pthread_self(), &attr);
- pthread_attr_getstack(&attr, &t_mainStack, &t_mainStackSize);
- pthread_attr_destroy(&attr);
-#elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
- t_mainStack = pthread_get_stackaddr_np(pthread_self());
- t_mainStackSize = pthread_get_stacksize_np(pthread_self());
-#else
-#error Cannot determine stack size and base on this platform
-#endif
-
-#endif /* HAVE_FIBER_SANITIZER */
- }
-
- //! Constructor
- /** Constructor with a small default stacksize. If any of your threads exceeds this stack, your application will crash.
- This limit applies solely to the stack, the heap is not limited in any way. If threads need to allocate a lot of data,
- the use of new/delete is suggested.
- */
- MTasker(size_t stacksize=16*8192, size_t stackCacheSize=0) : d_stacksize(stacksize), d_maxCachedStacks(stackCacheSize), d_waitstatus(Error)
- {
- initMainStackBounds();
-
- // make sure our stack is 16-byte aligned to make all the architectures happy
- d_stacksize = d_stacksize >> 4 << 4;
- }
-
- typedef void tfunc_t(void *); //!< type of the pointer that starts a thread
- int waitEvent(EventKey &key, EventVal *val=nullptr, unsigned int timeoutMsec=0, const struct timeval* now=nullptr);
- void yield();
- int sendEvent(const EventKey& key, const EventVal* val=nullptr);
- void getEvents(std::vector<EventKey>& events);
- void makeThread(tfunc_t *start, void* val);
- bool schedule(const struct timeval* now=nullptr);
- bool noProcesses() const;
- unsigned int numProcesses() const;
- int getTid() const;
- uint64_t getMaxStackUsage();
- unsigned int getUsec();
-
-private:
- std::shared_ptr<pdns_ucontext_t> getUContext();
-
- EventKey d_eventkey; // for waitEvent, contains exact key it was awoken for
-};
-#include "mtasker.cc"
};
zone "wtest.com"{
- type master;
+ type primary;
file "wtest.com";
};
zone "nztest.com"{
- type master;
+ type secondary;
file "nztest.com";
+ primaries { 1.2.3.4:5678; };
};
zone "dnssec-parent.com"{
- type master;
+ type primary;
file "dnssec-parent.com";
};
zone "delegated.dnssec-parent.com"{
- type master;
+ type primary;
file "delegated.dnssec-parent.com";
};
zone "secure-delegated.dnssec-parent.com"{
- type master;
+ type primary;
file "secure-delegated.dnssec-parent.com";
};
zone "minimal.com"{
- type master;
+ type primary;
file "minimal.com";
};
zone "tsig.com"{
- type master;
+ type primary;
file "tsig.com";
};
zone "stest.com"{
- type master;
+ type primary;
file "stest.com";
};
if(buffer.length() > p.getMaxReplyLen()) {
g_log<<Logger::Error<<"Weird, trying to send a message that needs truncation, "<< buffer.length()<<" > "<<p.getMaxReplyLen()<<". Question was for "<<p.qdomain<<"|"<<p.qtype.toString()<<endl;
}
- if(sendmsg(p.getSocket(), &msgh, 0) < 0)
- g_log<<Logger::Error<<"Error sending reply with sendmsg (socket="<<p.getSocket()<<", dest="<<p.d_remote.toStringWithPort()<<"): "<<stringerror()<<endl;
+ if (sendOnNBSocket(p.getSocket(), &msgh) < 0) {
+ int err = errno;
+ g_log<<Logger::Error<<"Error sending reply with sendmsg (socket="<<p.getSocket()<<", dest="<<p.d_remote.toStringWithPort()<<"): "<<stringerror(err)<<endl;
+ }
}
bool UDPNameserver::receive(DNSPacket& packet, std::string& buffer)
unsigned int r;
for (int i = 0; i < 1024; i += 4) {
- r = dns_random(0xffffffff);
+ r = dns_random_uint32();
entropy.append((const char*)&r, 4);
}
}
else
content=mode;
- rr.dr.setContent(DNSRecordContent::mastermake(QType::TXT, 1, "\""+content+"\""));
+ rr.dr.setContent(DNSRecordContent::make(QType::TXT, 1, "\"" + content + "\""));
}
else if (target==idserver) {
// modes: disabled, hostname or custom
if(!tid.empty() && tid[0]!='"') { // see #6010 however
tid = "\"" + tid + "\"";
}
- rr.dr.setContent(DNSRecordContent::mastermake(QType::TXT, 1, tid));
+ rr.dr.setContent(DNSRecordContent::make(QType::TXT, 1, tid));
}
else {
r->setRcode(RCode::Refused);
DNSZoneRecord rr;
DNSName subdomain(target);
bool haveSomething=false;
+ bool haveCNAME = false;
#ifdef HAVE_LUA_RECORDS
bool doLua=g_doLuaRecord;
B.lookup(QType(QType::ANY), g_wildcarddnsname+subdomain, d_sd.domain_id, &p);
}
while(B.get(rr)) {
+ if (haveCNAME) {
+ continue;
+ }
#ifdef HAVE_LUA_RECORDS
if (rr.dr.d_type == QType::LUA && !d_dk.isPresigned(d_sd.qname)) {
if(!doLua) {
rr.dr.d_type = rec->d_type; // might be CNAME
rr.dr.setContent(r);
rr.scopeMask = p.getRealRemote().getBits(); // this makes sure answer is a specific as your question
+ if (rr.dr.d_type == QType::CNAME) {
+ haveCNAME = true;
+ *ret = {rr};
+ break;
+ }
ret->push_back(rr);
}
}
}
else
#endif
- if(rr.dr.d_type == p.qtype.getCode() || rr.dr.d_type == QType::CNAME || (p.qtype.getCode() == QType::ANY && rr.dr.d_type != QType::RRSIG)) {
+ if(rr.dr.d_type != QType::ENT && (rr.dr.d_type == p.qtype.getCode() || rr.dr.d_type == QType::CNAME || (p.qtype.getCode() == QType::ANY && rr.dr.d_type != QType::RRSIG))) {
+ if (rr.dr.d_type == QType::CNAME) {
+ haveCNAME = true;
+ ret->clear();
+ }
ret->push_back(rr);
}
*/
-int PacketHandler::trySuperMaster(const DNSPacket& p, const DNSName& tsigkeyname)
+int PacketHandler::tryAutoPrimary(const DNSPacket& p, const DNSName& tsigkeyname)
{
if(p.d_tcp)
{
// do it right now if the client is TCP
// rarely happens
- return trySuperMasterSynchronous(p, tsigkeyname);
+ return tryAutoPrimarySynchronous(p, tsigkeyname);
}
else
{
// queue it if the client is on UDP
- Communicator.addTrySuperMasterRequest(p);
+ Communicator.addTryAutoPrimaryRequest(p);
return 0;
}
}
-int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName& tsigkeyname)
+int PacketHandler::tryAutoPrimarySynchronous(const DNSPacket& p, const DNSName& tsigkeyname)
{
ComboAddress remote = p.getInnerRemote();
if(p.hasEDNSSubnet() && pdns::isAddressTrustedNotificationProxy(remote)) {
}
if(!haveNS) {
- g_log<<Logger::Error<<"While checking for supermaster, did not find NS for "<<p.qdomain<<" at: "<< remote <<endl;
+ g_log << Logger::Error << "While checking for autoprimary, did not find NS for " << p.qdomain << " at: " << remote << endl;
return RCode::ServFail;
}
DNSBackend *db;
if (!::arg().mustDo("allow-unsigned-autoprimary") && tsigkeyname.empty()) {
- g_log<<Logger::Error<<"Received unsigned NOTIFY for "<<p.qdomain<<" from potential supermaster "<<remote<<". Refusing."<<endl;
+ g_log << Logger::Error << "Received unsigned NOTIFY for " << p.qdomain << " from potential autoprimary " << remote << ". Refusing." << endl;
return RCode::Refused;
}
- if(!B.superMasterBackend(remote.toString(), p.qdomain, nsset, &nameserver, &account, &db)) {
- g_log<<Logger::Error<<"Unable to find backend willing to host "<<p.qdomain<<" for potential supermaster "<<remote<<". Remote nameservers: "<<endl;
+ if (!B.autoPrimaryBackend(remote.toString(), p.qdomain, nsset, &nameserver, &account, &db)) {
+ g_log << Logger::Error << "Unable to find backend willing to host " << p.qdomain << " for potential autoprimary " << remote << ". Remote nameservers: " << endl;
for(const auto& rr: nsset) {
if(rr.qtype==QType::NS)
g_log<<Logger::Error<<rr.content<<endl;
return RCode::Refused;
}
try {
- db->createSlaveDomain(remote.toString(), p.qdomain, nameserver, account);
+ db->createSecondaryDomain(remote.toString(), p.qdomain, nameserver, account);
DomainInfo di;
if (!db->getDomainInfo(p.qdomain, di, false)) {
- g_log << Logger::Error << "Failed to create " << p.qdomain << " for potential supermaster " << remote << endl;
+ g_log << Logger::Error << "Failed to create " << p.qdomain << " for potential autoprimary " << remote << endl;
return RCode::ServFail;
}
g_zoneCache.add(p.qdomain, di.id);
}
}
catch(PDNSException& ae) {
- g_log<<Logger::Error<<"Database error trying to create "<<p.qdomain<<" for potential supermaster "<<remote<<": "<<ae.reason<<endl;
+ g_log << Logger::Error << "Database error trying to create " << p.qdomain << " for potential autoprimary " << remote << ": " << ae.reason << endl;
return RCode::ServFail;
}
- g_log<<Logger::Warning<<"Created new slave zone '"<<p.qdomain<<"' from supermaster "<<remote<<endl;
+ g_log << Logger::Warning << "Created new secondary zone '" << p.qdomain << "' from autoprimary " << remote << endl;
return RCode::NoError;
}
was this notification from an approved address?
was this notification approved by TSIG?
We determine our internal SOA id (via UeberBackend)
- We determine the SOA at our (known) master
- if master is higher -> do stuff
+ We determine the SOA at our (known) primary
+ if primary is higher -> do stuff
*/
g_log<<Logger::Debug<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<endl;
if(!::arg().mustDo("secondary") && s_forwardNotify.empty()) {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" but slave support is disabled in the configuration"<<endl;
+ g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " but secondary support is disabled in the configuration" << endl;
return RCode::Refused;
}
DomainInfo di;
if(!B.getDomainInfo(p.qdomain, di, false) || !di.backend) {
if(::arg().mustDo("autosecondary")) {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" for which we are not authoritative, trying supermaster"<<endl;
- return trySuperMaster(p, p.getTSIGKeyname());
+ g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " for which we are not authoritative, trying autoprimary" << endl;
+ return tryAutoPrimary(p, p.getTSIGKeyname());
}
g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" for which we are not authoritative (Refused)"<<endl;
return RCode::Refused;
}
if(pdns::isAddressTrustedNotificationProxy(p.getInnerRemote())) {
- if(di.masters.empty()) {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from trusted-notification-proxy "<<p.getRemoteString()<<", zone does not have any masters defined (Refused)"<<endl;
+ if (di.primaries.empty()) {
+ g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from trusted-notification-proxy " << p.getRemoteString() << ", zone does not have any primaries defined (Refused)" << endl;
return RCode::Refused;
}
g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from trusted-notification-proxy "<<p.getRemoteString()<<endl;
g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " but we are primary (Refused)" << endl;
return RCode::Refused;
}
- else if(!di.isMaster(p.getInnerRemote())) {
- g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" which is not a master (Refused)"<<endl;
+ else if (!di.isPrimary(p.getInnerRemote())) {
+ g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " which is not a primary (Refused)" << endl;
return RCode::Refused;
}
if(::arg().mustDo("secondary")) {
g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" - queueing check"<<endl;
di.receivedNotify = true;
- Communicator.addSlaveCheckRequest(di, p.getInnerRemote());
+ Communicator.addSecondaryCheckRequest(di, p.getInnerRemote());
}
return 0;
}
nodata=true;
}
else {
+ bestmatch = target;
for(auto& rr: rrset) {
rr.wildcardname = rr.dr.d_name;
- rr.dr.d_name=bestmatch=target;
+ rr.dr.d_name = bestmatch;
if(rr.dr.d_type == QType::CNAME) {
retargeted=true;
UeberBackend *getBackend();
- int trySuperMasterSynchronous(const DNSPacket& p, const DNSName& tsigkeyname);
+ int tryAutoPrimarySynchronous(const DNSPacket& p, const DNSName& tsigkeyname);
static NetmaskGroup s_allowNotifyFrom;
static set<string> s_forwardNotify;
static bool s_SVCAutohints;
static const std::shared_ptr<CDSRecordContent> s_deleteCDSContent;
private:
- int trySuperMaster(const DNSPacket& p, const DNSName& tsigkeyname);
+ int tryAutoPrimary(const DNSPacket& p, const DNSName& tsigkeyname);
int processNotify(const DNSPacket& );
void addRootReferral(DNSPacket& r);
int doChaosRequest(const DNSPacket& p, std::unique_ptr<DNSPacket>& r, DNSName &target) const;
bool d_logDNSDetails;
bool d_doDNAME;
bool d_doExpandALIAS;
- bool d_dnssec;
+ bool d_dnssec{false};
SOAData d_sd;
std::unique_ptr<AuthLua4> d_pdl;
std::unique_ptr<AuthLua4> d_update_policy_lua;
+++ /dev/null
-#!/bin/sh
-# chkconfig: - 80 75
-# description: PDNS is a versatile high performance authoritative nameserver
-
-### BEGIN INIT INFO
-# Provides: pdns
-# Required-Start: $remote_fs $network $syslog
-# Required-Stop: $remote_fs $network $syslog
-# Should-Start:
-# Should-Stop:
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
-# Short-Description: PowerDNS authoritative server
-# Description: PowerDNS authoritative server
-### END INIT INFO
-
-set -e
-
-exec_prefix=@exec_prefix@
-BINARYPATH=@bindir@
-SBINARYPATH=@sbindir@
-SOCKETPATH=@socketdir@/pdns
-DAEMON_ARGS=""
-
-[ -f "$SBINARYPATH/pdns_server" ] || exit 0
-
-[ -r /etc/default/pdns ] && . /etc/default/pdns
-
-[ "$START" = "no" ] && exit 0
-
-# Make sure that /var/run exists
-mkdir -p $SOCKETPATH
-cd $SOCKETPATH
-suffix=$(basename $0 | cut -d- -f2- -s)
-if [ -n "$suffix" ]
-then
- EXTRAOPTS=--config-name=$suffix
- PROGNAME=pdns-$suffix
-else
- PROGNAME=pdns
-fi
-
-pdns_server="$SBINARYPATH/pdns_server $DAEMON_ARGS $EXTRAOPTS"
-
-doPC()
-{
- ret=$($BINARYPATH/pdns_control $EXTRAOPTS $1 $2 2> /dev/null)
-}
-
-NOTRUNNING=0
-doPC ping || NOTRUNNING=$?
-
-case "$1" in
- status)
- if test "$NOTRUNNING" = "0"
- then
- doPC status
- echo $ret
- else
- echo "not running"
- exit 3
- fi
- ;;
-
- stop)
- echo -n "Stopping PowerDNS authoritative nameserver: "
- if test "$NOTRUNNING" = "0"
- then
- doPC quit
- echo $ret
- else
- echo "not running"
- fi
- ;;
-
-
- force-stop)
- echo -n "Stopping PowerDNS authoritative nameserver: "
- killall -v -9 pdns_server
- echo "killed"
- ;;
-
- start)
- echo -n "Starting PowerDNS authoritative nameserver: "
- if test "$NOTRUNNING" = "0"
- then
- echo "already running"
- else
- if $pdns_server --daemon --guardian=yes
- then
- echo "started"
- else
- echo "starting failed"
- exit 1
- fi
- fi
- ;;
-
- force-reload | restart)
- echo -n "Restarting PowerDNS authoritative nameserver: "
- if test "$NOTRUNNING" = "1"
- then
- echo "not running, starting"
- else
-
- echo -n stopping and waiting..
- doPC quit
- sleep 3
- echo done
- fi
- $0 start
- ;;
-
- reload)
- echo -n "Reloading PowerDNS authoritative nameserver: "
- if test "$NOTRUNNING" = "0"
- then
- doPC cycle
- echo requested reload
- else
- echo not running yet
- $0 start
- fi
- ;;
-
- monitor)
- if test "$NOTRUNNING" = "0"
- then
- echo "already running"
- else
- $pdns_server --daemon=no --guardian=no --control-console --loglevel=9
- fi
- ;;
-
- dump)
- if test "$NOTRUNNING" = "0"
- then
- doPC list
- echo $ret
- else
- echo "not running"
- fi
- ;;
-
- show)
- if [ $# -lt 2 ]
- then
- echo Insufficient parameters
- exit
- fi
- if test "$NOTRUNNING" = "0"
- then
- echo -n "$2="
- doPC show $2 ; echo $ret
- else
- echo "not running"
- fi
- ;;
-
- mrtg)
- if [ $# -lt 2 ]
- then
- echo Insufficient parameters
- exit
- fi
- if test "$NOTRUNNING" = "0"
- then
- doPC show $2 ; echo $ret
- if [ "$3x" != "x" ]
- then
- doPC show $3 ; echo $ret
- else
- echo 0
- fi
- doPC uptime ; echo $ret
- echo PowerDNS daemon
- else
- echo "not running"
- fi
-
- ;;
-
- cricket)
- if [ $# -lt 2 ]
- then
- echo Insufficient parameters
- exit
- fi
- if test "$NOTRUNNING" = "0"
- then
- doPC show $2 ; echo $ret
- else
- echo "not running"
- fi
-
- ;;
-
-
-
- *)
- echo pdns [start\|stop\|force-reload\|reload\|restart\|status\|dump\|show\|mrtg\|cricket\|monitor]
-
- ;;
-esac
-
-
Documentation=man:pdns_server(1) man:pdns_control(1)
Documentation=https://doc.powerdns.com
Wants=network-online.target
-After=network-online.target mysqld.service postgresql.service slapd.service mariadb.service time-sync.target
+After=network-online.target mysql.service mysqld.service postgresql.service slapd.service mariadb.service time-sync.target
[Service]
ExecStart=@sbindir@/pdns_server --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no
-
+#include <boost/smart_ptr/make_shared_array.hpp>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "statbag.hh"
#include "base32.hh"
#include "base64.hh"
+#include "dns.hh"
#include <boost/program_options.hpp>
#include <boost/assign/std/vector.hpp>
#include <fstream>
#include <utility>
#include <cerrno>
+#include <sys/stat.h>
#include <termios.h> //termios, TCSANOW, ECHO, ICANON
#include "opensslsigners.hh"
#ifdef HAVE_LIBSODIUM
}
#endif
openssl_seed();
- /* init rng before chroot */
- dns_random_init();
if (!::arg()["chroot"].empty()) {
if (chroot(::arg()["chroot"].c_str())<0 || chdir("/") < 0) {
while(B.get(rr)) {
hits++;
}
- B.lookup(QType(QType::A), DNSName(std::to_string(random()))+domain, -1);
+ B.lookup(QType(QType::A), DNSName(std::to_string(dns_random_uint32()))+domain, -1);
while(B.get(rr)) {
}
misses++;
return 1;
}
} catch(const PDNSException &e) {
- if (di.kind == DomainInfo::Slave) {
+ if (di.kind == DomainInfo::Secondary) {
cout << "[Error] non-IP address for primaries: " << e.reason << endl;
numerrors++;
}
rr.content = "\""+rr.content+"\"";
try {
- shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+ shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
string tmp=drc->serialize(rr.qname);
tmp = drc->getZoneRepresentation(true);
if (rr.qtype.getCode() != QType::AAAA) {
}
if (rr.qtype.getCode() == QType::SVCB || rr.qtype.getCode() == QType::HTTPS) {
- shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+ shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
// I, too, like to live dangerously
auto svcbrc = std::dynamic_pointer_cast<SVCBBaseRecordContent>(drc);
if (svcbrc->getPriority() == 0 && svcbrc->hasParams()) {
}
svcbAliases.insert(rr.qname);
}
- svcbTargets.emplace(std::make_tuple(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint)));
+ svcbTargets.emplace(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint));
svcbRecords.insert(rr.qname);
break;
case QType::HTTPS:
}
httpsAliases.insert(rr.qname);
}
- httpsTargets.emplace(std::make_tuple(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint)));
+ httpsTargets.emplace(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint));
httpsRecords.insert(rr.qname);
break;
}
}
}
- for (const auto &svcb : svcbTargets) {
- const auto& name = std::get<0>(svcb);
- const auto& target = std::get<2>(svcb);
- auto prio = std::get<1>(svcb);
- auto v4hintsAuto = std::get<3>(svcb);
- auto v6hintsAuto = std::get<4>(svcb);
-
+ for (const auto& [name, prio, target, v4hintsAuto, v6hintsAuto] : svcbTargets) {
if (name == target) {
cout<<"[Error] SVCB record "<<name<<" has itself as target."<<endl;
numerrors++;
}
}
- auto trueTarget = target.isRoot() ? name : target;
+ const auto& trueTarget = target.isRoot() ? name : target;
if (prio > 0) {
if(v4hintsAuto && arecords.find(trueTarget) == arecords.end()) {
cout << "[warning] SVCB record for "<< name << " has automatic IPv4 hints, but no A-record for the target at "<< trueTarget <<" exists."<<endl;
}
}
- for (const auto &httpsRecord : httpsTargets) {
- const auto& name = std::get<0>(httpsRecord);
- const auto& target = std::get<2>(httpsRecord);
- auto prio = std::get<1>(httpsRecord);
- auto v4hintsAuto = std::get<3>(httpsRecord);
- auto v6hintsAuto = std::get<4>(httpsRecord);
-
+ for (const auto& [name, prio, target, v4hintsAuto, v6hintsAuto] : httpsTargets) {
if (name == target) {
cout<<"[Error] HTTPS record "<<name<<" has itself as target."<<endl;
numerrors++;
for (auto const &rr : checkCNAME) {
DNSName target;
- shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+ shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
switch (rr.qtype) {
case QType::MX:
target = std::dynamic_pointer_cast<MXRecordContent>(drc)->d_mxname;
B.getAllDomains(&domainInfo, true, true);
int errors=0;
- for(auto di : domainInfo) {
+ for (auto& di : domainInfo) {
if (checkZone(dk, B, di.zone) > 0) {
errors++;
}
errors++;
}
- seenInfos.insert(di);
+ seenInfos.insert(std::move(di));
- if(errors && exitOnError)
+ if (errors && exitOnError) {
return EXIT_FAILURE;
+ }
}
cout<<"Checked "<<domainInfo.size()<<" zones, "<<errors<<" had errors."<<endl;
if(!errors)
sd.db->startTransaction(zone, -1);
- if (!sd.db->replaceRRSet(sd.domain_id, zone, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+ if (!sd.db->replaceRRSet(sd.domain_id, zone, rr.qtype, {rr})) {
sd.db->abortTransaction();
cerr<<"Backend did not replace SOA record. Backend might not support this operation."<<endl;
return -1;
cerr << "Zone '" << zone << "' not found!" << endl;
return EXIT_FAILURE;
}
+
+ /* ensure that the temporary file will only
+ be accessible by the current user, not even
+ by other users in the same group, and certainly
+ not by other users.
+ */
+ umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
vector<DNSRecord> pre, post;
char tmpnam[]="/tmp/pdnsutil-XXXXXX";
int tmpfd=mkstemp(tmpnam);
cerr << col.red() << col.bold() << "There was a problem with your zone" << col.rst() << "\nOptions are: (e)dit your changes, (r)etry with original zone, (a)pply change anyhow, (q)uit: " << std::flush;
int c=read1char();
cerr<<"\n";
- if(c!='a')
+ if(c=='e') {
post.clear();
- if(c=='e')
goto editMore;
- else if(c=='r')
+ } else if(c=='r') {
+ post.clear();
goto editAgain;
- else if(c=='q')
+ } else if(c=='q') {
return EXIT_FAILURE;
- else if(c!='a')
+ } else if(c!='a') {
goto reAsk;
+ }
}
haveSOA = true;
}
try {
- DNSRecordContent::mastermake(rr.qtype, QClass::IN, rr.content);
+ DNSRecordContent::make(rr.qtype, QClass::IN, rr.content);
}
catch (const PDNSException &pe) {
cerr<<"Bad record content in record for "<<rr.qname<<"|"<<rr.qtype.toString()<<": "<<pe.reason<<endl;
return EXIT_SUCCESS;
}
-static int createSlaveZone(const vector<string>& cmds) {
+static int createSecondaryZone(const vector<string>& cmds)
+{
UeberBackend B;
DomainInfo di;
DNSName zone(cmds.at(1));
cerr << "Zone '" << zone << "' exists already" << endl;
return EXIT_FAILURE;
}
- vector<ComboAddress> masters;
+ vector<ComboAddress> primaries;
for (unsigned i=2; i < cmds.size(); i++) {
- masters.emplace_back(cmds.at(i), 53);
+ primaries.emplace_back(cmds.at(i), 53);
}
- cerr << "Creating secondary zone '" << zone << "', with primaries '" << comboAddressVecToString(masters) << "'" << endl;
- B.createDomain(zone, DomainInfo::Slave, masters, "");
+ cerr << "Creating secondary zone '" << zone << "', with primaries '" << comboAddressVecToString(primaries) << "'" << endl;
+ B.createDomain(zone, DomainInfo::Secondary, primaries, "");
if(!B.getDomainInfo(zone, di)) {
cerr << "Zone '" << zone << "' was not created!" << endl;
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
-static int changeSlaveZoneMaster(const vector<string>& cmds) {
+static int changeSecondaryZonePrimary(const vector<string>& cmds)
+{
UeberBackend B;
DomainInfo di;
DNSName zone(cmds.at(1));
cerr << "Zone '" << zone << "' doesn't exist" << endl;
return EXIT_FAILURE;
}
- vector<ComboAddress> masters;
+ vector<ComboAddress> primaries;
for (unsigned i=2; i < cmds.size(); i++) {
- masters.emplace_back(cmds.at(i), 53);
+ primaries.emplace_back(cmds.at(i), 53);
}
- cerr << "Updating secondary zone '" << zone << "', primaries to '" << comboAddressVecToString(masters) << "'" << endl;
+ cerr << "Updating secondary zone '" << zone << "', primaries to '" << comboAddressVecToString(primaries) << "'" << endl;
try {
- di.backend->setMasters(zone, masters);
+ di.backend->setPrimaries(zone, primaries);
return EXIT_SUCCESS;
}
catch (PDNSException& e) {
cout<<"Current records for "<<rr.qname<<" IN "<<rr.qtype.toString()<<" will be replaced"<<endl;
}
for(auto i = contentStart ; i < cmds.size() ; ++i) {
- rr.content = DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, cmds.at(i))->getZoneRepresentation(true);
+ rr.content = DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, cmds.at(i))->getZoneRepresentation(true);
newrrs.push_back(rr);
}
return EXIT_SUCCESS;
}
-// addSuperMaster add a new autoprimary
-static int addSuperMaster(const std::string &IP, const std::string &nameserver, const std::string &account)
+// addAutoPrimary add a new autoprimary
+static int addAutoPrimary(const std::string& IP, const std::string& nameserver, const std::string& account)
{
UeberBackend B("default");
const AutoPrimary primary(IP, nameserver, account);
- if ( B.superMasterAdd(primary) ){
+ if (B.autoPrimaryAdd(primary)) {
return EXIT_SUCCESS;
}
cerr<<"could not find a backend with autosecondary support"<<endl;
throw runtime_error("No backends available for DNSSEC key storage");
}
- ChunkedSigningPipe csp(DNSName(zone), true, cores);
+ ChunkedSigningPipe csp(DNSName(zone), true, cores, 100);
vector<DNSZoneRecord> signatures;
uint32_t rnd;
DTime dt;
dt.set();
for(unsigned int n=0; n < 100000; ++n) {
- rnd = dns_random(UINT32_MAX);
+ rnd = dns_random_uint32();
snprintf(tmp, sizeof(tmp), "%d.%d.%d.%d",
octets[0], octets[1], octets[2], octets[3]);
rr.content=tmp;
if(rr.qtype.getCode() == QType::DNSKEY) {
cerr<<"got DNSKEY!"<<endl;
apex=rr.qname;
- drc = *std::dynamic_pointer_cast<DNSKEYRecordContent>(DNSRecordContent::mastermake(QType::DNSKEY, QClass::IN, rr.content));
+ drc = *std::dynamic_pointer_cast<DNSKEYRecordContent>(DNSRecordContent::make(QType::DNSKEY, QClass::IN, rr.content));
}
else if(rr.qtype.getCode() == QType::RRSIG) {
cerr<<"got RRSIG"<<endl;
- rrc = *std::dynamic_pointer_cast<RRSIGRecordContent>(DNSRecordContent::mastermake(QType::RRSIG, QClass::IN, rr.content));
+ rrc = *std::dynamic_pointer_cast<RRSIGRecordContent>(DNSRecordContent::make(QType::RRSIG, QClass::IN, rr.content));
}
else if(rr.qtype.getCode() == QType::DS) {
cerr<<"got DS"<<endl;
- dsrc = *std::dynamic_pointer_cast<DSRecordContent>(DNSRecordContent::mastermake(QType::DS, QClass::IN, rr.content));
+ dsrc = *std::dynamic_pointer_cast<DSRecordContent>(DNSRecordContent::make(QType::DS, QClass::IN, rr.content));
}
else {
qname = rr.qname;
- toSign.insert(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+ toSign.insert(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
}
}
return EXIT_SUCCESS;
}
-static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = false)
+static bool showZone(DNSSECKeeper& dnsseckeeper, const DNSName& zone, bool exportDS = false) // NOLINT(readability-function-cognitive-complexity)
{
UeberBackend B("default");
DomainInfo di;
}
}
else if (di.isSecondaryType()) {
- cout << "Primar" << addS(di.masters, "y", "ies") << ": ";
- for(const auto& m : di.masters)
+ cout << "Primar" << addS(di.primaries, "y", "ies") << ": ";
+ for (const auto& m : di.primaries)
cout<<m.toStringWithPort()<<" ";
cout<<endl;
struct tm tm;
}
}
- if(!dk.isSecuredZone(zone)) {
+ if(!dnsseckeeper.isSecuredZone(zone)) {
auto &outstream = (exportDS ? cerr : cout);
outstream << "Zone is not actively secured" << endl;
if (exportDS) {
NSEC3PARAMRecordContent ns3pr;
bool narrow = false;
- bool haveNSEC3=dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
+ bool haveNSEC3=dnsseckeeper.getNSEC3PARAM(zone, &ns3pr, &narrow);
- DNSSECKeeper::keyset_t keyset=dk.getKeys(zone);
+ DNSSECKeeper::keyset_t keyset=dnsseckeeper.getKeys(zone);
if (!exportDS) {
std::vector<std::string> meta;
}
- if (dk.isPresigned(zone)) {
+ if (dnsseckeeper.isPresigned(zone)) {
if (!exportDS) {
cout <<"Zone is presigned"<<endl;
}
cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
}
cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA256).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
- try {
- string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_GOST).getZoneRepresentation();
- cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( GOST R 34.11-94 digest )" << endl;
+ if (g_verbose) {
+ try {
+ string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_GOST).getZoneRepresentation();
+ cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( GOST R 34.11-94 digest )" << endl;
+ }
+ catch(...)
+ {}
}
- catch(...)
- {}
try {
string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA384).getZoneRepresentation();
cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( SHA-384 digest )" << endl;
cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
}
cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA256).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
- try {
- string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_GOST).getZoneRepresentation();
- cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( GOST R 34.11-94 digest )" << endl;
+ if (g_verbose) {
+ try {
+ string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_GOST).getZoneRepresentation();
+ cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( GOST R 34.11-94 digest )" << endl;
+ }
+ catch(...)
+ {}
}
- catch(...)
- {}
try {
string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA384).getZoneRepresentation();
cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( SHA-384 digest )" << endl;
static bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
{
// temp var for addKey
- int64_t id;
+ int64_t id{-1};
// parse attribute
string k_algo = ::arg()["default-ksk-algorithm"];
return false;
}
- if(di.kind == DomainInfo::Slave)
- {
+ if (di.kind == DomainInfo::Secondary) {
cerr << "Warning! This is a secondary zone! If this was a mistake, please run" << endl;
cerr<<"pdnsutil disable-dnssec "<<zone<<" right now!"<<endl;
}
cout<<"Constructing UeberBackend"<<endl;
UeberBackend B("default");
cout<<"Picking first backend - if this is not what you want, edit launch line!"<<endl;
- DNSBackend *db = B.backends[0];
+ DNSBackend *db = B.backends[0].get();
cout << "Creating secondary zone " << zone << endl;
- db->createSlaveDomain("127.0.0.1", zone, "", "_testschema");
+ db->createSecondaryDomain("127.0.0.1", zone, "", "_testschema");
cout << "Secondary zone created" << endl;
DomainInfo di;
return 0;
}
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): TODO Clean this function up.
int main(int argc, char** argv)
try
{
cout << " [producer|consumer]" << endl;
cout << " [coo|unique|group] VALUE" << endl;
cout << " [VALUE ...]" << endl;
- cout << "set-catalog ZONE CATALOG Change the catalog of ZONE to CATALOG" << endl;
+ cout << "set-catalog ZONE CATALOG Change the catalog of ZONE to CATALOG. Setting CATALOG to an empty "" removes ZONE from the catalog it is in" << endl;
cout << "set-account ZONE ACCOUNT Change the account (owner) of ZONE to ACCOUNT" << endl;
cout << "set-nsec3 ZONE ['PARAMS' [narrow]] Enable NSEC3 with PARAMS. Optionally narrow" << endl;
cout << "set-presigned ZONE Use presigned RRSIGs from storage" << endl;
// DNSResourceRecord rr;
// rr.qtype = DNSRecordContent::TypeToNumber(cmds.at(1));
// rr.content = cmds.at(2);
- auto drc = DNSRecordContent::mastermake(DNSRecordContent::TypeToNumber(cmds.at(1)), QClass::IN, cmds.at(2));
+ auto drc = DNSRecordContent::make(DNSRecordContent::TypeToNumber(cmds.at(1)), QClass::IN, cmds.at(2));
cout<<makeLuaString(drc->serialize(DNSName(), true))<<endl;
return 0;
return EXIT_FAILURE;
}
}
- int64_t id;
+ int64_t id{-1};
if (!dk.addKey(zone, keyOrZone, algorithm, id, bits, active, published)) {
cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
return 1;
}
return createZone(DNSName(cmds.at(1)), cmds.size() > 2 ? DNSName(cmds.at(2)) : DNSName());
}
- else if (cmds.at(0) == "create-secondary-zone" || cmds.at(0) == "create-slave-zone") {
+ else if (cmds.at(0) == "create-secondary-zone") {
if(cmds.size() < 3 ) {
cerr << "Syntax: pdnsutil create-secondary-zone ZONE primary-ip [primary-ip..]" << endl;
return 0;
}
- return createSlaveZone(cmds);
+ return createSecondaryZone(cmds);
}
- else if (cmds.at(0) == "change-secondary-zone-primary" || cmds.at(0) == "change-slave-zone-master") {
+ else if (cmds.at(0) == "change-secondary-zone-primary") {
if(cmds.size() < 3 ) {
cerr << "Syntax: pdnsutil change-secondary-zone-primary ZONE primary-ip [primary-ip..]" << endl;
return 0;
}
- return changeSlaveZoneMaster(cmds);
+ return changeSecondaryZonePrimary(cmds);
}
else if (cmds.at(0) == "add-record") {
if(cmds.size() < 5) {
}
return addOrReplaceRecord(true, cmds);
}
- else if (cmds.at(0) == "add-autoprimary" || cmds.at(0) == "add-supermaster") {
+ else if (cmds.at(0) == "add-autoprimary" || cmds.at(0) == "add-autoprimary") {
if(cmds.size() < 3) {
cerr << "Syntax: pdnsutil add-autoprimary IP NAMESERVER [account]" << endl;
return 0;
}
- exit(addSuperMaster(cmds.at(1), cmds.at(2), cmds.size() > 3 ? cmds.at(3) : ""));
+ exit(addAutoPrimary(cmds.at(1), cmds.at(2), cmds.size() > 3 ? cmds.at(3) : ""));
}
else if (cmds.at(0) == "remove-autoprimary") {
if(cmds.size() < 3) {
const auto algorithm = pdns::checked_stoi<unsigned int>(cmds.at(3));
errno = 0;
- std::unique_ptr<std::FILE, decltype(&std::fclose)> fp{std::fopen(filename.c_str(), "r"), &std::fclose};
- if (fp == nullptr) {
+ pdns::UniqueFilePtr filePtr{std::fopen(filename.c_str(), "r")};
+ if (filePtr == nullptr) {
auto errMsg = pdns::getMessageFromErrno(errno);
throw runtime_error("Failed to open PEM file `" + filename + "`: " + errMsg);
}
DNSKEYRecordContent drc;
- shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, algorithm, *fp, filename)};
+ shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, algorithm, *filePtr, filename)};
if (!key) {
cerr << "Could not convert key from PEM to internal format" << endl;
return 1;
}
dpk.setKey(key, flags, algo);
- int64_t id;
+ int64_t id{-1};
if (!dk.addKey(DNSName(zone), dpk, id)) {
cerr << "Adding key failed, perhaps DNSSEC not enabled in configuration?" << endl;
return 1;
}
dpk.setKey(key, flags, algo);
- int64_t id;
+ int64_t id{-1};
if (!dk.addKey(DNSName(zone), dpk, id, active, published)) {
cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
return 1;
}
DNSName zname(cmds.at(1));
string name = cmds.at(2);
- if (cmds.at(3) == "primary" || cmds.at(3) == "master" || cmds.at(3) == "producer")
+ if (cmds.at(3) == "primary" || cmds.at(3) == "producer")
metaKey = "TSIG-ALLOW-AXFR";
- else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer" || cmds.at(3) == "slave")
+ else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer")
metaKey = "AXFR-MASTER-TSIG";
else {
cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
}
DNSName zname(cmds.at(1));
string name = cmds.at(2);
- if (cmds.at(3) == "primary" || cmds.at(3) == "producer" || cmds.at(3) == "master")
+ if (cmds.at(3) == "primary" || cmds.at(3) == "producer")
metaKey = "TSIG-ALLOW-AXFR";
- else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer" || cmds.at(3) == "slave")
+ else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer")
metaKey = "AXFR-MASTER-TSIG";
else {
cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
return 1;
}
- int64_t id;
bool keyOrZone = (cmds.at(4) == "ksk" ? true : false);
string module = cmds.at(5);
string slot = cmds.at(6);
// make sure this key isn't being reused.
B.getDomainKeys(zone, keys);
- id = -1;
+ int64_t id{-1};
for(DNSBackend::KeyData& kd : keys) {
if (kd.content == iscString.str()) {
// it's this one, I guess...
}
else if (cmds.at(0) == "b2b-migrate") {
if (cmds.size() < 3) {
- cerr<<"Usage: b2b-migrate OLD NEW"<<endl;
+ cerr << "Usage: b2b-migrate OLD NEW" << endl;
return 1;
}
- DNSBackend *src = nullptr;
- DNSBackend *tgt = nullptr;
+ if (cmds.at(1) == cmds.at(2)) {
+ cerr << "Error: b2b-migrate OLD NEW: OLD cannot be the same as NEW" << endl;
+ return 1;
+ }
- for(DNSBackend *b : BackendMakers().all()) {
- if (b->getPrefix() == cmds.at(1))
- src = b;
- if (b->getPrefix() == cmds.at(2))
- tgt = b;
+ unique_ptr<DNSBackend> src{nullptr};
+ unique_ptr<DNSBackend> tgt{nullptr};
+
+ for (auto& backend : BackendMakers().all()) {
+ if (backend->getPrefix() == cmds.at(1)) {
+ src = std::move(backend);
+ }
+ else if (backend->getPrefix() == cmds.at(2)) {
+ tgt = std::move(backend);
+ }
}
+
if (src == nullptr) {
cerr << "Unknown source backend '" << cmds.at(1) << "'" << endl;
return 1;
DNSResourceRecord rr;
cout<<"Processing '"<<di.zone<<"'"<<endl;
// create zone
- if (!tgt->createDomain(di.zone, di.kind, di.masters, di.account)) throw PDNSException("Failed to create zone");
+ if (!tgt->createDomain(di.zone, di.kind, di.primaries, di.account))
+ throw PDNSException("Failed to create zone");
if (!tgt->getDomainInfo(di.zone, di_new)) throw PDNSException("Failed to create zone");
// move records
if (!src->list(di.zone, di.id, true)) throw PDNSException("Failed to list records");
Comment c;
while(src->getComment(c)) {
c.domain_id = di_new.id;
- tgt->feedComment(c);
+ if (!tgt->feedComment(c)) {
+ throw PDNSException("Target backend does not support comments - remove them first");
+ }
nc++;
}
}
return 1;
}
- DNSBackend *db = nullptr;
+ std::unique_ptr<DNSBackend> matchingBackend{nullptr};
- for(DNSBackend *b : BackendMakers().all()) {
- if (b->getPrefix() == cmds.at(1))
- db = b;
+ for (auto& backend : BackendMakers().all()) {
+ if (backend->getPrefix() == cmds.at(1)) {
+ matchingBackend = std::move(backend);
+ }
}
- if (db == nullptr) {
+ if (matchingBackend == nullptr) {
cerr << "Unknown backend '" << cmds.at(1) << "'" << endl;
return 1;
}
- for(auto i=next(begin(cmds),2); i != end(cmds); ++i) {
- cerr<<"== "<<*i<<endl;
- cout<<db->directBackendCmd(*i);
+ for (auto i = next(begin(cmds), 2); i != end(cmds); ++i) {
+ cerr << "== " << *i << endl;
+ cout << matchingBackend->directBackendCmd(*i);
}
return 0;
class P11KitAttribute {
private:
CK_ATTRIBUTE_TYPE type;
- CK_BYTE ckByte;
- CK_ULONG ckLong;
+ CK_BYTE ckByte{0};
+ CK_ULONG ckLong{0};
std::string ckString;
CkaValueType ckType;
std::unique_ptr<unsigned char[]> buffer;
CK_SESSION_HANDLE d_session;
CK_SLOT_ID d_slot;
CK_RV d_err{};
+ std::string d_pin;
void logError(const std::string& operation) const {
if (d_err) {
}
}
- bool Login(const std::string& pin) {
- if (d_logged_in) return true;
+ bool Login(const std::string& pin, CK_USER_TYPE userType=CKU_USER) {
+ if (userType == CKU_USER && d_logged_in) {
+ return true;
+ }
auto uPin = std::make_unique<unsigned char[]>(pin.size());
memcpy(uPin.get(), pin.c_str(), pin.size());
- d_err = d_functions->C_Login(this->d_session, CKU_USER, uPin.get(), pin.size());
- memset(uPin.get(), 0, pin.size());
+ d_err = d_functions->C_Login(this->d_session, userType, uPin.get(), pin.size());
logError("C_Login");
- if (d_err == 0) {
+ if (d_err == 0 && userType == CKU_USER) {
d_logged_in = true;
+ d_pin = pin;
}
return d_logged_in;
}
+ bool Relogin() {
+ return Login(d_pin, CKU_CONTEXT_SPECIFIC);
+ }
+
bool LoggedIn() const { return d_logged_in; }
CK_SESSION_HANDLE& Session() { return d_session; }
private:
std::shared_ptr<LockGuarded<Pkcs11Slot>> d_slot;
- CK_OBJECT_HANDLE d_public_key;
- CK_OBJECT_HANDLE d_private_key;
- CK_KEY_TYPE d_key_type;
+ CK_OBJECT_HANDLE d_public_key{0};
+ CK_OBJECT_HANDLE d_private_key{0};
+ CK_KEY_TYPE d_key_type{0};
+ bool d_always_auth{false};
CK_ULONG d_bits;
std::string d_exponent;
auto slot = d_slot->lock();
std::vector<P11KitAttribute> attr;
std::vector<CK_OBJECT_HANDLE> key;
- attr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PRIVATE_KEY));
-// attr.push_back(P11KitAttribute(CKA_SIGN, (char)CK_TRUE));
- attr.push_back(P11KitAttribute(CKA_LABEL, d_label));
+ attr.emplace_back(CKA_CLASS, (unsigned long)CKO_PRIVATE_KEY);
+ attr.emplace_back(CKA_LABEL, d_label);
FindObjects2(*slot, attr, key, 1);
if (key.size() == 0) {
g_log<<Logger::Warning<<"Cannot load PKCS#11 private key "<<d_label<<std::endl;;
}
d_private_key = key[0];
attr.clear();
- attr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PUBLIC_KEY));
-// attr.push_back(P11KitAttribute(CKA_VERIFY, (char)CK_TRUE));
- attr.push_back(P11KitAttribute(CKA_LABEL, d_pub_label));
+ attr.emplace_back(CKA_ALWAYS_AUTHENTICATE, '\0');
+ if (GetAttributeValue2(*slot, d_private_key, attr)==0) {
+ d_always_auth = attr[0].byte() != 0;
+ }
+ attr.clear();
+ attr.emplace_back(CKA_CLASS, (unsigned long)CKO_PUBLIC_KEY);
+ attr.emplace_back(CKA_LABEL, d_pub_label);
FindObjects2(*slot, attr, key, 1);
if (key.size() == 0) {
g_log<<Logger::Warning<<"Cannot load PKCS#11 public key "<<d_pub_label<<std::endl;
d_public_key = key[0];
attr.clear();
- attr.push_back(P11KitAttribute(CKA_KEY_TYPE, 0UL));
+ attr.emplace_back(CKA_KEY_TYPE, 0UL);
if (GetAttributeValue2(*slot, d_public_key, attr)==0) {
d_key_type = attr[0].ulong();
if (d_key_type == CKK_RSA) {
attr.clear();
- attr.push_back(P11KitAttribute(CKA_MODULUS, ""));
- attr.push_back(P11KitAttribute(CKA_PUBLIC_EXPONENT, ""));
- attr.push_back(P11KitAttribute(CKA_MODULUS_BITS, 0UL));
+ attr.emplace_back(CKA_MODULUS, "");
+ attr.emplace_back(CKA_PUBLIC_EXPONENT, "");
+ attr.emplace_back(CKA_MODULUS_BITS, 0UL);
if (!GetAttributeValue2(*slot, d_public_key, attr)) {
d_modulus = attr[0].str();
}
} else if (d_key_type == CKK_EC || d_key_type == CKK_ECDSA) {
attr.clear();
- attr.push_back(P11KitAttribute(CKA_ECDSA_PARAMS, ""));
- attr.push_back(P11KitAttribute(CKA_EC_POINT, ""));
+ attr.emplace_back(CKA_ECDSA_PARAMS, "");
+ attr.emplace_back(CKA_EC_POINT, "");
if (!GetAttributeValue2(*slot, d_public_key, attr)) {
d_ecdsa_params = attr[0].str();
d_bits = ecparam2bits(d_ecdsa_params);
CK_ULONG buflen = sizeof buffer; // should be enough for most signatures.
auto slot = d_slot->lock();
- // perform signature
if ((d_err = slot->f()->C_SignInit(slot->Session(), mechanism, d_private_key))) { logError("C_SignInit"); return d_err; }
+ // check if we need to relogin
+ if (d_always_auth) {
+ slot->Relogin();
+ }
+ // perform signature
d_err = slot->f()->C_Sign(slot->Session(), (unsigned char*)data.c_str(), data.size(), buffer, &buflen);
if (!d_err) {
auto slot = d_slot->lock();
if ((d_err = slot->f()->C_VerifyInit(slot->Session(), mechanism, d_public_key))) { logError("C_VerifyInit"); return d_err; }
+ // check if we need to relogin
+ if (d_always_auth) {
+ slot->Relogin();
+ }
+
d_err = slot->f()->C_Verify(slot->Session(), (unsigned char*)data.c_str(), data.size(), (unsigned char*)signature.c_str(), signature.size());
logError("C_Verify");
return d_err;
if (this->d_slot->lock()->LoggedIn()) LoadAttributes();
}
-Pkcs11Token::~Pkcs11Token() {
-}
+Pkcs11Token::~Pkcs11Token() = default;
bool PKCS11ModuleSlotLogin(const std::string& module, const string& tokenId, const std::string& pin)
{
}
PKCS11DNSCryptoKeyEngine::PKCS11DNSCryptoKeyEngine(unsigned int algorithm): DNSCryptoKeyEngine(algorithm) {}
-PKCS11DNSCryptoKeyEngine::~PKCS11DNSCryptoKeyEngine() {}
+PKCS11DNSCryptoKeyEngine::~PKCS11DNSCryptoKeyEngine() = default;
PKCS11DNSCryptoKeyEngine::PKCS11DNSCryptoKeyEngine(const PKCS11DNSCryptoKeyEngine& orig) : DNSCryptoKeyEngine(orig.d_algorithm) {}
void PKCS11DNSCryptoKeyEngine::create(unsigned int bits) {
// FINE! I'll do this myself, then, shall I?
switch(d_algorithm) {
case 5: {
- return pdns_sha1sum(msg);
+ return pdns::sha1sum(msg);
}
case 8: {
- return pdns_sha256sum(msg);
+ return pdns::sha256sum(msg);
}
case 10: {
- return pdns_sha512sum(msg);
+ return pdns::sha512sum(msg);
}
case 13: {
- return pdns_sha256sum(msg);
+ return pdns::sha256sum(msg);
}
case 14: {
- return pdns_sha384sum(msg);
+ return pdns::sha384sum(msg);
}
};
};
public:
PKCS11DNSCryptoKeyEngine(unsigned int algorithm);
- ~PKCS11DNSCryptoKeyEngine();
+ ~PKCS11DNSCryptoKeyEngine() override;
PKCS11DNSCryptoKeyEngine(const PKCS11DNSCryptoKeyEngine& orig);
public:
PollFDMultiplexer(unsigned int /* maxEventsHint */)
{}
- ~PollFDMultiplexer()
- {
- }
int run(struct timeval* tv, int timeout = 500) override;
void getAvailableFDs(std::vector<int>& fds, int timeout) override;
return;
}
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
+ const dnsheader_aligned dh(packet);
if (ntohs(dh->ancount) == 0) {
return;
public:
enum class MetaValueField : protozero::pbf_tag_type { stringVal = 1, intVal = 2 };
+ enum class HTTPVersion : protozero::pbf_tag_type { HTTP1 = 1, HTTP2 = 2, HTTP3 = 3 };
enum class MetaField : protozero::pbf_tag_type { key = 1, value = 2 };
enum class Event : protozero::pbf_tag_type { ts = 1, event = 2, start = 3, boolVal = 4, intVal = 5, stringVal = 6, bytesVal = 7, custom = 8 };
enum class MessageType : int32_t { DNSQueryType = 1, DNSResponseType = 2, DNSOutgoingQueryType = 3, DNSIncomingResponseType = 4 };
- enum class Field : protozero::pbf_tag_type { type = 1, messageId = 2, serverIdentity = 3, socketFamily = 4, socketProtocol = 5, from = 6, to = 7, inBytes = 8, timeSec = 9, timeUsec = 10, id = 11, question = 12, response = 13, originalRequestorSubnet = 14, requestorId = 15, initialRequestId = 16, deviceId = 17, newlyObservedDomain = 18, deviceName = 19, fromPort = 20, toPort = 21, meta = 22, trace = 23 };
+ enum class Field : protozero::pbf_tag_type { type = 1, messageId = 2, serverIdentity = 3, socketFamily = 4, socketProtocol = 5, from = 6, to = 7, inBytes = 8, timeSec = 9, timeUsec = 10, id = 11, question = 12, response = 13, originalRequestorSubnet = 14, requestorId = 15, initialRequestId = 16, deviceId = 17, newlyObservedDomain = 18, deviceName = 19, fromPort = 20, toPort = 21, meta = 22, trace = 23, httpVersion = 24 };
enum class QuestionField : protozero::pbf_tag_type { qName = 1, qType = 2, qClass = 3 };
enum class ResponseField : protozero::pbf_tag_type { rcode = 1, rrs = 2, appliedPolicy = 3, tags = 4, queryTimeSec = 5, queryTimeUsec = 6, appliedPolicyType = 7, appliedPolicyTrigger = 8, appliedPolicyHit = 9, appliedPolicyKind = 10, validationState = 11 };
enum class RRField : protozero::pbf_tag_type { name = 1, type = 2, class_ = 3, ttl = 4, rdata = 5, udr = 6 };
- enum class TransportProtocol : protozero::pbf_tag_type { UDP = 1, TCP = 2, DoT = 3, DoH = 4, DNSCryptUDP = 5, DNSCryptTCP = 6 };
+ enum class TransportProtocol : protozero::pbf_tag_type { UDP = 1, TCP = 2, DoT = 3, DoH = 4, DNSCryptUDP = 5, DNSCryptTCP = 6, DoQ = 7 };
Message(std::string& buffer): d_buffer(buffer), d_message{d_buffer}
{
add_enum(d_message, Field::type, static_cast<int32_t>(mtype));
}
+ void setHTTPVersion(HTTPVersion version)
+ {
+ add_enum(d_message, Field::httpVersion, static_cast<int32_t>(version));
+ }
+
void setMessageIdentity(const boost::uuids::uuid& uniqueId)
{
add_bytes(d_message, Field::messageId, reinterpret_cast<const char*>(uniqueId.begin()), uniqueId.size());
{
return type == rhs.type && content == rhs.content;
}
+
+ enum class Types : uint8_t { PP_TLV_ALPN = 0x01, PP_TLV_SSL = 0x20 };
};
static const size_t s_proxyProtocolMinimumHeaderSize = 16;
{"EUI48", 108},
{"EUI64", 109},
{"TKEY", 249},
- // {"TSIG", 250},
+ {"TSIG", 250},
{"IXFR", 251},
{"AXFR", 252},
{"MAILB", 253},
bool QType::isMetadataType() const
{
- if (code == QType::AXFR ||
- code == QType::MAILA ||
- code == QType::MAILB ||
- code == QType::TSIG ||
- code == QType::IXFR)
+ // rfc6895 section 3.1, note ANY is 255 and falls outside the range
+ if (code == QType::OPT || (code >= rfc6895MetaLowerBound && code <= rfc6895MetaUpperBound)) {
return true;
-
+ }
return false;
}
return code;
}
+ /**
+ * \brief Return whether we know the name of this type.
+ *
+ * This does not presume that we have an implemented a content representation for this type,
+ * for that please see DNSRecordContent::isRegisteredType().
+ */
bool isSupportedType() const;
+ /**
+ * \brief Whether the type is either a QTYPE or Meta-Type as defined by rfc6895 section 3.1.
+ *
+ * Note that ANY is 255 and falls outside the range.
+ */
bool isMetadataType() const;
static uint16_t chartocode(const char* p);
skipSpaces();
DNSName sval;
- const char* strptr=d_string.c_str();
string::size_type begin_pos = d_pos;
- while(d_pos < d_end) {
- if(strptr[d_pos]!='\r' && dns_isspace(strptr[d_pos]))
+ while (d_pos < d_end) {
+ if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
break;
+ }
d_pos++;
}
- sval = DNSName(std::string(strptr+begin_pos, strptr+d_pos));
- if(sval.empty())
- sval=d_zone;
- else if(!d_zone.empty())
- sval+=d_zone;
- val = sval;
+ {
+ std::string_view view(d_string);
+ sval = DNSName(view.substr(begin_pos, d_pos - begin_pos));
+ }
+
+ if (sval.empty()) {
+ sval = d_zone;
+ }
+ else if (!d_zone.empty()) {
+ sval += d_zone;
+ }
+ val = std::move(sval);
}
static bool isbase64(char c, bool acceptspace)
/rec_control
/pdns-recursor-*
/recursor.conf-dist
+/recursor.yml-dist
/ext/Makefile
/ext/Makefile.in
/dnsmessage.pb.cc
/*.pb.h
/.cache
/.clang-tidy
+/recursor.yml
-JSON11_LIBS = $(top_srcdir)/ext/json11/libjson11.la
-PROBDS_LIBS = $(top_srcdir)/ext/probds/libprobds.la
+JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
+PROBDS_LIBS = $(top_builddir)/ext/probds/libprobds.la
+ARC4RANDOM_LIBS = $(top_builddir)/ext/arc4random/libarc4random.la
+RUST_LIBS = $(top_builddir)/settings/rust/libsettings.a $(LIBDL)
AM_CPPFLAGS = $(LUA_CFLAGS) $(YAHTTP_CFLAGS) $(BOOST_CPPFLAGS) $(LIBSODIUM_CFLAGS) $(NET_SNMP_CFLAGS) $(LIBCAP_CFLAGS) $(SANITIZER_FLAGS) -O3 -Wall -pthread -DSYSCONFDIR=\"${sysconfdir}\" $(SYSTEMD_CFLAGS)
AM_CPPFLAGS += \
-I$(top_srcdir)/ext/json11 \
-I$(top_srcdir)/ext/protozero/include \
+ -I$(top_srcdir)/settings \
+ -I$(top_builddir)/settings \
$(YAHTTP_CFLAGS) \
$(LIBCRYPTO_INCLUDES) \
-DBOOST_CONTAINER_USE_STD_EXCEPTIONS
-DPKGLIBDIR=\"$(pkglibdir)\" \
-DLOCALSTATEDIR=\"$(socketdir)\"
-if NOD_ENABLED
AM_CXXFLAGS += \
- -DNODCACHEDIR=\"$(nodcachedir)\"
-endif
+ -DNODCACHEDIRNOD=\"$(nodcachedir)/nod\" -DNODCACHEDIRUDR=\"$(nodcachedir)/udr\"
if FSTRM
AM_CPPFLAGS += \
dnslabeltext.cc
CLEANFILES = htmlfiles.h \
- recursor.conf-dist
+ recursor.conf-dist recursor.yml-dist
-htmlfiles.h: incfiles html/* html/js/*
- ./incfiles > $@
+htmlfiles.h: incfiles ${srcdir}/html/* ${srcdir}/html/js/*
+ $(AM_V_GEN)$(srcdir)/incfiles > $@.tmp
+ @mv $@.tmp $@
-SUBDIRS=ext
+# We explicitly build settings in two steps, as settings modifies files in the settings/rust subdir
+SUBDIRS=ext settings settings/rust
if LUA
AM_CPPFLAGS +=$(LUA_CFLAGS)
lua_hpp.mk \
malloctrace.cc malloctrace.hh \
mkpubsuffixcc \
- mtasker.cc \
mtasker_fcontext.cc mtasker_ucontext.cc \
NOTICE \
opensslsigners.hh opensslsigners.cc \
if UNIT_TESTS
noinst_PROGRAMS = testrunner
-TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message SRCDIR='$(srcdir)'
+TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message BOOST_TEST_RANDOM=1 SRCDIR='$(srcdir)'
TESTS += testrunner
else
check-local:
burtle.hh \
cachecleaner.hh \
capabilities.cc capabilities.hh \
+ channel.cc channel.hh \
circular_buffer.hh \
comment.hh \
+ coverage.cc coverage.hh \
credentials.cc credentials.hh \
dns.hh dns.cc \
- dns_random.hh dns_random.cc \
+ dns_random.hh \
dnsbackend.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
rpzloader.cc rpzloader.hh \
secpoll-recursor.cc secpoll-recursor.hh \
secpoll.cc secpoll.hh \
+ settings/cxxsupport.cc \
sha.hh \
sholder.hh \
shuffle.cc shuffle.hh \
uuid-utils.hh uuid-utils.cc \
validate.cc validate.hh validate-recursor.cc validate-recursor.hh \
version.cc version.hh \
+ views.hh \
webserver.cc webserver.hh \
ws-api.cc ws-api.hh \
ws-recursor.cc ws-recursor.hh \
zonemd.cc zonemd.hh \
zoneparser-tng.cc zoneparser-tng.hh
+nodist_pdns_recursor_SOURCES = \
+ settings/cxxsettings-generated.cc
+
if !HAVE_LUA_HPP
BUILT_SOURCES += lua.hpp
-nodist_pdns_recursor_SOURCES = lua.hpp
+nodist_pdns_recursor_SOURCES += lua.hpp
endif
CLEANFILES += lua.hpp
$(RT_LIBS) \
$(BOOST_SYSTEM_LIBS) \
$(PROBDS_LIBS) \
- $(LIBCAP_LIBS)
+ $(LIBCAP_LIBS) \
+ $(ARC4RANDOM_LIBS) \
+ $(RUST_LIBS)
pdns_recursor_LDFLAGS = $(AM_LDFLAGS) \
$(LIBCRYPTO_LDFLAGS) $(BOOST_CONTEXT_LDFLAGS) \
$(BOOST_FILESYSTEM_LDFLAGS)
endif
-rec_control_LDADD = $(LIBCRYPTO_LIBS)
+rec_control_LDADD = $(LIBCRYPTO_LIBS) $(ARC4RANDOM_LIBS) $(RUST_LIBS)
rec_control_LDFLAGS = $(AM_LDFLAGS) \
$(LIBCRYPTO_LDFLAGS)
circular_buffer.hh \
credentials.cc credentials.hh \
dns.cc dns.hh \
- dns_random.cc dns_random.hh \
+ dns_random.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.hh dnsparser.cc \
root-dnssec.hh \
rpzloader.cc rpzloader.hh \
secpoll.cc \
+ settings/cxxsupport.cc \
sholder.hh \
sillyrecords.cc \
sstuff.hh \
test-reczones-helpers.cc \
test-rpzloader_cc.cc \
test-secpoll_cc.cc \
+ test-settings.cc \
test-signers.cc \
test-syncres_cc.cc \
test-syncres_cc.hh \
zonemd.cc zonemd.hh \
zoneparser-tng.cc zoneparser-tng.hh
+nodist_testrunner_SOURCES = \
+ settings/cxxsettings-generated.cc
+
testrunner_LDFLAGS = \
$(AM_LDFLAGS) \
$(BOOST_CONTEXT_LDFLAGS) \
$(RT_LIBS) \
$(BOOST_SYSTEM_LIBS) \
$(PROBDS_LIBS) \
- $(LIBCAP_LIBS)
+ $(LIBCAP_LIBS) \
+ $(ARC4RANDOM_LIBS) \
+ $(RUST_LIBS)
if NOD_ENABLED
testrunner_SOURCES += nod.hh nod.cc \
qtype.cc \
rec_channel.cc rec_channel.hh \
rec_control.cc \
+ settings/cxxsupport.cc \
unix_utility.cc
+nodist_rec_control_SOURCES = \
+ settings/cxxsettings-generated.cc
+
dnslabeltext.cc: dnslabeltext.rl
$(AM_V_GEN)$(RAGEL) $< -o dnslabeltext.cc
$(curl_verbose)if ! curl -s -S https://publicsuffix.org/list/public_suffix_list.dat > $@; then rm -f $@; exit 1; fi
pubsuffix.cc: $(srcdir)/effective_tld_names.dat
- $(AM_V_GEN)./mkpubsuffixcc
+ $(srcdir)/mkpubsuffixcc $< $@
## Config file
-sysconf_DATA = recursor.conf-dist
+sysconf_DATA = recursor.conf-dist recursor.yml-dist
recursor.conf-dist: pdns_recursor
$(AM_V_GEN)./pdns_recursor --config=default > $@
+recursor.yml-dist: pdns_recursor
+ dir=$$(mktemp -d) && touch "$$dir/recursor.yml" && ./pdns_recursor --config-dir="$$dir" --config=default 2> /dev/null > $@ && rm "$$dir/recursor.yml" && rmdir "$$dir"
+
## Manpages
MANPAGES=pdns_recursor.1 \
rec_control.1
if HAVE_VENV
if !HAVE_MANPAGES
$(MANPAGES): %: docs/manpages/%.rst .venv
- .venv/bin/python -msphinx -b man docs . $<
+ $(builddir)/.venv/bin/python -msphinx -b man "$(srcdir)/docs" "$(builddir)" $<
endif # if !HAVE_MANPAGES
.venv: docs/requirements.txt
$(PYTHON) -m venv .venv
.venv/bin/pip install -U pip setuptools setuptools-git wheel
- .venv/bin/pip install -r docs/requirements.txt
+ .venv/bin/pip install -r ${top_srcdir}/docs/requirements.txt
html-docs: docs/** .venv
.venv/bin/python -msphinx -b html docs html-docs
License
=======
-PowerDNS is copyright © 2001-2023 by PowerDNS.COM BV and lots of
+PowerDNS is copyright © by PowerDNS.COM BV and lots of
contributors, using the GNU GPLv2 license (see NOTICE for the
exact license and exception used).
REVISION "202302240000Z"
DESCRIPTION "Added metrics for sharded packet cache contention"
+ REVISION "202306080000Z"
+ DESCRIPTION "Added metrics for NOD and UDR events"
+
::= { powerdns 2 }
powerdns OBJECT IDENTIFIER ::= { enterprises 43315 }
"Number of packet cache lock acquisitions"
::= { stats 146 }
+nodEvents OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of NOD events"
+ ::= { stats 147 }
+
+udrEvents OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of UDR events"
+ ::= { stats 148 }
---
--- Traps / Notifications
authrcode14Count,
authrcode15Count,
packetCacheContended,
- packetCacheAcquired
+ packetCacheAcquired,
+ nodEvents,
+ udrEvents
}
STATUS current
DESCRIPTION "Objects conformance group for PowerDNS Recursor"
#include "validate.hh"
std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache{nullptr};
+uint64_t AggressiveNSECCache::s_nsec3DenialProofMaxCost{0};
uint8_t AggressiveNSECCache::s_maxNSEC3CommonPrefix = AggressiveNSECCache::s_default_maxNSEC3CommonPrefix;
/* this is defined in syncres.hh and we are not importing that here */
{
uint64_t maxNumberOfEntries = d_maxEntries;
std::vector<DNSName> emptyEntries;
-
uint64_t erased = 0;
- uint64_t lookedAt = 0;
- uint64_t toLook = std::max(d_entriesCount / 5U, static_cast<uint64_t>(1U));
- if (d_entriesCount > maxNumberOfEntries) {
- uint64_t toErase = d_entriesCount - maxNumberOfEntries;
- toLook = toErase * 5;
- // we are full, scan at max 5 * toErase entries and stop once we have nuked enough
+ auto zones = d_zones.write_lock();
+ // To start, just look through 10% of each zone and nuke everything that is expired
+ zones->visit([now, &erased, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
+ if (!node.d_value) {
+ return;
+ }
- auto zones = d_zones.write_lock();
- zones->visit([now, &erased, toErase, toLook, &lookedAt, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
- if (!node.d_value || erased > toErase || lookedAt > toLook) {
- return;
+ auto zoneEntry = node.d_value->lock();
+ auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries);
+ const auto toLookAtForThisZone = (zoneEntry->d_entries.size() + 9) / 10;
+ uint64_t lookedAt = 0;
+ for (auto it = sidx.begin(); it != sidx.end() && lookedAt < toLookAtForThisZone; ++lookedAt) {
+ if (it->d_ttd <= now) {
+ it = sidx.erase(it);
+ ++erased;
+ }
+ else {
+ ++it;
}
+ }
- auto zoneEntry = node.d_value->lock();
- auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries);
- for (auto it = sidx.begin(); it != sidx.end(); ++lookedAt) {
- if (erased >= toErase || lookedAt >= toLook) {
- break;
- }
+ if (zoneEntry->d_entries.empty()) {
+ emptyEntries.push_back(zoneEntry->d_zone);
+ }
+ });
- if (it->d_ttd < now) {
- it = sidx.erase(it);
- ++erased;
- }
- else {
- ++it;
- }
- }
+ d_entriesCount -= erased;
- if (zoneEntry->d_entries.size() == 0) {
- emptyEntries.push_back(zoneEntry->d_zone);
- }
- });
- }
- else {
- // we are not full, just look through 10% of the cache and nuke everything that is expired
- auto zones = d_zones.write_lock();
- zones->visit([now, &erased, toLook, &lookedAt, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
- if (!node.d_value) {
+ // If we are still above try harder by nuking entries from each zone in LRU order
+ auto entriesCount = d_entriesCount.load();
+ if (entriesCount > maxNumberOfEntries) {
+ erased = 0;
+ uint64_t toErase = entriesCount - maxNumberOfEntries;
+ zones->visit([&erased, &toErase, &entriesCount, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
+ if (!node.d_value || entriesCount == 0) {
return;
}
-
auto zoneEntry = node.d_value->lock();
+ const auto zoneSize = zoneEntry->d_entries.size();
auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries);
- for (auto it = sidx.begin(); it != sidx.end(); ++lookedAt) {
- if (lookedAt >= toLook) {
+ const auto toTrimForThisZone = static_cast<uint64_t>(std::round(static_cast<double>(toErase) * static_cast<double>(zoneSize) / static_cast<double>(entriesCount)));
+ if (entriesCount < zoneSize) {
+ throw std::runtime_error("Inconsistent agggressive cache " + std::to_string(entriesCount) + " " + std::to_string(zoneSize));
+ }
+ // This is comparable to what cachecleaner.hh::pruneMutexCollectionsVector() is doing, look there for an explanation
+ entriesCount -= zoneSize;
+ uint64_t trimmedFromThisZone = 0;
+ for (auto it = sidx.begin(); it != sidx.end() && trimmedFromThisZone < toTrimForThisZone;) {
+ it = sidx.erase(it);
+ ++erased;
+ ++trimmedFromThisZone;
+ if (--toErase == 0) {
break;
}
- if (it->d_ttd < now || lookedAt > toLook) {
- it = sidx.erase(it);
- ++erased;
- }
- else {
- ++it;
- }
}
-
- if (zoneEntry->d_entries.size() == 0) {
+ if (zoneEntry->d_entries.empty()) {
emptyEntries.push_back(zoneEntry->d_zone);
}
});
- }
- d_entriesCount -= erased;
+ d_entriesCount -= erased;
+ }
if (!emptyEntries.empty()) {
- auto zones = d_zones.write_lock();
for (const auto& entry : emptyEntries) {
zones->remove(entry);
}
bool AggressiveNSECCache::isSmallCoveringNSEC3(const DNSName& owner, const std::string& nextHash)
{
std::string ownerHash(fromBase32Hex(owner.getRawLabel(0)));
+ // Special case: empty zone, so the single NSEC3 covers everything. Prefix is long but we still want it cached.
+ if (ownerHash == nextHash) {
+ return false;
+ }
return commonPrefixIsLong(ownerHash, nextHash, AggressiveNSECCache::s_maxNSEC3CommonPrefix);
}
/* the TTL is already a TTD by now */
if (!nsec3 && isWildcardExpanded(owner.countLabels(), *signatures.at(0))) {
DNSName realOwner = getNSECOwnerName(owner, signatures);
- auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, std::move(realOwner), std::move(next), record.d_ttl});
+ auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, realOwner, next, record.d_ttl});
if (pair.second) {
++d_entriesCount;
}
+ else {
+ zoneEntry->d_entries.replace(pair.first, {record.getContent(), signatures, std::move(realOwner), next, record.d_ttl});
+ }
}
else {
- auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, owner, std::move(next), record.d_ttl});
+ auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, owner, next, record.d_ttl});
if (pair.second) {
++d_entriesCount;
}
+ else {
+ zoneEntry->d_entries.replace(pair.first, {record.getContent(), signatures, owner, std::move(next), record.d_ttl});
+ }
}
}
}
return false;
}
+ auto firstIndexIterator = zoneEntry->d_entries.project<ZoneEntry::OrderedTag>(it);
if (it->d_ttd <= now) {
- moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, it);
+ moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
return false;
}
entry = *it;
- moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, it);
+ moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
return true;
}
return false;
}
- addToRRSet(now, wcSet, wcSignatures, name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
+ addToRRSet(now, wcSet, std::move(wcSignatures), name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
/* no need for closest encloser proof, the wildcard is there */
+ // coverity[store_truncates_time_t]
addRecordToRRSet(nextCloser.d_owner, QType::NSEC3, nextCloser.d_ttd - now, nextCloser.d_record, nextCloser.d_signatures, doDNSSEC, ret);
/* and of course we won't deny the wildcard either */
return false;
}
- addToRRSet(now, wcSet, wcSignatures, name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
+ addToRRSet(now, wcSet, std::move(wcSignatures), name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
+ // coverity[store_truncates_time_t]
addRecordToRRSet(nsec.d_owner, QType::NSEC, nsec.d_ttd - now, nsec.d_record, nsec.d_signatures, doDNSSEC, ret);
VLOG(log, name << ": Synthesized valid answer from NSECs and wildcard!" << endl);
return true;
}
-bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog& log)
+bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog& log, pdns::validation::ValidationContext& validationContext)
{
DNSName zone;
std::string salt;
iterations = entry->d_iterations;
}
- auto nameHash = DNSName(toBase32Hex(hashQNameWithSalt(salt, iterations, name))) + zone;
+ const auto zoneLabelsCount = zone.countLabels();
+ if (s_nsec3DenialProofMaxCost != 0) {
+ const auto worstCaseIterations = getNSEC3DenialProofWorstCaseIterationsCount(name.countLabels() - zoneLabelsCount, iterations, salt.length());
+ if (worstCaseIterations > s_nsec3DenialProofMaxCost) {
+ // skip NSEC3 aggressive cache for expensive NSEC3 parameters: "if you want us to take the pain of PRSD away from you, you need to make it cheap for us to do so"
+ VLOG(log, name << ": Skipping aggressive use of the NSEC3 cache since the zone parameters are too expensive" << endl);
+ return false;
+ }
+ }
+
+ auto nameHash = DNSName(toBase32Hex(getHashFromNSEC3(name, iterations, salt, validationContext))) + zone;
ZoneEntry::CacheEntry exactNSEC3;
if (getNSEC3(now, zoneEntry, nameHash, exactNSEC3)) {
DNSName closestEncloser(name);
bool found = false;
ZoneEntry::CacheEntry closestNSEC3;
- while (!found && closestEncloser.chopOff()) {
- auto closestHash = DNSName(toBase32Hex(hashQNameWithSalt(salt, iterations, closestEncloser))) + zone;
+ auto remainingLabels = closestEncloser.countLabels() - 1;
+ while (!found && closestEncloser.chopOff() && remainingLabels >= zoneLabelsCount) {
+ auto closestHash = DNSName(toBase32Hex(getHashFromNSEC3(closestEncloser, iterations, salt, validationContext))) + zone;
+ remainingLabels--;
if (getNSEC3(now, zoneEntry, closestHash, closestNSEC3)) {
VLOG(log, name << ": Found closest encloser at " << closestEncloser << " (" << closestHash << ")" << endl);
DNSName nsecFound;
DNSName nextCloser(closestEncloser);
nextCloser.prependRawLabel(name.getRawLabel(labelIdx - 1));
- auto nextCloserHash = toBase32Hex(hashQNameWithSalt(salt, iterations, nextCloser));
+ auto nextCloserHash = toBase32Hex(getHashFromNSEC3(nextCloser, iterations, salt, validationContext));
VLOG(log, name << ": Looking for a NSEC3 covering the next closer " << nextCloser << " (" << nextCloserHash << ")" << endl);
ZoneEntry::CacheEntry nextCloserEntry;
/* An ancestor NSEC3 would be fine here, since it does prove that there is no delegation at the next closer
name (we don't insert opt-out NSEC3s into the cache). */
DNSName wildcard(g_wildcarddnsname + closestEncloser);
- auto wcHash = toBase32Hex(hashQNameWithSalt(salt, iterations, wildcard));
+ auto wcHash = toBase32Hex(getHashFromNSEC3(wildcard, iterations, salt, validationContext));
VLOG(log, name << ": Looking for a NSEC3 covering the wildcard " << wildcard << " (" << wcHash << ")" << endl);
ZoneEntry::CacheEntry wcEntry;
addRecordToRRSet(nextCloserEntry.d_owner, QType::NSEC3, nextCloserEntry.d_ttd - now, nextCloserEntry.d_record, nextCloserEntry.d_signatures, doDNSSEC, ret);
}
if (wcEntry.d_owner != closestNSEC3.d_owner && wcEntry.d_owner != nextCloserEntry.d_owner) {
+ // coverity[store_truncates_time_t]
addRecordToRRSet(wcEntry.d_owner, QType::NSEC3, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
}
return true;
}
-bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, const OptLog& log)
+bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, pdns::validation::ValidationContext& validationContext, const OptLog& log)
{
std::shared_ptr<LockGuarded<ZoneEntry>> zoneEntry;
if (type == QType::DS) {
}
if (nsec3) {
- return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log);
+ return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log, validationContext);
}
ZoneEntry::CacheEntry entry;
ret.reserve(ret.size() + soaSet.size() + soaSignatures.size() + /* NSEC */ 1 + entry.d_signatures.size() + (needWildcard ? (/* NSEC */ 1 + wcEntry.d_signatures.size()) : 0));
- addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret);
+ addToRRSet(now, soaSet, std::move(soaSignatures), zone, doDNSSEC, ret);
+ // coverity[store_truncates_time_t]
addRecordToRRSet(entry.d_owner, QType::NSEC, entry.d_ttd - now, entry.d_record, entry.d_signatures, doDNSSEC, ret);
if (needWildcard) {
+ // coverity[store_truncates_time_t]
addRecordToRRSet(wcEntry.d_owner, QType::NSEC, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
}
return true;
}
-size_t AggressiveNSECCache::dumpToFile(std::unique_ptr<FILE, int (*)(FILE*)>& fp, const struct timeval& now)
+size_t AggressiveNSECCache::dumpToFile(pdns::UniqueFilePtr& filePtr, const struct timeval& now)
{
size_t ret = 0;
auto zones = d_zones.read_lock();
- zones->visit([&ret, now, &fp](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
+ zones->visit([&ret, now, &filePtr](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
if (!node.d_value) {
return;
}
auto zone = node.d_value->lock();
- fprintf(fp.get(), "; Zone %s\n", zone->d_zone.toString().c_str());
+ fprintf(filePtr.get(), "; Zone %s\n", zone->d_zone.toString().c_str());
for (const auto& entry : zone->d_entries) {
int64_t ttl = entry.d_ttd - now.tv_sec;
try {
- fprintf(fp.get(), "%s %" PRId64 " IN %s %s\n", entry.d_owner.toString().c_str(), ttl, zone->d_nsec3 ? "NSEC3" : "NSEC", entry.d_record->getZoneRepresentation().c_str());
+ fprintf(filePtr.get(), "%s %" PRId64 " IN %s %s\n", entry.d_owner.toString().c_str(), ttl, zone->d_nsec3 ? "NSEC3" : "NSEC", entry.d_record->getZoneRepresentation().c_str());
for (const auto& signature : entry.d_signatures) {
- fprintf(fp.get(), "- RRSIG %s\n", signature->getZoneRepresentation().c_str());
+ fprintf(filePtr.get(), "- RRSIG %s\n", signature->getZoneRepresentation().c_str());
}
++ret;
}
catch (const std::exception& e) {
- fprintf(fp.get(), "; Error dumping record from zone %s: %s\n", zone->d_zone.toString().c_str(), e.what());
+ fprintf(filePtr.get(), "; Error dumping record from zone %s: %s\n", zone->d_zone.toString().c_str(), e.what());
}
catch (...) {
- fprintf(fp.get(), "; Error dumping record from zone %s\n", zone->d_zone.toString().c_str());
+ fprintf(filePtr.get(), "; Error dumping record from zone %s\n", zone->d_zone.toString().c_str());
}
}
});
*/
#pragma once
+#include <atomic>
#include <boost/utility.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include "lock.hh"
#include "stat_t.hh"
#include "logger.hh"
+#include "validate.hh"
class AggressiveNSECCache
{
public:
- static const uint8_t s_default_maxNSEC3CommonPrefix = 10;
+ static constexpr uint8_t s_default_maxNSEC3CommonPrefix = 10;
+ static uint64_t s_nsec3DenialProofMaxCost;
static uint8_t s_maxNSEC3CommonPrefix;
AggressiveNSECCache(uint64_t entries) :
{
}
+ void setMaxEntries(uint64_t number)
+ {
+ d_maxEntries = number;
+ }
+
static bool nsec3Disabled()
{
return s_maxNSEC3CommonPrefix == 0;
}
void insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, bool nsec3);
- bool getDenial(time_t, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, const OptLog& log = std::nullopt);
+ bool getDenial(time_t, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, pdns::validation::ValidationContext& validationContext, const OptLog& log = std::nullopt);
void removeZoneInfo(const DNSName& zone, bool subzones);
static bool isSmallCoveringNSEC3(const DNSName& owner, const std::string& nextHash);
void prune(time_t now);
- size_t dumpToFile(std::unique_ptr<FILE, int (*)(FILE*)>& fp, const struct timeval& now);
+ size_t dumpToFile(pdns::UniqueFilePtr& filePtr, const struct timeval& now);
private:
struct ZoneEntry
std::shared_ptr<LockGuarded<ZoneEntry>> getBestZone(const DNSName& zone);
bool getNSECBefore(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, const DNSName& name, ZoneEntry::CacheEntry& entry);
bool getNSEC3(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, const DNSName& name, ZoneEntry::CacheEntry& entry);
- bool getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog&);
+ bool getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog&, pdns::validation::ValidationContext& validationContext);
bool synthesizeFromNSEC3Wildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nextCloser, const DNSName& wildcardName, const OptLog&);
bool synthesizeFromNSECWildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nsec, const DNSName& wildcardName, const OptLog&);
pdns::stat_t d_nsecWildcardHits{0};
pdns::stat_t d_nsec3WildcardHits{0};
pdns::stat_t d_entriesCount{0};
- uint64_t d_maxEntries{0};
+ std::atomic<uint64_t> d_maxEntries{0};
};
extern std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache;
--- /dev/null
+../channel.cc
\ No newline at end of file
--- /dev/null
+../channel.hh
\ No newline at end of file
m4_pattern_forbid([^_?PKG_[A-Z_]+$], [*** pkg.m4 missing, please install pkg-config])
AX_CXX_COMPILE_STDCXX_17([noext], [mandatory])
-LT_INIT()
+PDNS_CHECK_CARGO([1.64])
+
+# Rust runtime used dlopen from its static lib
+LT_INIT([dlopen])
+AC_SUBST([LIBDL], [$lt_cv_dlopen_libs])
PDNS_CHECK_OS
PDNS_CHECK_NETWORK_LIBS
PDNS_ENABLE_UNIT_TESTS
PDNS_ENABLE_REPRODUCIBLE
+PDNS_ENABLE_COVERAGE
PDNS_WITH_LUA([mandatory])
AS_IF([test "x$LUAPC" = "xluajit"], [
dnl the *_r functions are in posix so we can use them unconditionally, but the ext/yahttp code is
dnl using the defines.
-AC_CHECK_FUNCS_ONCE([localtime_r gmtime_r strcasestr getrandom arc4random])
+AC_CHECK_FUNCS_ONCE([localtime_r gmtime_r strcasestr])
+AC_CHECK_FUNCS_ONCE([getrandom getentropy arc4random arc4random_uniform arc4random_buf])
+PDNS_CHECK_SECURE_MEMSET
+
+AC_CHECK_HEADERS([sys/random.h])
PDNS_CHECK_PTHREAD_NP
AC_CONFIG_FILES([Makefile
ext/Makefile
+ ext/arc4random/Makefile
ext/json11/Makefile
ext/probds/Makefile
ext/yahttp/Makefile
- ext/yahttp/yahttp/Makefile])
+ ext/yahttp/yahttp/Makefile
+ settings/Makefile
+ settings/rust/Makefile])
AC_OUTPUT
--- /dev/null
+../coverage.cc
\ No newline at end of file
--- /dev/null
+../coverage.hh
\ No newline at end of file
+++ /dev/null
-../dns_random.cc
\ No newline at end of file
+++ /dev/null
-../dns_random_urandom.cc
\ No newline at end of file
_build
+/settings.rst
+/yamlsettings.rst
End of life statements
======================
-We aim to have a release every six months.
-The latest release receives correctness, stability and security updates.
-The two releases before that get critical updates only.
+We aim to have a major release every six months.
+The latest major release train receives correctness, stability and security updates by the way of minor releases.
+We support older releases with critical updates for one year after the following major release.
Older releases are marked end of life and receive no updates at all.
Pre-releases do not receive immediate security updates.
-The currently supported release train of the PowerDNS Recursor is 4.8.
+The currently supported release train of the PowerDNS Recursor is 5.0.
-PowerDNS Recursor 4.7 will only receive critical updates and will be
-end of life after PowerDNS Recursor 4.10 or 5.0 is released.
+PowerDNS Recursor 4.9 will only receive critical updates and will be End of Life one year after PowerDNS Recursor 5.0 was released.
-PowerDNS Recursor 4.6 will only receive critical updates and will be
-end of life after PowerDNS Recursor 4.9 is released.
+PowerDNS Recursor 4.8 will only receive critical updates and will be End of Life one year after PowerDNS Recursor 4.9 was released.
-PowerDNS Recursor 4.0 through 4.5, 3.x, and 2.x are End of Life.
+PowerDNS Recursor 4.0 through 4.7, 3.x, and 2.x are End of Life.
Note: Users with a commercial agreement with PowerDNS.COM BV or Open-Xchange
can receive extended support for releases which are End Of Life. If you are
such a user, these EOL statements do not apply to you.
-Please refer to the commercial support `commitment
+Please refer to the support `commitment
<https://oxpedia.org/wiki/index.php?title=PowerDNS:Version_Support_Commitment>`_
for details.
+Note that for the Open Source support channels we only support the latest minor release of a release train.
+That means that we ask you to reproduce potential issues on the latest minor release first.
.. list-table:: PowerDNS Recursor Release Life Cycle
:header-rows: 1
- Release date
- Critical-Only updates
- End of Life
+ * - 5.0
+ - January 10 2024
+ - ~ July 2024
+ - ~ July 2025
+ * - 4.9
+ - June 30 2023
+ - January 10 2024
+ - January 10 2025
* - 4.8
- December 12 2022
- - ~ June 2023
- - ~ June 2024
+ - June 30 2023
+ - June 30 2024
* - 4.7
- May 30 2022
- December 12 2022
- - ~ December 2023
+ - EOL January 10 2024
* - 4.6
- December 17 2021
- May 30 2022
- - ~ June 2023
+ - EOL June 30 2023
* - 4.5
- May 11 2021
- December 17 2021
On startup, the :program:`Recursor` uses root hints to resolve the names and addresses of the root name servers and puts the record sets found into the record cache.
This is needed to be able to resolve names, as the recursive algorithm starts at the root (using cached data) and then tries to resolve delegations until it finds the name servers that are authoritative for the domain in question.
-If the :ref:`setting-hint-file` is not set, it wil use a compiled-in table as root hints.
-Starting with version 4.6.2, if :ref:`setting-hint-file` is set to ``no``, the :program:`Recursor` will not fill the cache with root data.
-This can be used in special cases, e.g. when all queries are forwarded.
+If the :ref:`setting-hint-file` is not set, :program:`Recursor` wil use a compiled-in table as root hints.
-Note that the root hints and resolved root data can differ if the root hints are outdated.
-As long as at least one root server mentioned in the root hints can be contacted, this mechanism will produce the desired record sets corresponding to the actual root server data.
-
-Periodically, based on the :ref:`setting-max-cache-ttl`, the :program:`Recursor` will refetch the root data using data in its cache.
-If that does not succeed, it wil fall back to using the root hints to fill the cache with root data.
+Periodically, based on the :ref:`setting-max-cache-ttl`, the :program:`Recursor` will refetch the root data using data in its cache by doing a `. NS` query.
+If that does not succeed, it will fall back to using the root hints to fill the cache with root data.
Prior to version 4.7.0, the period for re-fetching root data was :ref:`setting-max-cache-ttl` divided by 12, with a minimum of 10 seconds.
Starting with version 4.7.0, the period is adaptive, starting at 80% of :ref:`setting-max-cache-ttl`, reducing the interval on failure.
-There is another detail: after refreshing the root records, the :program:`Recursor` will resolve the ``NS`` records for the top level domain of the root servers.
+The root hints and resolved root data can differ if the root hints are outdated.
+As long as at least one root server mentioned in the root hints can be contacted, the periodic refresh will produce the desired record sets corresponding to the current up-to-date root server data.
+
+Starting with version 4.6.2, if :ref:`setting-hint-file` is set to ``no``, the :program:`Recursor` will not prime the cache with root data obtained from hints, but will still do the periodic refresh.
+A (recursive) forward configuration is needed to make the periodic refresh work.
+
+Starting with version 4.9, setting :ref:`setting-hint-file` to ``no-refresh`` disables both the initial reading of the hints and the periodic refresh of cached root data.
+This prevents :program:`Recursor` from resolving names by itself, so it is only useful in cases where all queries are forwarded.
+
+With versions older than 4.8, there is another detail: after refreshing the root records, the :program:`Recursor` will resolve the ``NS`` records for the top level domain of the root servers.
For example, in the default setup the root name servers are called ``[a-m].root-servers.net``, so the :program:`Recursor` will resolve the name servers of the ``.net`` domain.
-This is needed to correctly determine zone cuts to be able to decide if the ``.root-servers.net`` domain is DNSSEC protected.
+This is needed to correctly determine zone cuts to be able to decide if the ``.root-servers.net`` domain is DNSSEC protected. Newer versions solve this by querying the needed information top-down.
--- /dev/null
+# Start of converted recursor.yml based on recursor.conf
+dnssec:
+ validation: validate
+incoming:
+ listen:
+ - 128.66.23.4:5353
+ - '[::1]:53'
+recursor:
+ forward_zones:
+ - zone: example
+ recurse: false
+ forwarders:
+ - 127.0.0.1:1024
+ forward_zones_file: fwzones.txt
+ include_dir: recursor.d
+# Validation result: OK
+# End of converted recursor.conf
+#
+# Found 1 .conf file in recursor.d
+# Converted include-dir recursor.d/01.conf to YAML format:
+incoming:
+ listen: !override
+ - 0.0.0.0
+recursor:
+ forward_zones:
+ - zone: example.com
+ recurse: false
+ forwarders:
+ - 128.66.9.99
+ - zone: example.net
+ recurse: false
+ forwarders:
+ - 128.66.9.100
+ - 128.66.9.101
+# Validation result: OK
+# End of converted recursor.d/01.conf
+#
+# Converted fwzones.txt to YAML format for recursor.forward_zones_file:
+- zone: example.org
+ forwarders:
+ - 127.0.0.1:1024
+ recurse: true
+ notify_allowed: true
+# Validation result: OK
+# End of converted fwzones.txt
+#
+
--- /dev/null
+^+example.org=127.0.0.1:1024
\ No newline at end of file
--- /dev/null
+../../../rec_control show-yaml recursor.conf > conversion
--- /dev/null
+dnssec=validate
+include-dir=recursor.d
+forward-zones = example=127.0.0.1:1024
+forward-zones-file=fwzones.txt
+local-address=128.66.23.4:5353, [::1]:53
--- /dev/null
+forward-zones += example.com=128.66.9.99
+forward-zones += example.net=128.66.9.100;128.66.9.101
+local-address = 0.0.0.0
--- /dev/null
+Structured Logging Dictionary
+=============================
+
+This page describes the common entries of the Structured Logging component.
+Currently :ref:`setting-structured-logging-backend` can have these values:
+
+- The ``default`` text based backend
+- The ``systemd-journal`` backend
+- The ``json`` backend (added in version 5.1.0).
+
+The ``default`` backend
+-----------------------
+The default backend uses a text representation of the key-value pairs.
+A line is constructed by appending all key-value pairs as ``key="value"``, separated by spaces.
+The output is written by passing the resulting text line to the standard error stream and also to ``syslog`` if :ref:`setting-disable-syslog` is false.
+Depending on the value of :ref:`setting-log-timestamp` a timestamp is prepended to the log line.
+
+An example line (including prepended timestamp) looks like this::
+
+ Oct 18 08:45:21 msg="Raised soft limit on number of filedescriptors to match max-mthreads and threads settings" subsystem="config" level="0" prio="Warning" tid="0" ts="1697611521.119" limit="6469"
+
+- Key names are not quoted.
+- Values are quoted with double quotes.
+- If a value contains a double quote, it is escaped with a backslash.
+- Backslashes in the value are escaped by prepending a backslash.
+
+The following keys are always present:
+
++-------------+------------------+--------------------------------------+---------------------------------------+
+| **Key** | **Type** | **Example** | **Remarks** |
++-------------+------------------+--------------------------------------+---------------------------------------+
+|``msg`` |``string`` | ``"Launching distributor threads"`` |Value is the same for all instances of |
+| | | |this log entry, together with |
+| | | |``subsystem`` it uniquely identifies |
+| | | |the log message. |
++-------------+------------------+--------------------------------------+---------------------------------------+
+|``subsystem``|``string`` |``"incoming"`` |Uniquely identifies the log |
+| | | |entry together with the value of |
+| | | |``msg``. |
++-------------+------------------+--------------------------------------+---------------------------------------+
+| ``level`` |``number`` |``"0"`` |The detail level of the log entry, do |
+| | | |not confuse with |
+| | | |:ref:`setting-loglevel`. Not actively |
+| | | |used currently. |
++-------------+------------------+--------------------------------------+---------------------------------------+
+| ``prio`` |``enum`` |``"Notice"`` |One of ``Alert=1``, ``Critical=2``, |
+| | | |``Error=3``, ``Warning=4``, |
+| | | |``Notice=5``, ``Info=6``, |
+| | | |``Debug=7``. A log entry will only |
+| | | |produced if its ``prio`` is equal or |
+| | | |lower than :ref:`setting-loglevel`. |
++-------------+------------------+--------------------------------------+---------------------------------------+
+| ``tid`` |``number`` |``"2"`` |The Posix worker thread id that |
+| | | |produced the log entry. If not produced|
+| | | |by a worker thread, the value is zero. |
++-------------+------------------+--------------------------------------+---------------------------------------+
+| ``ts`` |``number`` |``"1697614303.039"`` |Number of seconds since the Unix epoch,|
+| | | |including fractional part. |
++-------------+------------------+--------------------------------------+---------------------------------------+
+
+A log entry can also have zero or more additional key-value pairs. Common keys are:
+
++-------------+---------------------+--------------------------------------+---------------------------------------+
+| **Key** | **Type** |**Example** | **Remarks** |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``error`` |``string`` |``"No such file or directory"`` |An error cause. |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``address`` |``ip address:port`` |``"[::]:5301"`` |An IP: port combination. |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``addresses``|``list of subnets`` |``"127.0.0.0/8 ::ffff:0:0/96"`` |A list of subnets, space separated. |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``path`` |``filesystem path`` |``"tmp/api-dir/apizones"`` | |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``proto`` |``string`` |``"udp"`` | |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``qname`` |``DNS name`` |``"example.com"`` | |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``qtype`` |``DNS Query Type`` |``"AAAA"`` |Text representation of DNS query type. |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+| ``rcode`` |``DNS Response Code``|``"3"`` |Numeric DNS response code |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``mtid`` |``Number`` |``"234"`` |The id of the MThread that produced the|
+| | | |log entry. |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+
+The ``systemd-journal`` backend
+-------------------------------
+The ``systemd-journal`` structured logging backend uses mostly the same keys and values as the default backend, with the exceptions:
+
+- keys are capitalized as required for ``systemd-journal``.
+- ``msg`` is translated to ``MESSAGE``.
+- ``prio`` is translated to ``PRIORITY``.
+- ``ts`` is translated to ``TIMESTAMP``.
+- If the original key is in a list of keys special to ``systemd-journal``, it is capitalized and prepended by ``PDNS_``.
+ The list of special keys is: message, message_id, priority, code_file, code_line, code_func, errno, invocation_id, user_invocation_id, syslog_facility, syslog_identifier, syslog_pid, syslog_timestamp, syslog_raw, documentation, tid, unit, user_unit, object_pid.
+
+To use this logging backend, add the ``--structured-logging-backend=systemd-journal`` to the command line in the systemd unit file.
+Note that adding it to the recursor configuration file does not work as expected, as this file is processed after the logging has been set up.
+
+To query the log, use a command similar to::
+
+ # journalctl -r -n 1 -o json-pretty -u pdns-recursor.service
+
+The ``json`` backend
+--------------------
+The ``json`` structured logging backend has been added in version 5.1.0 and uses the same keys and values as the default backend.
+An example of a a log object::
+
+ {"level": "0", "limit": "10765", "msg": "Raised soft limit on number of filedescriptors to match max-mthreads and threads settings", "priority": "4", "subsystem": "config", "tid": "0", "ts": "1709285994.851"}
+
+All values are represented as strings.
+
+The JSON log objects are written to the standard error stream.
--- /dev/null
+Conversion of old-style settings to YAML format
+================================================
+
+Running the command
+
+.. code-block:: sh
+
+ rec_control show-yaml
+
+will show the conversion of existing old-style settings into the new YAML format.
+The existing settings will be read from the default old-style settings file ``recursor.conf`` in the configuration directory.
+It is also possible to show the conversion of a specific old-style settings file by running
+
+.. code-block:: sh
+
+ rec_control show-yaml path/to/recursor.conf
+
+``rec_control show-yaml`` will also show the conversions of any included ``.conf`` file (if :ref:`setting-include-dir` is set) and other associated settings file, like :ref:`setting-forward-zones-file`.
+
+Example
+-------
+
+Consider the old style configuration file ``recursor.conf``:
+
+.. literalinclude:: example/recursor.conf
+
+With the contents of ``recursor.d/01.conf``:
+
+.. literalinclude:: example/recursor.d/01.conf
+
+And ``fwzones.txt``:
+
+.. literalinclude:: example/fwzones.txt
+
+To show the conversion result, run:
+
+.. code-block:: sh
+
+ cd example
+ rec_control show-yaml recursor.conf
+
+Produces the following conversion report:
+
+.. literalinclude:: example/conversion
+
+Note the ``!override`` tag for ``incoming.listen`` (corresponding to the ``=`` in ``recursor.d/01.conf``.
+The ``recursor.forward_zones`` settings is extending the setting in the main ``recursord.yml`` file, as ``recursor.d/01.conf`` uses a ``+=`` for the ``forward-zones`` settings.
+Consult :doc:`../yamlsettings` for details on how settings spread over multiple files are merged.
+
+The contents of the report can be used to produce YAML settings equivalent to the old-style settings.
+This is a manual step and consists of copy-pasting the sections of the conversion report to individual files.
+
+Any converted settings filename in the **include** directory should end with ``.yml``.
+The names of associated files (like a ``recursor.forward_zones_file``) should also end in ``.yml``, but should *not* be put into the **include** directory, as they do not contain full configuration YAML clauses but YAML sequences of a specific type.
+The associated files *can* be put in the **config** directory, the directory that is searched for a ``recursor.conf`` or ``recursor.yml`` file.
+
+API Managed Files
+-----------------
+The format of API managed files was also changed to use YAML format.
+Specifically, the list of API managed zones is now a single file containing a sequence of ``auth_zones`` and a sequence of ``forward_zones`` instead of a settings file per zone.
+The list of ACLs is a YAML sequence of subnets or IP addresses.
+
+When using YAML settings :ref:`setting-yaml-recursor.include_dir` and :ref:`setting-yaml-webservice.api_dir` must have a different value.
+When YAML settings are active the :program:`Recursor` will read old-style API managed files from the include directory on startup, convert them to the new format and write them into the API config directory.
+After conversion, it will inactivate the old-style API managed config files in the include directory by renaming them.
+
The full release notes can be read `on the blog <https://blog.powerdns.com/2017/12/04/powerdns-recursor-4-1/>`__.
- This is a major release containing significant speedups (both in throughput and latency), enhanced capabilities and a highly conformant and robust DNSSEC validation implementation that is ready for heavy production use. In addition, our EDNS Client Subnet implementation now scales effortlessly to networks needing very fine grained scopes (as used by some ‘country sized’ service providers).
+ This is a major release containing significant speedups (both in throughput and latency), enhanced capabilities and a highly conformant and robust DNSSEC validation implementation that is ready for heavy production use. In addition, our EDNS Client Subnet implementation now scales effortlessly to networks needing very fine-grained scopes (as used by some ‘country sized’ service providers).
- Improved DNSSEC support,
- Improved documentation,
:tags: Bug Fixes
:pullreq: 5209
- Ensure locks can not be copied.
+ Ensure locks cannot be copied.
.. change::
:tags: Improvements, RPZ
:pullreq: 10634
:tickets: 10631
- Move MacOS to kqueue event handler and assorted compile fixes.
+ Move macOS to kqueue event handler and assorted compile fixes.
.. change::
:tags: Bug Fixes
Changelogs for 4.7.X
====================
+.. changelog::
+ :version: 4.7.6
+ :released: 25th of August 2023
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13157
+ :tickets: 13105
+
+ (I)XFR: handle partial read of len prefix.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13079
+ :tickets: 12892
+
+ YaHTTP: Prevent integer overflow on very large chunks.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13075
+ :tickets: 12961
+
+ Work around Red Hat 8 misfeature in OpenSSL's headers.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13058
+ :tickets: 13021
+
+ Fix setting of policy tags for packet cache hits.
+
.. changelog::
:version: 4.7.5
:released: 29th of March 2023
Changelogs for 4.8.X
====================
+.. changelog::
+ :version: 4.8.7
+ :released: 7th of March 2024
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13797
+ :tickets: 13353
+
+ If serving stale, wipe CNAME records from cache when we get a NODATA negative response for them.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13799
+
+ Fix the zoneToCache regression introduced by SA 2024-01.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13854
+ :tickets: 13847
+
+ Fix gathering of denial of existence proof for wildcard-expanded names.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13796
+ :tickets: 13387
+
+ Update new b-root-server.net addresses in built-in hints.
+
+.. changelog::
+ :version: 4.8.6
+ :released: 13th of February 2024
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13784
+
+ `Security advisory 2024-01 <https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html>`__: CVE-2023-50387 and CVE-2023-50868
+
+.. changelog::
+ :version: 4.8.5
+ :released: 25th of August 2023
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13158
+ :tickets: 13105
+
+ (I)XFR: handle partial read of len prefix.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13078
+ :tickets: 12892
+
+ YaHTTP: Prevent integer overflow on very large chunks.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13077
+ :tickets: 12935
+
+ Stop using the now deprecated ERR_load_CRYPTO_strings() to detect OpenSSL.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13076
+ :tickets: 12961
+
+ Work around Red Hat 8 misfeature in OpenSSL's headers.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13056
+ :tickets: 13021
+
+ Fix setting of policy tags for packet cache hits.
.. changelog::
:version: 4.8.4
:pullreq: 11857
:tickets: 11855
- Set ``rec_control_LDFLAGS``, needed for MacOS or any platforms where libcrypto is not in default lib path.
+ Set ``rec_control_LDFLAGS``, needed for macOS or any platforms where libcrypto is not in default lib path.
.. change::
:tags: Improvements
Changelogs for 4.9.X
====================
+.. changelog::
+ :version: 4.9.4
+ :released: 7th of March 2024
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13853
+
+ Fix gathering of denial of existence proof for wildcard-expanded names.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13795
+ :tickets: 13788
+
+ Fix the zoneToCache regression introduced by SA 2024-01.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13793
+ :tickets: 13387, 12897
+
+ Update new b-root-server.net addresses in built-in hints.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13792
+ :tickets: 13543
+
+ A single NSEC3 record covering everything is a special case.
+
+.. changelog::
+ :version: 4.9.3
+ :released: 13th of February 2024
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13783
+
+ `Security advisory 2024-01 <https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html>`__: CVE-2023-50387 and CVE-2023-50868
+
+.. changelog::
+ :version: 4.9.2
+ :released: 8th of November 2023
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13449
+ :tickets: 13383, 13409
+
+ Handle serve stale logic in getRootNXTrust().
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13411
+ :tickets: 13353
+
+ If serving stale, wipe CNAME records from cache when we get a NODATA negative response for them.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13412
+ :tickets: 13408
+
+ Handle stack memory on NetBSD as on OpenBSD.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13286
+ :tickets: 13092
+
+ Prevent two cases of copy of data that can be moved.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13284
+ :tickets: 13210
+
+ Remove Before=nss-lookup.target line from systemd unit file.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13283
+ :tickets: 13278
+
+ Prevent lookups for unsupported qtypes or rcode != 0 to submit refresh tasks.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13282
+ :tickets: 13209
+
+ Implement a more fair way to prune the aggressive cache.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13176
+ :tickets: 13102
+
+ Do not assume the records are in a particular order when determining if an answer is NODATA.
+
+.. changelog::
+ :version: 4.9.1
+ :released: 25th of August 2023
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13163
+ :tickets: 13071
+
+ Fix code producing json.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13161
+ :tickets: 13106
+
+ Replace data in the aggressive cache if new data becomes available.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13160
+ :tickets: 13151
+
+ Fix a few typos in log messages.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13159
+ :tickets: 13105
+
+ (I)XFR: handle partial read of len prefix.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13057
+ :tickets: 13021
+
+ Fix setting of policy tags on packet cache hits.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12995
+ :tickets: 12961
+
+ Work around Red Hat 8 misfeature OpenSSL's headers.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12994
+ :tickets: 12935
+
+ Stop using the now deprecated ERR_load_CRYPTO_strings() to detect OpenSSL.
+
+.. changelog::
+ :version: 4.9.0
+ :released: 30th of June 2023
+
+ Please review the :doc:`Upgrade Guide <../upgrade>` before upgrading from versions < 4.9.x.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12968
+ :tickets: 12963
+
+ Fix qname length getting out-of-sync with qname-minimization iteration count.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12936
+ :tickets: 12933
+
+ Rewrite and fix loop that checks if algorithms are available.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12932
+ :tickets: 12928
+
+ Fix daemonize() to properly background the process.
+
+.. changelog::
+ :version: 4.9.0-rc1
+ :released: 15nd of June 2023
+
+ Please review the :doc:`Upgrade Guide <../upgrade>` before upgrading from versions < 4.9.x.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12906
+ :tickets: 12468
+
+ Escape key names that are special in the systemd-journal structured logging backend.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12893
+ :tickets: 12890
+
+ Add feature to switch off unsupported DNSSEC algos, either automatically or manually.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12900
+
+ Prevent duplicate C/DNAMEs being included when doing serve-stale.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12896
+ :tickets: 12855
+
+ Expose NOD/UDR metrics.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12883
+ :tickets: 8232
+
+ Add SOA to RPZ modified answers if configured to do so.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12898
+
+ Keep track of max depth reached and report it if !quiet.
+ .. change::
+ :tags: Improvements
+ :pullreq: 12793,12904
+
+ Another set of fixes for clang-tidy reports.
+
+.. changelog::
+ :version: 4.9.0-beta1
+ :released: 2nd of June 2023
+
+ Please review the :doc:`Upgrade Guide <../upgrade>` before upgrading from versions < 4.9.x.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12861
+ :tickets: 12848
+
+ Introduce a way to completely disable root-refresh.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12673
+
+ Sanitize d_orig_ttl stored in record cache.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12838,12837,12836,12790
+
+ Delint some files to make clang-tidy not report any issue.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 12829
+ :tickets: 12790
+
+ Fix clang-tidy botch with respect to spelling of "log-fail".
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12779,12862
+
+ Distinguish between recursion depth and CNAME chain length.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12750
+
+ Log if the answer was marked variable by SyncRes and if it was stored into the packet cache (if !quiet).
+
.. changelog::
:version: 4.9.0-alpha1
:released: 14th of April 2023
+ Please review the :doc:`Upgrade Guide <../upgrade>` before upgrading from versions < 4.9.x.
+
.. change::
:tags: Improvements
:pullreq: 12710
:tags: Improvements
:pullreq: 12709
- More fine grained capping of packet cache TTL.
+ More fine-grained capping of packet cache TTL.
.. change::
:tags: Bug Fixes
:tags: Improvements
:pullreq: 10072,12716
- Update Debian packaging for Recursor (Chris Hofstaedtler).
+ Update Debian packaging for Recursor, including removal of sysv init script (Chris Hofstaedtler).
.. change::
:tags: Improvements
--- /dev/null
+Changelogs for 5.0.X
+====================
+
+Before upgrading, it is advised to read the :doc:`../upgrade`.
+
+.. changelog::
+ :version: 5.0.3
+ :released: 7th of March 2024
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13845
+ :tickets: 13824
+
+ Log if a DNSSEC related limit was hit if log_bogus is set.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13846
+ :tickets: 13830
+
+ Reduce RPZ memory usage by not keeping the initially loaded RPZs in memory.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13852
+ :tickets: 13847
+
+ Fix gathering of denial of existence proof for wildcard-expanded names.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13791
+ :tickets: 13788
+
+ Fix the zoneToCache regression introduced by SA 2024-01.
+
+.. changelog::
+ :version: 5.0.2
+ :released: 13th of February 2024
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13782
+
+ `Security advisory 2024-01 <https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html>`__: CVE-2023-50387 and CVE-2023-50868
+
+.. changelog::
+ :version: 5.0.1
+ :released: 10th of January 2024, with no changes compared to the second release candidate. Version 5.0.0 was never released publicly.
+
+.. changelog::
+ :version: 5.0.0-rc2
+ :released: 20th of December 2023
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13646
+ :tickets: 13588, 13612
+
+ Fix handling of RUNTIME_DIRECTORY and NOD dirs.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13645
+ :tickets: 13567
+
+ Warn that disabling structured logging is now deprecated.
+
+.. changelog::
+ :version: 5.0.0-rc1
+ :released: 6th of December 2023
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13557
+
+ Remove experimental warnings for YAML.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13507
+ :tickets: 13386
+
+ Disallow (by answering Refused) RD=0 by default.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13543
+ :tickets: 13542
+
+ A single NSEC3 record covering everything is a special case.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13434
+
+ Make syncres code clang-tidy.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13511
+ :tickets: 13463
+
+ Document outgoing query counts better, including a small fix.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13501
+ :tickets: 12842
+
+ Introduce a setting to allow RPZ duplicates, including a dup handling fix.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13497
+ :tickets: 13483
+
+ Take into account throttled queries when determining if we had a cache hit.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13387
+
+ Update new b-root-server.net addresses in built-in hints.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13480
+ :tickets: 13467
+
+ Correctly apply outgoing.tcp_max_queries bound.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13478
+
+ Change default of nsec3-max-iterations to 50.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13477
+
+ Warn if truncation occurred dumping the trace.
+
+.. changelog::
+ :version: 5.0.0-beta1
+ :released: 10th of November 2023
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13468
+
+ Fix ubsan error: using a value of 80 for bool.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13462
+
+ Be more memory efficient handling RPZ updates.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13464
+
+ Change default of extended-resolution-errors setting to true.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13455
+
+ Move a few settings from recursor to outgoing section.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13446
+
+ For structured logging always log addresses including port.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13438
+
+ Teach configure to check for cargo version and require >= 1.64.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13410
+ :tickets: 12612
+
+ Tidy cache and only copy values if non-expired entry was found.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13409
+ :tickets: 13383
+
+ Handle serve stale logic in getRootNXTrust().
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13432,13430
+ :tickets: 13430
+
+ Add endbr64 instructions in the right spots for OpenBSD/amd64.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13408
+
+ Handle stack memory on NetBSD as on OpenBSD (Tom Ivar Helbekkmo)
+
+.. changelog::
+ :version: 5.0.0-alpha2
+ :released: 17th of October 2023
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13362
+ :tickets: 13233, 12679
+
+ Convert API managed config from old style to YAML if YAML settings are active.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13364
+
+ If we miss glue--but not for all NS records--try to resolve the missing address records.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13353
+ :tickets: 12395
+
+ If serving stale, wipe CNAME records from cache when we get a NODATA negative response for them.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13363
+
+ Fix Coverity 1522436 potential dereference of null return value.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13296
+
+ Make QName Minimization parameters from :rfc:`9156` settable.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13312
+
+ Conform to :rfc:`2181` 10.3: don't allow NS records to point to aliases.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13303,13311
+
+ Fix log messages text and levels.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13295
+ :tickets: 8646
+
+ Do not use Qname Minimization for infra-queries.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13289
+
+ Implement probabilistic un-throttle.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13290
+
+ Put files generated by settings/generate.py into tarball so package builds do not have to run it.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13278
+ :tickets: 13266
+
+ Fix packetcache submit refresh task logic.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13276
+ :tickets: 13259
+
+ Fix sysconfdir handling in new settings code.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13277
+ :tickets: 13264
+
+ Allow loglevel to be set to levels < 3.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13195
+ :tickets: 8394
+
+ Move tcp-in processing to dedicated thread(s).
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13250
+
+ Fix Coverity 1519054: Using invalid iterator.
+
+.. changelog::
+ :version: 5.0.0-alpha1
+ :released: 13th of September 2023
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13008
+
+ Rewrite settings code, introducing YAML settings file, using Rust and generated code to implement YAML processing
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13209
+
+ Make aggressive cache pruning more effective and more fair.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13210
+
+ Remove Before=nss-lookup.target line from unit file.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13208
+
+ Remove make_tuple and make_pair (Rosen Penev).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13190
+
+ Rec: fix a few unused argument warnings (depending on features enabled).
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13167
+
+ TCPIOHandler: Fix a race when creating the first TLS connections.
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 13174
+
+ Rec: Include cstdint in mtasker_ucontext.cc, noted by @zeha.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13168
+
+ Change the default for building with net-snmp from `auto` to `no`.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13155
+ :tickets: 13147
+
+ Channel: Make the blocking parameters of the object queue explicit.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13102
+
+ Do not assume the records are in a particular order when determining if an answer is NODATA.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13111
+
+ Document default for `webserver-loglevel` (Frank Louwers).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13087
+
+ Remove unused sysv init files.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13092
+
+ Fixes a few performance issues reported by Coverity.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13074
+
+ Highlight why regression tests failed with github annotation (Josh Soref)
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13073
+
+ Switch from deprecated ::set-output (Josh Soref).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13067
+
+ Use backticks in rec_control(1) (Josh Soref).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13068
+
+ Clarify why bulktest is failing (Josh Soref).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13043
+ :tickets: 13011
+
+ Set TTL in getFakePTRRecords.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13032
+
+ Update settings.rst -- clarify edns-subnet-allow-list (Seth Arnold).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 13026
+
+ Dnsheader: Switch from bitfield to uint16_t whenever possible.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12805
+
+ Clarify log message for NODATA/NXDOMAIN without AA (Håkan Lindqvist).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12913,12931,12999,13001,13022,13175,15197
+
+ Use arc4random only for random values.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12851
+
+ Update base Debian version in Docker docs (Italo Cunha).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12917
+
+ Delint pdns recursor.cc.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12957
+
+ Include qname when logging skip of step 4 of qname minimization (Doug Freed).
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12952
+
+ Fix a set of move optimizations, as suggested by Coverity.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12934
+
+ Silence Coverity 1462719 Unchecked return value from library.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12930
+
+ Fix compile warnings.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12913
+
+ Dns random: add method to get full 32-bits of randomness.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 12808
+
+ Reformat and delint arguments.cc and arguments.hh.
+
+
+
The changelogs for the recursor are split between release trains.
+Before upgrading, it is advised to read the :doc:`../upgrade`.
+
.. toctree::
:maxdepth: 2
+ 5.0
4.9
4.8
4.7
# General information about the project.
project = 'PowerDNS Recursor'
-copyright = '2001-' + str(datetime.date.today().year) + ', PowerDNS.COM BV'
+copyright = 'PowerDNS.COM BV'
author = 'PowerDNS.COM BV'
# The version info for the project you're documenting, acts as replacement for
On OpenBSD, :program:`Recursor` is available through the `OpenBSD ports system <https://openports.se/net/powerdns_recursor>`_.
Run ``pkg_add powerdns-recursor`` as root to install.
-MacOS
+macOS
^^^^^
-On MacOS :program:`Recursor` is available through `brew <https://brew.sh/>`_.
+On macOS :program:`Recursor` is available through `brew <https://brew.sh/>`_.
Run ``brew install pdnsrec`` to install.
Compiling From Source
Only :ref:`setting-allow-from` and :ref:`setting-allow-notify-from` can be set.
.. note::
- For configuration changes to work :ref:`setting-include-dir` and :ref:`setting-api-config-dir` should have the same value.
+ For configuration changes to work :ref:`setting-include-dir` and :ref:`setting-api-config-dir` should have the same value for old-style settings.
+ When using YAML settings :ref:`setting-yaml-recursor.include_dir` and :ref:`setting-yaml-webservice.api_dir` must have a different value.
:param server_id: The name of the server
:param config_setting_name: The name of the setting to change
curl -v -H 'X-API-Key: changeme' http://127.0.0.1:8082/api/v1/servers/localhost | jq .
curl -v -H 'X-API-Key: changeme' http://127.0.0.1:8082/api/v1/servers/localhost/zones | jq .
+A few examples for zone manipulation follow, first one is to create a forwarding zone::
+
+ curl --no-progress-meter -H 'X-API-Key: changeme' -H 'Content-type: application/json' -X POST --data-binary @- http://localhost:8082/api/v1/servers/localhost/zones << EOF | jq
+ {
+ "name": "example.com.",
+ "type": "Zone",
+ "kind": "Forwarded",
+ "servers": ["192.168.178.1", "192.168.178.2:5353"],
+ "recursion_desired" : false
+ }
+ EOF
+
+Example output of the above command::
+
+ {
+ "id": "example.com.",
+ "kind": "Forwarded",
+ "name": "example.com.",
+ "records": [],
+ "recursion_desired": false,
+ "servers": [
+ "192.168.178.1:53",
+ "192.168.178.2:5353"
+ ],
+ "url": "/api/v1/servers/localhost/zones/example.com."
+ }
+
+To delete the forwarding zone added above::
+
+ curl --no-progress-meter -H 'X-API-Key: changeme' -X DELETE http://localhost:8082/api/v1/servers/localhost/zones/example.com.
+
URL Endpoints
-------------
Zone
----
-A Zone object represents an authoritative DNS Zone.
+A Zone object represents a forward or authoritative DNS Zone.
A Resource Record Set (below as "RRset") are all records for a given name and type.
command line. Setting these options on the command line will
override what has been set in the dynamically generated
configuration files.
-* ``include-dir`` must refer to the same directory as
- ``api-config-dir`` for the dynamic reloading to work.
+
+* For configuration changes to work :ref:`setting-include-dir` and :ref:`setting-api-config-dir` should have the same value for old-style settings.
+ When using YAML settings :ref:`setting-yaml-recursor.include_dir` and :ref:`setting-yaml-webservice.api_dir` must have a different value.
.. include:: ../common/api/zone.rst
running
dnssec
settings
+ yamlsettings
lua-config/index
lua-scripting/index
dns64
The first line specifies that additional records should be added to the results of ``MX`` queries using the default mode.
The qtype of the records to be added are ``A`` and ``AAAA``.
-The default mode is ``pdns.AdditionalMode.CacheOnlyRequireAuth``, this mode will only look in the record cache.
+The default mode is ``pdns.AdditionalMode.CacheOnlyRequireAuth``; this mode will only look in the record cache.
The second line specifies that three record types should be added to ``NAPTR`` answers.
If needed, the Recursor will do an active resolve to retrieve these records.
+Note that with record types such as ``NAPTR`` which can return records such as ``SRV``, which may themselves return additional
+``A`` or ``AAAA`` records, the above example would not be sufficient to return those additional ``A`` and/or ``AAAA`` records.
+In such a case, you would need to add an additional line to tell the recursor to fetch the additional records for the ``SRV``
+qtype as well. An example configuration for this case is shown below:
+
+.. code-block:: Lua
+
+ addAllowedAdditionalQType(pdns.NAPTR, {pdns.A, pdns.AAAA, pdns.SRV}, {mode=pdns.AdditionalMode.ResolveImmediately})
+ addAllowedAdditionalQType(pdns.SRV, {pdns.A, pdns.AAAA}, {mode=pdns.AdditionalMode.ResolveImmediately})
+
The modes available are:
``pdns.AdditionalMode.Ignore``
An extended error extra text (:rfc:`8914`) to set on RPZ hits. See :ref:`setting-extended-resolution-errors`.
+includeSOA
+^^^^^^^^^^
+.. versionadded:: 4.9.0
+
+Include the RPZ's SOA record to the reply's additional section if modified by a policy hit.
+Defaults to ``false``.
+
+ignoreDuplicates
+^^^^^^^^^^^^^^^^
+.. versionadded:: 5.0.0
+
+When loading an RPZ, ignore duplicate entries, keeping only the first one present in the zone.
+Defaults to ``false``, duplicate entries will cause failure to load the zone.
+
maxTTL
^^^^^^
The maximum TTL value of the synthesized records, overriding a higher value from ``defttl`` or the zone. Default is unlimited.
refresh
^^^^^^^
An integer describing the interval between checks for updates.
-By default, the RPZ zone's default is used
+By default, the RPZ zone's default is used.
+If allowed by :ref:`setting-allow-notify-for` and :ref:`setting-allow-notify-from`, a ``notify`` for an RPZ zone will initiate a freshness check.
maxReceivedMBytes
^^^^^^^^^^^^^^^^^
localAddress
~~~~~~~~~~~~
The source IP address to use when transferring using the ``axfr`` or ``url`` methods.
-When unset, :ref:`setting-query-local-address` is used.
+For the ``axfr`` method :ref:`setting-query-local-address` is used by default.
+The default used for ``url`` method is system dependent.
zonemd
~~~~~~
-- Perform here your maintenance
end
-The interval can be configured through the :ref:`setting-maintenance-interval` setting.
+The interval can be configured through the :ref:`setting-lua-maintenance-interval` setting.
Shows the currently active queries.
clear-dont-throttle-names NAME [NAME...]
- Remove names that are not allowed to be throttled. If *NAME* is '*', remove all
+ Remove names that are not allowed to be throttled. If *NAME* is ``*``, remove all
clear-dont-throttle-netmasks NETMASK [NETMASK...]
- Remove netmasks that are not allowed to be throttled. If *NETMASK* is '*', remove all
+ Remove netmasks that are not allowed to be throttled. If *NETMASK* is ``*``, remove all
clear-nta *DOMAIN*...
Remove Negative Trust Anchor for one or more *DOMAIN*\ s. Set domain to
- '*' to remove all NTA's.
+ ``*`` to remove all NTA's.
clear-ta [*DOMAIN*]...
Remove Trust Anchor for one or more *DOMAIN*\ s. Note that removing the
Shows a list of supported commands understood by the running
:program:`pdns_recursor`
+list-dnssec-algos
+ List supported (and potentially disabled) DNSSEC algorithms.
+
ping
Check if server is alive.
not empty, also set the carbon-ourname setting to *CARBON OURNAME*.
set-dnssec-log-bogus *SETTING*
- Set dnssec-log-bogus setting to *SETTING*. Set to 'on' or 'yes' to log
- DNSSEC validation failures and to 'no' or 'off' to disable logging these
+ Set dnssec-log-bogus setting to *SETTING*. Set to ``on`` or ``yes`` to log
+ DNSSEC validation failures and to ``no`` or ``off`` to disable logging these
failures.
set-ecs-minimum-ttl *NUM*
Set ecs-minimum-ttl-override to *NUM*.
+set-max-aggr-nsec-cache-size *NUM*
+ Change the maximum number of entries in the NSEC aggressive cache. If the
+ cache is disabled by setting its size to 0 in the config, the cache size
+ cannot be set by this command. Setting the size to 0 by this command still
+ keeps the cache, but makes it mostly ineffective as it is emptied periodically.
+
set-max-cache-entries *NUM*
Change the maximum number of entries in the DNS cache. If reduced, the
cache size will start shrinking to this number as part of the normal
Set minimum-ttl-override to *NUM*.
set-event-trace-enabled *NUM*
- Set logging of event trace messages, 0 = disabled, 1 = protobuf, 2 = log file, 3 = both.
+ Set logging of event trace messages, ``0`` = disabled, ``1`` = protobuf,
+ ``2`` = log file, ``3`` = protobuf and log file.
+
+show-yaml [*FILE*]
+ Show Yaml representation of odl-style config.
top-queries
Shows the top-20 queries. Statistics are over the last
Wipe entries for *DOMAIN* (exact name match) from the cache. This is useful
if, for example, an important server has a new IP address, but the TTL has
not yet expired. Multiple domain names can be passed.
- *DOMAIN* can be suffixed with a '$'. to delete the whole tree from the
- cache. i.e. 'powerdns.com$' will remove all cached entries under and
+ *DOMAIN* can be suffixed with a ``$``. to delete the whole tree from the
+ cache. i.e. ``powerdns.com$`` will remove all cached entries under and
including the powerdns.com name.
**Note**: this command also wipes the negative cache.
all-outqueries
^^^^^^^^^^^^^^
-counts the number of outgoing UDP queries since starting
+counts the number of outgoing queries since starting, this includes UDP, TCP, DoT queries both over IPv4 and IPv6
answers-slow
^^^^^^^^^^^^
dot-outqueries
^^^^^^^^^^^^^^
-counts the number of outgoing DoT queries since starting
+counts the number of outgoing DoT queries since starting, both using IPv4 and IPv6
qname-min-fallback-success
^^^^^^^^^^^^^^^^^^^^^^^^^^
ipv6-outqueries
^^^^^^^^^^^^^^^
-number of outgoing queries over IPv6
+number of outgoing queries over IPv6 using UDP, since version 5.0.0 also including TCP and DoT
ipv6-questions
^^^^^^^^^^^^^^
-counts all end-user initiated queries with the RD bit set, received over IPv6 UDP
+counts all client initiated queries using IPv6
maintenance-usec
^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^
number of erroneous received packets
+nod-events
+^^^^^^^^^^
+.. versionadded:: 4.9.0
+
+Count of NOD events
+
+udr-events
+^^^^^^^^^^
+.. versionadded:: 4.9.0
+
+Count of UDR events
+
nod-lookups-dropped-oversize
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Number of NOD lookups dropped because they would exceed the maximum name length
tcp-outqueries
^^^^^^^^^^^^^^
-counts the number of outgoing TCP queries since starting
+counts the number of outgoing TCP queries since starting, both using IPv4 and IPV6
tcp-questions
^^^^^^^^^^^^^
If ``SO_REUSEPORT`` support is available and :ref:`setting-reuseport` is set to ``yes``, which is the
default since version 4.9.0, separate listening sockets are opened for each worker thread and the query distributions is handled by the kernel, avoiding any thundering herd issue as well as preventing the distributor thread from becoming the bottleneck.
+The next section discusses how to determine if the mechanism is working properly.
-On some systems setting :ref:`setting-reuseport` to ``yes`` does not have the desired effect.
-If your systems shows imbalance in the number of queries processed per thread (as reported by the periodic statistics report), try switching :ref:`setting-reuseport` to ``no`` and/or setting :ref:`setting-pdns-distributes-queries` to ``yes``.
+.. _worker_imbalance:
+
+Imbalance
+^^^^^^^^^
+Due to the nature of the distribution method used by the kernel imbalance with the new default settings of :ref:`setting-reuseport` and :ref:`setting-pdns-distributes-queries` may occur if you have very few clients.
+Imbalance can be observed by reading the periodic statistics reported by :program:`Recursor`::
+
+ Jun 26 11:06:41 pepper pdns-recursor[10502]: msg="Queries handled by thread" subsystem="stats" level="0" prio="Info" tid="0" ts="1687770401.359" count="7" thread="0"
+ Jun 26 11:06:41 pepper pdns-recursor[10502]: msg="Queries handled by thread" subsystem=" stats" level="0" prio="Info" tid="0" ts="1687770401.359" count="535167" thread="1"
+ Jun 26 11:06:41 pepper pdns-recursor[10502]: msg="Queries handled by thread" subsystem=" stats" level="0" prio="Info" tid="0" ts="1687770401.359" count="5" thread="2"
+
+In the above log lines we see that almost all queries are processed by thread 1.
+This can typically be observed when using ``dnsdist`` in front of :program:`Recursor`.
+
+When using ``dnsdist`` with a single ``newServer`` to a recursor instance in its configuration, the kernel will regard ``dnsdist`` as a single client unless you use the ``sockets`` parameter to ``newServer`` to increase the number of source ports used by ``dnsdist``.
+The following guideline applies for the ``dnsdist`` case:
+
+- Be generous with the ``sockets`` setting of ``newServer``.
+ A starting points is to configure twice as many sockets as :program:`Recursor` threads.
+- As long as the threads of the :program:`Recursor` as not overloaded, some imbalance will not impact performance significantly.
+- If you want to reduce imbalance, increase the value of ``sockets`` even more.
+
+Non-Linux systems
+^^^^^^^^^^^^^^^^^
+On some systems setting :ref:`setting-reuseport` to ``yes`` does not have the desired effect at all.
+If your systems shows great imbalance in the number of queries processed per thread (as reported by the periodic statistics report), try switching :ref:`setting-reuseport` to ``no`` and/or setting :ref:`setting-pdns-distributes-queries` to ``yes``.
.. versionadded:: 4.1.0
The :ref:`setting-cpu-map` parameter can be used to pin worker threads to specific CPUs, in order to keep caches as warm as possible and optimize memory access on NUMA systems.
PowerDNS Recursor uses a cooperative multitasking in userspace called ``MTasker``, based either on ``boost::context`` if available, or on ``System V ucontexts`` otherwise. For maximum performance, please make sure that your system supports ``boost::context``, as the alternative has been known to be quite slower.
The maximum number of simultaneous MTasker threads, called ``MThreads``, can be tuned via :ref:`setting-max-mthreads`, as the default value of 2048 might not be enough for large-scale installations.
+This setting limits the number of mthreads *per physical (Posix) thread*.
+The threads that create mthreads are the distributor and worker threads.
When a ``MThread`` is started, a new stack is dynamically allocated for it on the heap. The size of that stack can be configured via the :ref:`setting-stack-size` parameter, whose default value is 200 kB which should be enough in most cases.
-To reduce the cost of allocating a new stack for every query, the recursor can cache a small amount of stacks to make sure that the allocation stays cheap. This can be configured via the :ref:`setting-stack-cache-size` setting. The only trade-off of enabling this cache is a slightly increased memory consumption, at worst equals to the number of stacks specified by :ref:`setting-stack-cache-size` multiplied by the size of one stack, itself specified via :ref:`setting-stack-size`.
+To reduce the cost of allocating a new stack for every query, the recursor can cache a small amount of stacks to make sure that the allocation stays cheap. This can be configured via the :ref:`setting-stack-cache-size` setting.
+This limit is per physical (Posix) thread.
+The only trade-off of enabling this cache is a slightly increased memory consumption, at worst equals to the number of stacks specified by :ref:`setting-stack-cache-size` multiplied by the size of one stack, itself specified via :ref:`setting-stack-size`.
Performance tips
----------------
A Recursor under high load puts a severe stress on any stateful (connection tracking) firewall, so much so that the firewall may fail.
Specifically, many Linux distributions run with a connection tracking firewall configured.
-For high load operation (thousands of queries/second), It is advised to either turn off iptables completely, or use the ``NOTRACK`` feature to make sure DNS traffic bypasses the connection tracking.
+For high load operation (thousands of queries/second), It is advised to either turn off iptables completely, or use the ``NOTRACK`` feature to make sure client DNS traffic bypasses the connection tracking.
Sample Linux command lines would be::
## IPv4
## NOTRACK rules for 53/udp, keep in mind that you also need your regular rules for 53/tcp
- iptables -t raw -I OUTPUT -p udp --dport 53 -j CT --notrack
iptables -t raw -I OUTPUT -p udp --sport 53 -j CT --notrack
iptables -t raw -I PREROUTING -p udp --dport 53 -j CT --notrack
- iptables -t raw -I PREROUTING -p udp --sport 53 -j CT --notrack
iptables -I INPUT -p udp --dport 53 -j ACCEPT
- iptables -I INPUT -p udp --sport 53 -j ACCEPT
- iptables -I OUTPUT -p udp --dport 53 -j ACCEPT
- iptables -I OUTPUT -p udp --sport 53 -j ACCEPT
## IPv6
## NOTRACK rules for 53/udp, keep in mind that you also need your regular rules for 53/tcp
- ip6tables -t raw -I OUTPUT -p udp --dport 53 -j CT --notrack
ip6tables -t raw -I OUTPUT -p udp --sport 53 -j CT --notrack
- ip6tables -t raw -I PREROUTING -p udp --sport 53 -j CT --notrack
ip6tables -t raw -I PREROUTING -p udp --dport 53 -j CT --notrack
ip6tables -I INPUT -p udp --dport 53 -j ACCEPT
- ip6tables -I INPUT -p udp --sport 53 -j ACCEPT
- ip6tables -I OUTPUT -p udp --dport 53 -j ACCEPT
- ip6tables -I OUTPUT -p udp --sport 53 -j ACCEPT
When using FirewallD (Centos 7+ / Red Hat 7+ / Fedora 21+), connection tracking can be disabled via direct rules.
The settings can be made permanent by using the ``--permanent`` flag::
## IPv4
## NOTRACK rules for 53/udp, keep in mind that you also need your regular rules for 53/tcp
- firewall-cmd --direct --add-rule ipv4 raw OUTPUT 0 -p udp --dport 53 -j CT --notrack
firewall-cmd --direct --add-rule ipv4 raw OUTPUT 0 -p udp --sport 53 -j CT --notrack
firewall-cmd --direct --add-rule ipv4 raw PREROUTING 0 -p udp --dport 53 -j CT --notrack
- firewall-cmd --direct --add-rule ipv4 raw PREROUTING 0 -p udp --sport 53 -j CT --notrack
firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p udp --dport 53 -j ACCEPT
- firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p udp --sport 53 -j ACCEPT
- firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -p udp --dport 53 -j ACCEPT
- firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -p udp --sport 53 -j ACCEPT
## IPv6
## NOTRACK rules for 53/udp, keep in mind that you also need your regular rules for 53/tcp
- firewall-cmd --direct --add-rule ipv6 raw OUTPUT 0 -p udp --dport 53 -j CT --notrack
firewall-cmd --direct --add-rule ipv6 raw OUTPUT 0 -p udp --sport 53 -j CT --notrack
firewall-cmd --direct --add-rule ipv6 raw PREROUTING 0 -p udp --dport 53 -j CT --notrack
- firewall-cmd --direct --add-rule ipv6 raw PREROUTING 0 -p udp --sport 53 -j CT --notrack
firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -p udp --dport 53 -j ACCEPT
- firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -p udp --sport 53 -j ACCEPT
- firewall-cmd --direct --add-rule ipv6 filter OUTPUT 0 -p udp --dport 53 -j ACCEPT
- firewall-cmd --direct --add-rule ipv6 filter OUTPUT 0 -p udp --sport 53 -j ACCEPT
Following the instructions above, you should be able to attain very high query rates.
A response to a query is sent immediately when it becomes available; the response can be sent before other responses to queries that were received earlier by the Recursor.
This is the Out-of-Order feature which greatly enhances performance, as a single slow query does not prevent other queries to be processed.
+Before version 5.0.0, TCP queries are processed by either the distributer thread(s) if :ref:`setting-pdns-distributes-queries` is true, or by worker threads if :ref:`setting-pdns-distributes-queries` is false.
+Starting with version 5.0.0, :program:`Recursor` has dedicated thread(s) processing TCP queries.
+
The maximum number of mthreads consumed by TCP queries is :ref:`setting-max-tcp-clients` times :ref:`setting-max-concurrent-requests-per-tcp-connection`.
-This number should be (much) lower than :ref:`setting-max-mthreads`, to also allow UDP queries to be handled as these also consume mthreads.
+Before version 5.0.0, if :ref:`setting-pdns-distributes-queries` is false, this number should be (much) lower than :ref:`setting-max-mthreads`, to also allow UDP queries to be handled as these also consume mthreads.
+Note that :ref:`setting-max-mthreads` is a per Posix thread setting.
+This means that the global maximum number of mthreads is (#distributor threads + #worker threads) * max-mthreads.
If you expect few clients, you can increase :ref:`setting-max-concurrent-requests-per-tcp-connection`, to allow more concurrency per TCP connection.
If you expect many clients and you have increased :ref:`setting-max-tcp-clients`, reduce :ref:`setting-max-concurrent-requests-per-tcp-connection` number to prevent mthread starvation or increase the maximum number of mthreads.
If a query could not be handled due to mthread shortage, the :ref:`stat-over-capacity-drops` metric is increased.
As an example, if you have typically 200 TCP clients, and the default maximum number of mthreads of 2048, a good number of concurrent requests per TCP connection would be 5. Assuming a worst case packet cache hit ratio, if all 200 TCP clients fill their connections with queries, about half (5 * 200) of the mthreads would be used by incoming TCP queries, leaving the other half for incoming UDP queries.
+Note that starting with version 5.0.0, TCP queries are processed by dedicated TCP thread(s), so the sharing of mthreads between UDP and TCP queries no longer applies.
The total number of incoming TCP connections is limited by :ref:`setting-max-tcp-clients`.
There is also a per client address limit: :ref:`setting-max-tcp-per-client` to limit the impact of a single client.
sphinxcontrib-fulltoc
docutils!=0.15,<0.18
jinja2<3.1.0
+alabaster==0.7.13
In a production environment, you will want to be able to monitor PowerDNS performance.
Furthermore, PowerDNS can perform a configurable amount of operational logging.
-On modern Linux distributions, the PowerDNS recursor logs to stdout, which is consumed by ``systemd-journald``.
+On modern Linux distributions, the PowerDNS recursor logs to stderr, which is consumed by ``systemd-journald``.
This means that looking into the logs that are produced, `journalctl <https://www.freedesktop.org/software/systemd/man/journalctl.html>`_ can be used::
# journalctl -u pdns-recursor -n 100
--- /dev/null
+PowerDNS Security Advisory 2024-01: crafted DNSSEC records in a zone can lead to a denial of service in Recursor
+================================================================================================================
+
+- CVE: CVE-2023-50387 and CVE-2023-50868
+- Date: 13th of February 2024.
+- Affects: PowerDNS Recursor up to and including 4.8.5, 4.9.2 and 5.0.1
+- Not affected: PowerDNS Recursor 4.8.6, 4.9.3 and 5.0.2
+- Severity: High
+- Impact: Denial of service
+- Exploit: This problem can be triggered by an attacker publishing a crafted zone
+- Risk of system compromise: None
+- Solution: Upgrade to patched version or disable DNSSEC validation
+
+An attacker can publish a zone that contains crafted DNSSEC related records. While validating
+results from queries to that zone using the RFC mandated algorithms, the Recursor's resource usage
+can become so high that processing of other queries is impacted, resulting in a denial of
+service. Note that any resolver following the RFCs can be impacted, this is not a problem of this
+particular implementation.
+
+CVSS Score: 7.5, see
+https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H&version=3.1
+
+The remedies are one of:
+
+- upgrade to a patched version
+- disable DNSSEC validation by setting ``dnssec=off`` or ``process-no-validate``; when using YAML settings:
+ ``dnssec.validate: off`` or ``process-no-validate``. Note that this will affect clients depending on
+ DNSSEC validation.
+
+We would like to thank Elias Heftrig, Haya Schulmann, Niklas Vogel, and Michael Waidner from the
+German National Research Center for Applied Cybersecurity ATHENE for bringing this issue to the
+attention of the DNS community and especially Niklas Vogel for his assistance in validating the
+patches. We would also like to thank Petr Špaček from ISC for discovering and responsibly disclosing
+CVE-2023-50868.
+++ /dev/null
-PowerDNS Recursor Settings
-==========================
-Each setting can appear on the command line, prefixed by ``--``, or in the configuration file.
-The command line overrides the configuration file.
-
-.. note::
- Settings marked as ``Boolean`` can either be set to an empty value, which means **on**, or to ``no`` or ``off`` which means **off**.
- Anything else means **on**.
-
- For example:
-
- - ``serve-rfc1918`` on its own means: do serve those zones.
- - ``serve-rfc1918 = off`` or ``serve-rfc1918 = no`` means: do not serve those zones.
- - Anything else means: do serve those zones.
-
-You can use ``+=`` syntax to set some variables incrementally, but this
-requires you to have at least one non-incremental setting for the
-variable to act as base setting. This is mostly useful for
-:ref:`setting-include-dir` directive. An example::
-
- forward-zones = foo.example.com=192.168.100.1;
- forward-zones += bar.example.com=[1234::abcde]:5353;
-
-When a list of **Netmasks** is mentioned, a list of subnets can be specified.
-A subnet that is not followed by ``/`` will be interpreted as a ``/32`` or ``/128`` subnet (a single address), depending on address family.
-For most settings, it is possible to exclude ranges by prefixing an item with the negation character ``!``.
-For example::
-
- allow-from = 2001:DB8::/32, 128.66.0.0/16, !128.66.1.2
-
-In this case the address ``128.66.1.2`` is excluded from the addresses allowed access.
-
-.. _setting-aggressive-nsec-cache-size:
-
-``aggressive-nsec-cache-size``
-------------------------------
-.. versionadded:: 4.5.0
-
-- Integer
-- Default: 100000
-
-The number of records to cache in the aggressive cache. If set to a value greater than 0, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in :rfc:`8198`.
-To use this, DNSSEC processing or validation must be enabled by setting `dnssec`_ to ``process``, ``log-fail`` or ``validate``.
-
-.. _setting-aggressive-cache-min-nsec3-hit-ratio:
-
-``aggressive-cache-min-nsec3-hit-ratio``
-----------------------------------------
-.. versionadded:: 4.9.0
-
-- Integer
-- Default: 2000
-
-The limit for which to put NSEC3 records into the aggressive cache.
-A value of ``n`` means that an NSEC3 record is only put into the aggressive cache if the estimated probability of a random name hitting the NSEC3 record is higher than ``1/n``.
-A higher ``n`` will cause more records to be put into the aggressive cache, e.g. a value of 4000 will cause records to be put in the aggressive cache even if the estimated probability of hitting them is twice as low as would be the case for ``n=2000``.
-A value of 0 means no NSEC3 records will be put into the aggressive cache.
-
-For large zones the effectiveness of the NSEC3 cache is reduced since each NSEC3 record only covers a randomly distributed subset of all possible names.
-This setting avoids doing unnecessary work for such large zones.
-
-.. _setting-allow-from:
-
-``allow-from``
---------------
-- IP addresses or netmasks, separated by commas, negation supported
-- Default: 127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10
-
-Netmasks (both IPv4 and IPv6) that are allowed to use the server.
-The default allows access only from :rfc:`1918` private IP addresses.
-An empty value means no checking is done, all clients are allowed.
-Due to the aggressive nature of the internet these days, it is highly recommended to not open up the recursor for the entire internet.
-Questions from IP addresses not listed here are ignored and do not get an answer.
-
-When the Proxy Protocol is enabled (see `proxy-protocol-from`_), the recursor will check the address of the client IP advertised in the Proxy Protocol header instead of the one of the proxy.
-
-Note that specifying an IP address without a netmask uses an implicit netmask of /32 or /128.
-
-.. _setting-allow-from-file:
-
-``allow-from-file``
--------------------
-- Path
-
-Like `allow-from`_, except reading from file.
-Overrides the `allow-from`_ setting. To use this feature, supply one netmask per line, with optional comments preceded by a "#".
-
-.. _setting-allow-notify-for:
-
-``allow-notify-for``
----------------------
-.. versionadded:: 4.6.0
-
-- Comma separated list of domain-names
-- Default: (empty)
-
-Domain names specified in this list are used to permit incoming
-NOTIFY operations to wipe any cache entries that match the domain
-name. If this list is empty, all NOTIFY operations will be ignored.
-
-.. _setting-allow-notify-for-file:
-
-``allow-notify-for-file``
--------------------------
-.. versionadded:: 4.6.0
-
-- Path
-
-Like `allow-notify-for`_, except reading from file. To use this
-feature, supply one domain name per line, with optional comments
-preceded by a "#".
-
-NOTIFY-allowed zones can also be specified using `forward-zones-file`_.
-
-.. _setting-allow-notify-from:
-
-``allow-notify-from``
----------------------
-.. versionadded:: 4.6.0
-
-- IP addresses or netmasks, separated by commas, negation supported
-- Default: unset
-
-Netmasks (both IPv4 and IPv6) that are allowed to issue NOTIFY operations
-to the server. NOTIFY operations from IP addresses not listed here are
-ignored and do not get an answer.
-
-When the Proxy Protocol is enabled (see `proxy-protocol-from`_), the
-recursor will check the address of the client IP advertised in the
-Proxy Protocol header instead of the one of the proxy.
-
-Note that specifying an IP address without a netmask uses an implicit
-netmask of /32 or /128.
-
-NOTIFY operations received from a client listed in one of these netmasks
-will be accepted and used to wipe any cache entries whose zones match
-the zone specified in the NOTIFY operation, but only if that zone (or
-one of its parents) is included in `allow-notify-for`_,
-`allow-notify-for-file`_, or `forward-zones-file`_ with a '^' prefix.
-
-.. _setting-allow-notify-from-file:
-
-``allow-notify-from-file``
---------------------------
-.. versionadded:: 4.6.0
-
-- Path
-
-Like `allow-notify-from`_, except reading from file. To use this
-feature, supply one netmask per line, with optional comments preceded
-by a "#".
-
-.. _setting-any-to-tcp:
-
-``any-to-tcp``
---------------
-- Boolean
-- Default: no
-
-Answer questions for the ANY type on UDP with a truncated packet that refers the remote server to TCP.
-Useful for mitigating ANY reflection attacks.
-
-.. _setting-allow-trust-anchor-query:
-
-``allow-trust-anchor-query``
-----------------------------
-.. versionadded:: 4.3.0
-
-- Boolean
-- Default: no
-
-Allow ``trustanchor.server CH TXT`` and ``negativetrustanchor.server CH TXT`` queries to view the configured :doc:`DNSSEC <dnssec>` (negative) trust anchors.
-
-.. _setting-api-config-dir:
-
-``api-config-dir``
-------------------
-.. versionadded:: 4.0.0
-
-- Path
-- Default: unset
-
-Directory where the REST API stores its configuration and zones.
-For configuration updates to work, :ref:`setting-include-dir` should have the same value.
-
-.. _setting-api-key:
-
-``api-key``
------------
-.. versionadded:: 4.0.0
-.. versionchanged:: 4.6.0
- This setting now accepts a hashed and salted version.
-
-- String
-- Default: unset
-
-Static pre-shared authentication key for access to the REST API. Since 4.6.0 the key can be hashed and salted using ``rec_control hash-password`` instead of being stored in the configuration in plaintext, but the plaintext version is still supported.
-
-.. _setting-api-readonly:
-
-``api-readonly``
-----------------
-.. versionchanged:: 4.2.0
- This setting has been removed.
-
-- Boolean
-- Default: no
-
-Disallow data modification through the REST API when set.
-
-.. _setting-api-logfile:
-
-``api-logfile``
----------------
-.. versionchanged:: 4.2.0
- This setting has been removed.
-
-- Path
-- Default: unset
-
-Location of the server logfile (used by the REST API).
-
-.. _setting-auth-zones:
-
-``auth-zones``
---------------
-- Comma separated list of 'zonename=filename' pairs
-
-Zones read from these files (in BIND format) are served authoritatively (but without the AA bit set in responses).
-DNSSEC is not supported. Example:
-
-.. code-block:: none
-
- auth-zones=example.org=/var/zones/example.org, powerdns.com=/var/zones/powerdns.com
-
-.. _setting-carbon-interval:
-
-``carbon-interval``
--------------------
-- Integer
-- Default: 30
-
-If sending carbon updates, this is the interval between them in seconds.
-See :doc:`metrics`.
-
-.. _setting-carbon-namespace:
-
-``carbon-namespace``
---------------------
-.. versionadded:: 4.2.0
-
-- String
-
-Change the namespace or first string of the metric key. The default is pdns.
-
-.. _setting-carbon-ourname:
-
-``carbon-ourname``
-------------------
-- String
-
-If sending carbon updates, if set, this will override our hostname.
-Be careful not to include any dots in this setting, unless you know what you are doing.
-See :ref:`metricscarbon`.
-
-.. _setting-carbon-instance:
-
-``carbon-instance``
---------------------
-.. versionadded:: 4.2.0
-
-- String
-
-Change the instance or third string of the metric key. The default is recursor.
-
-.. _setting-carbon-server:
-
-``carbon-server``
------------------
-- IP address
-
-If set to an IP or IPv6 address, will send all available metrics to this server via the carbon protocol, which is used by graphite and metronome. Moreover you can specify more than one server using a comma delimited list, ex: carbon-server=10.10.10.10,10.10.10.20.
-You may specify an alternate port by appending :port, for example: ``127.0.0.1:2004``.
-See :doc:`metrics`.
-
-.. _setting-chroot:
-
-``chroot``
-----------
-- Path to a Directory
-
-If set, chroot to this directory for more security.
-This is not recommended; instead, we recommend containing PowerDNS using operating system features.
-We ship systemd unit files with our packages to make this easy.
-
-Make sure that ``/dev/log`` is available from within the chroot.
-Logging will silently fail over time otherwise (on logrotate).
-
-When using ``chroot``, all other paths (except for `config-dir`_) set in the configuration are relative to the new root.
-
-When using ``chroot`` and the API (`webserver`_), `api-readonly`_ **must** be set and `api-config-dir`_ unset.
-
-When running on a system where systemd manages services, ``chroot`` does not work out of the box, as PowerDNS cannot use the ``NOTIFY_SOCKET``.
-Either do not ``chroot`` on these systems or set the 'Type' of this service to 'simple' instead of 'notify' (refer to the systemd documentation on how to modify unit-files).
-
-.. _setting-client-tcp-timeout:
-
-``client-tcp-timeout``
-----------------------
-- Integer
-- Default: 2
-
-Time to wait for data from TCP clients.
-
-.. _setting-config-dir:
-
-``config-dir``
---------------
-- Path
-
-Location of configuration directory (``recursor.conf``).
-Usually ``/etc/powerdns``, but this depends on ``SYSCONFDIR`` during compile-time.
-
-.. _setting-config-name:
-
-``config-name``
----------------
-- String
-- Default: unset
-
-When running multiple recursors on the same server, read settings from :file:`recursor-{name}.conf`, this will also rename the binary image.
-
-.. _setting-cpu-map:
-
-``cpu-map``
------------
-
-- String
-- Default: unset
-
-Set CPU affinity for threads, asking the scheduler to run those threads on a single CPU, or a set of CPUs.
-This parameter accepts a space separated list of thread-id=cpu-id, or thread-id=cpu-id-1,cpu-id-2,...,cpu-id-N.
-For example, to make the worker thread 0 run on CPU id 0 and the worker thread 1 on CPUs 1 and 2::
-
- cpu-map=0=0 1=1,2
-
-The thread handling the control channel, the webserver and other internal stuff has been assigned id 0, the distributor
-threads if any are assigned id 1 and counting, and the worker threads follow behind.
-The number of distributor threads is determined by :ref:`setting-distributor-threads`, the number of worker threads is determined by the :ref:`setting-threads` setting.
-
-This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function.
-
-Note that depending on the configuration the Recursor can start more threads.
-Typically these threads will sleep most of the time.
-These threads cannot be specified in this setting as their thread-ids are left unspecified.
-
-.. _setting-daemon:
-
-``daemon``
-----------
-- Boolean
-- Default: no
-
-.. versionchanged:: 4.0.0
-
- Default is now "no", was "yes" before.
-
-Operate in the background.
-
-.. _setting-dont-throttle-names:
-
-``dont-throttle-names``
-----------------------------
-.. versionadded:: 4.2.0
-
-- Comma separated list of domain-names
-- Default: (empty)
-
-When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
-Any servers' name suffix-matching the supplied names will never be throttled.
-
-.. warning::
- Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-names`` could make this load on the upstream server even higher, resulting in further service degradation.
-
-.. _setting-dont-throttle-netmasks:
-
-``dont-throttle-netmasks``
-----------------------------
-.. versionadded:: 4.2.0
-
-- Comma separated list of netmasks, negation not supported
-- Default: (empty)
-
-When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
-Any servers matching the supplied netmasks will never be throttled.
-
-This can come in handy on lossy networks when forwarding, where the same server is configured multiple times (e.g. with ``forward-zones-recurse=example.com=192.0.2.1;192.0.2.1``).
-By default, the PowerDNS Recursor would throttle the "first" server on a timeout and hence not retry the "second" one.
-In this case, ``dont-throttle-netmasks`` could be set to ``192.0.2.1``.
-
-.. warning::
- Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-netmasks`` could make this load on the upstream server even higher, resulting in further service degradation.
-
-.. _setting-disable-packetcache:
-
-``disable-packetcache``
------------------------
-- Boolean
-- Default: no
-
-Turn off the packet cache. Useful when running with Lua scripts that can not be cached, though individual query caching can be controlled from Lua as well.
-
-.. _setting-disable-syslog:
-
-``disable-syslog``
-------------------
-- Boolean
-- Default: no
-
-Do not log to syslog, only to stdout.
-Use this setting when running inside a supervisor that handles logging (like systemd).
-**Note**: do not use this setting in combination with `daemon`_ as all logging will disappear.
-
-.. _setting-distribution-load-factor:
-
-``distribution-load-factor``
-----------------------------
-.. versionadded:: 4.1.12
-
-- Double
-- Default: 0.0
-
-If `pdns-distributes-queries`_ is set and this setting is set to another value
-than 0, the distributor thread will use a bounded load-balancing algorithm while
-distributing queries to worker threads, making sure that no thread is assigned
-more queries than distribution-load-factor times the average number of queries
-currently processed by all the workers.
-For example, with a value of 1.25, no server should get more than 125 % of the
-average load. This helps making sure that all the workers have roughly the same
-share of queries, even if the incoming traffic is very skewed, with a larger
-number of requests asking for the same qname.
-
-.. _setting-distribution-pipe-buffer-size:
-
-``distribution-pipe-buffer-size``
----------------------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Default: 0
-
-Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread.
-Requires support for `F_SETPIPE_SZ` which is present in Linux since 2.6.35. The actual size might be rounded up to
-a multiple of a page size. 0 means that the OS default size is used.
-A large buffer might allow the recursor to deal with very short-lived load spikes during which a worker thread gets
-overloaded, but it will be at the cost of an increased latency.
-
-.. _setting-distributor-threads:
-
-``distributor-threads``
------------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Default: 1 if `pdns-distributes-queries`_ is set, 0 otherwise
-
-If `pdns-distributes-queries`_ is set, spawn this number of distributor threads on startup. Distributor threads
-handle incoming queries and distribute them to other threads based on a hash of the query.
-
-.. _setting-dot-to-auth-names:
-
-``dot-to-auth-names``
----------------------
-.. versionadded:: 4.6.0
-
-- Comma separated list of domain-names or suffixes
-- Default: (empty).
-
-Force DoT to the listed authoritative nameservers. For this to work, DoT support has to be compiled in.
-Currently, the certificate is not checked for validity in any way.
-
-.. _setting-dot-to-port-853:
-
-``dot-to-port-853``
--------------------
-.. versionadded:: 4.6.0
-
-- Boolean
-- Default: ``yes`` if DoT support is compiled in, ``no`` otherwise.
-
-Enable DoT to forwarders that specify port 853.
-
-.. _setting-dns64-prefix:
-
-``dns64-prefix``
-----------------
-.. versionadded:: 4.4.0
-
-- Netmask, as a string
-- Default: None
-
-Enable DNS64 (:rfc:`6147`) support using the supplied /96 IPv6 prefix. This will generate 'fake' ``AAAA`` records for names
-with only ``A`` records, as well as 'fake' ``PTR`` records to make sure that reverse lookup of DNS64-generated IPv6 addresses
-generate the right name.
-See :doc:`dns64` for more flexible but slower alternatives using Lua.
-
-.. _setting-dnssec:
-
-``dnssec``
-----------
-.. versionadded:: 4.0.0
-
-.. versionchanged:: 4.5.0
- The default changed from ``process-no-validate`` to ``process``
-
-- One of ``off``, ``process-no-validate``, ``process``, ``log-fail``, ``validate``, String
-- Default: ``process``
-
-Set the mode for DNSSEC processing, as detailed in :doc:`dnssec`.
-
-``off``
- No DNSSEC processing whatsoever.
- Ignore DO-bits in queries, don't request any DNSSEC information from authoritative servers.
- This behaviour is similar to PowerDNS Recursor pre-4.0.
-``process-no-validate``
- Respond with DNSSEC records to clients that ask for it, set the DO bit on all outgoing queries.
- Don't do any validation.
-``process``
- Respond with DNSSEC records to clients that ask for it, set the DO bit on all outgoing queries.
- Do validation for clients that request it (by means of the AD- bit or DO-bit in the query).
-``log-fail``
- Similar behaviour to ``process``, but validate RRSIGs on responses and log bogus responses.
-``validate``
- Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses.
-
-.. _setting-dnssec-log-bogus:
-
-``dnssec-log-bogus``
---------------------
-- Boolean
-- Default: no
-
-Log every DNSSEC validation failure.
-**Note**: This is not logged per-query but every time records are validated as Bogus.
-
-.. _setting-dont-query:
-
-``dont-query``
---------------
-- Netmasks, comma separated, negation supported
-- Default: 127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10, 0.0.0.0/8, 192.0.0.0/24, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 240.0.0.0/4, ::/96, ::ffff:0:0/96, 100::/64, 2001:db8::/32
-
-The DNS is a public database, but sometimes contains delegations to private IP addresses, like for example 127.0.0.1.
-This can have odd effects, depending on your network, and may even be a security risk.
-Therefore, the PowerDNS Recursor by default does not query private space IP addresses.
-This setting can be used to expand or reduce the limitations.
-
-Queries for names in forward zones and to addresses as configured in any of the settings `forward-zones`_, `forward-zones-file`_ or `forward-zones-recurse`_ are performed regardless of these limitations.
-
-.. _setting-ecs-add-for:
-
-``ecs-add-for``
----------------
-.. versionadded:: 4.2.0
-
-- Comma separated list of netmasks, negation supported
-- Default: 0.0.0.0/0, ::/0, !127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10
-
-List of requestor netmasks for which the requestor IP Address should be used as the :rfc:`EDNS Client Subnet <7871>` for outgoing queries. Outgoing queries for requestors that do not match this list will use the `ecs-scope-zero-address`_ instead.
-Valid incoming ECS values from `use-incoming-edns-subnet`_ are not replaced.
-
-Regardless of the value of this setting, ECS values are only sent for outgoing queries matching the conditions in the `edns-subnet-allow-list`_ setting. This setting only controls the actual value being sent.
-
-This defaults to not using the requestor address inside RFC1918 and similar "private" IP address spaces.
-
-.. _setting-ecs-ipv4-bits:
-
-``ecs-ipv4-bits``
------------------
-.. versionadded:: 4.1.0
-
-- Integer
-- Default: 24
-
-Number of bits of client IPv4 address to pass when sending EDNS Client Subnet address information.
-
-.. _setting-ecs-ipv4-cache-bits:
-
-``ecs-ipv4-cache-bits``
------------------------
-.. versionadded:: 4.1.12
-
-- Integer
-- Default: 24
-
-Maximum number of bits of client IPv4 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
-That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
-
-.. _setting-ecs-ipv6-bits:
-
-``ecs-ipv6-bits``
------------------
-.. versionadded:: 4.1.0
-
-- Integer
-- Default: 56
-
-Number of bits of client IPv6 address to pass when sending EDNS Client Subnet address information.
-
-.. _setting-ecs-ipv6-cache-bits:
-
-``ecs-ipv6-cache-bits``
------------------------
-.. versionadded:: 4.1.12
-
-- Integer
-- Default: 56
-
-Maximum number of bits of client IPv6 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
-That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
-
-.. _setting-ecs-ipv4-never-cache:
-
-``ecs-ipv4-never-cache``
-------------------------
-.. versionadded:: 4.5.0
-
-- Boolean
-- Default: no
-
-When set, never cache replies carrying EDNS IPv4 Client Subnet scope in the record cache.
-In this case the decision made by ```ecs-ipv4-cache-bits`` and ``ecs-cache-limit-ttl`` is no longer relevant.
-
-.. _setting-ecs-ipv6-never-cache:
-
-``ecs-ipv6-never-cache``
-------------------------
-.. versionadded:: 4.5.0
-
-- Boolean
-- Default: no
-
-When set, never cache replies carrying EDNS IPv6 Client Subnet scope in the record cache.
-In this case the decision made by ```ecs-ipv6-cache-bits`` and ``ecs-cache-limit-ttl`` is no longer relevant.
-
-.. _setting-ecs-minimum-ttl-override:
-
-``ecs-minimum-ttl-override``
-----------------------------
-.. versionchanged:: 4.5.0
- Old versions used default 0.
-
-- Integer
-- Default: 1
-
-This setting artificially raises the TTLs of records in the ANSWER section of ECS-specific answers to be at least this long.
-Setting this to a value greater than 1 technically is an RFC violation, but might improve performance a lot.
-Using a value of 0 impacts performance of TTL 0 records greatly, since it forces the recursor to contact
-authoritative servers every time a client requests them.
-Can be set at runtime using ``rec_control set-ecs-minimum-ttl 3600``.
-
-.. _setting-ecs-cache-limit-ttl:
-
-``ecs-cache-limit-ttl``
------------------------
-.. versionadded:: 4.1.12
-
-- Integer
-- Default: 0 (disabled)
-
-The minimum TTL for an ECS-specific answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-ipv4-cache-bits`` or ``ecs-ipv6-cache-bits``.
-That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
-
-.. _setting-ecs-scope-zero-address:
-
-``ecs-scope-zero-address``
---------------------------
-.. versionadded:: 4.1.0
-
-- IPv4 or IPv6 Address
-- Default: empty
-
-The IP address sent via EDNS Client Subnet to authoritative servers listed in
-`edns-subnet-allow-list`_ when `use-incoming-edns-subnet`_ is set and the query has
-an ECS source prefix-length set to 0.
-The default is to look for the first usable (not an ``any`` one) address in
-`query-local-address`_ (starting with IPv4). If no suitable address is
-found, the recursor fallbacks to sending 127.0.0.1.
-
-.. _setting-edns-outgoing-bufsize:
-
-``edns-outgoing-bufsize``
--------------------------
-.. versionchanged:: 4.2.0
- Before 4.2.0, the default was 1680
-
-- Integer
-- Default: 1232
-
-.. note:: Why 1232?
-
- 1232 is the largest number of payload bytes that can fit in the smallest IPv6 packet.
- IPv6 has a minimum MTU of 1280 bytes (:rfc:`RFC 8200, section 5 <8200#section-5>`), minus 40 bytes for the IPv6 header, minus 8 bytes for the UDP header gives 1232, the maximum payload size for the DNS response.
-
-This is the value set for the EDNS0 buffer size in outgoing packets.
-Lower this if you experience timeouts.
-
-.. _setting-edns-padding-from:
-
-``edns-padding-from``
----------------------
-.. versionadded:: 4.5.0
-
-- Comma separated list of netmasks, negation supported
-- Default: (none)
-
-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.
-
-.. _setting-edns-padding-mode:
-
-``edns-padding-mode``
----------------------
-.. versionadded:: 4.5.0
-
-- One of ``always``, ``padded-queries-only``, String
-- Default: ``padded-queries-only``
-
-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.
-
-.. _setting-edns-padding-out:
-
-``edns-padding-out``
---------------------
-.. versionadded:: 4.8.0
-
-- Boolean
-- Default: yes
-
-Whether to add EDNS padding to outgoing DoT queries.
-
-.. _setting-edns-padding-tag:
-
-``edns-padding-tag``
---------------------
-.. versionadded:: 4.5.0
-
-- Integer
-- Default: 7830
-
-The packetcache tag to use for padded responses, to prevent a client not allowed by the `edns-padding-from`_ list to be served a cached answer generated for an allowed one. This
-effectively divides the packet cache in two when `edns-padding-from`_ is used. Note that this will not override a tag set from one of the ``Lua`` hooks.
-
-.. _setting-edns-subnet-whitelist:
-
-``edns-subnet-whitelist``
--------------------------
-.. deprecated:: 4.5.0
- Use :ref:`setting-edns-subnet-allow-list`.
-
-.. _setting-edns-subnet-allow-list:
-
-``edns-subnet-allow-list``
---------------------------
-.. versionadded:: 4.5.0
-
-- Comma separated list of domain names and netmasks, negation supported
-- Default: (none)
-
-List of netmasks and domains that :rfc:`EDNS Client Subnet <7871>` should be enabled for in outgoing queries.
-
-For example, an EDNS Client Subnet option containing the address of the initial requestor (but see `ecs-add-for`_) will be added to an outgoing query sent to server 192.0.2.1 for domain X if 192.0.2.1 matches one of the supplied netmasks, or if X matches one of the supplied domains.
-The initial requestor address will be truncated to 24 bits for IPv4 (see `ecs-ipv4-bits`_) and to 56 bits for IPv6 (see `ecs-ipv6-bits`_), as recommended in the privacy section of RFC 7871.
-
-By default, this option is empty, meaning no EDNS Client Subnet information is sent.
-
-.. _setting-entropy-source:
-
-``entropy-source``
-------------------
-- Path
-- Default: /dev/urandom
-
-PowerDNS can read entropy from a (hardware) source.
-This is used for generating random numbers which are very hard to predict.
-Generally on UNIX platforms, this source will be ``/dev/urandom``, which will always supply random numbers, even if entropy is lacking.
-Change to ``/dev/random`` if PowerDNS should block waiting for enough entropy to arrive.
-
-.. _setting-etc-hosts-file:
-
-``etc-hosts-file``
-------------------
-- Path
-- Default: /etc/hosts
-
-The path to the /etc/hosts file, or equivalent.
-This file can be used to serve data authoritatively using `export-etc-hosts`_.
-
-.. _setting-event-trace-enabled:
-
-``event-trace-enabled``
------------------------
-.. versionadded:: 4.6.0
-
-- Integer
-- Default: 0
-
-Enable the recording and logging of ref:`event traces`. This is an experimental feature and subject to change.
-Possible values are 0: (disabled), 1 (add information to protobuf logging messages) and 2 (write to log) and 3 (both).
-
-.. _setting-export-etc-hosts:
-
-``export-etc-hosts``
---------------------
-- Boolean
-- Default: no
-
-If set, this flag will export the host names and IP addresses mentioned in ``/etc/hosts``.
-
-.. _setting-export-etc-hosts-search-suffix:
-
-``export-etc-hosts-search-suffix``
-----------------------------------
-- String
-
-If set, all hostnames in the `export-etc-hosts`_ file are loaded in canonical form, based on this suffix, unless the name contains a '.', in which case the name is unchanged.
-So an entry called 'pc' with ``export-etc-hosts-search-suffix='home.com'`` will lead to the generation of 'pc.home.com' within the recursor.
-An entry called 'server1.home' will be stored as 'server1.home', regardless of this setting.
-
-.. _setting-extended-resolution-errors:
-
-``extended-resolution-errors``
-------------------------------
-.. versionadded:: 4.5.0
-
-- Boolean
-- Default: no
-
-If set, the recursor will add an EDNS Extended Error (:rfc:`8914`) to responses when resolution failed, like DNSSEC validation errors, explaining the reason it failed. This setting is not needed to allow setting custom error codes from Lua or from a RPZ hit.
-
-.. _setting-forward-zones:
-
-``forward-zones``
------------------
-- 'zonename=IP' pairs, comma separated
-
-Queries for zones listed here will be forwarded to the IP address listed. i.e.
-
-.. code-block:: none
-
- forward-zones=example.org=203.0.113.210, powerdns.com=2001:DB8::BEEF:5
-
-Multiple IP addresses can be specified and port numbers other than 53 can be configured:
-
-.. code-block:: none
-
- forward-zones=example.org=203.0.113.210:5300;127.0.0.1, powerdns.com=127.0.0.1;198.51.100.10:530;[2001:DB8::1:3]:5300
-
-Forwarded queries have the ``recursion desired (RD)`` bit set to ``0``, meaning that this setting is intended to forward queries to authoritative servers.
-If an ``NS`` record set for a subzone of the forwarded zone is learned, that record set will be used to determine addresses for name servers of the subzone.
-This allows e.g. a forward to a local authoritative server holding a copy of the root zone, delegations received from that server will work.
-
-**IMPORTANT**: When using DNSSEC validation (which is default), forwards to non-delegated (e.g. internal) zones that have a DNSSEC signed parent zone will validate as Bogus.
-To prevent this, add a Negative Trust Anchor (NTA) for this zone in the `lua-config-file`_ with ``addNTA("your.zone", "A comment")``.
-If this forwarded zone is signed, instead of adding NTA, add the DS record to the `lua-config-file`_.
-See the :doc:`dnssec` information.
-
-.. _setting-forward-zones-file:
-
-``forward-zones-file``
-----------------------
-- Path
-
-Same as `forward-zones`_, parsed from a file. Only 1 zone is allowed per line, specified as follows:
-
-.. code-block:: none
-
- example.org=203.0.113.210, 192.0.2.4:5300
-
-Zones prefixed with a '+' are treated as with
-`forward-zones-recurse`_. Default behaviour without '+' is as with
-`forward-zones`_.
-
-.. versionchanged:: 4.0.0
-
- Comments are allowed, everything behind '#' is ignored.
-
-The DNSSEC notes from `forward-zones`_ apply here as well.
-
-.. versionchanged:: 4.6.0
-
-Zones prefixed with a '^' are added to the `allow-notify-for`_
-list. Both prefix characters can be used if desired, in any order.
-
-.. _setting-forward-zones-recurse:
-
-``forward-zones-recurse``
--------------------------
-- 'zonename=IP' pairs, comma separated
-
-Like regular `forward-zones`_, but forwarded queries have the ``recursion desired (RD)`` bit set to ``1``, meaning that this setting is intended to forward queries to other recursive servers.
-In contrast to regular forwarding, the rule that delegations of the forwarded subzones are respected is not active.
-This is because we rely on the forwarder to resolve the query fully.
-
-See `forward-zones`_ for additional options (such as supplying multiple recursive servers) and an important note about DNSSEC.
-
-.. _setting-gettag-needs-edns-options:
-
-``gettag-needs-edns-options``
------------------------------
-.. versionadded:: 4.1.0
-
-- Boolean
-- Default: no
-
-If set, EDNS options in incoming queries are extracted and passed to the :func:`gettag` hook in the ``ednsoptions`` table.
-
-.. _setting-hint-file:
-
-``hint-file``
--------------
-- Path
-- Default: empty
-
-.. versionchanged:: 4.6.2
-
- Introduced the value ``no`` to disable root-hints processing.
-
-If set, the root-hints are read from this file. If empty, the default built-in root hints are used.
-
-In some special cases, processing the root hints is not needed, for example when forwarding all queries to another recursor.
-For these special cases, it is possible to disable the processing of root hints by setting the value to ``no``.
-See :ref:`handling-of-root-hints` for more information on root hints handling.
-
-.. _setting-ignore-unknown-settings:
-
-``ignore-unknown-settings``
----------------------------
-
-.. versionadded:: 4.6.0
-
-- Setting names, separated by commas
-- Default: empty
-
-Names of settings to be ignored while parsing configuration files, if the setting
-name is unknown to PowerDNS.
-
-Useful during upgrade testing.
-
-.. _setting-include-dir:
-
-``include-dir``
----------------
-- Path
-
-Directory to scan for additional config files. All files that end with .conf are loaded in order using ``POSIX`` as locale.
-
-.. _setting-latency-statistic-size:
-
-``latency-statistic-size``
---------------------------
-- Integer
-- Default: 10000
-
-Indication of how many queries will be averaged to get the average latency reported by the 'qa-latency' metric.
-
-.. _setting-local-address:
-
-``local-address``
------------------
-- IPv4/IPv6 Addresses, with optional port numbers, separated by commas or whitespace
-- Default: ``127.0.0.1``
-
-Local IP addresses to which we bind. Each address specified can
-include a port number; if no port is included then the
-:ref:`setting-local-port` port will be used for that address. If a
-port number is specified, it must be separated from the address with a
-':'; for an IPv6 address the address must be enclosed in square
-brackets.
-
-Examples::
-
- local-address=127.0.0.1 ::1
- local-address=0.0.0.0:5353
- local-address=[::]:8053
- local-address=127.0.0.1:53, [::1]:5353
-
-.. _setting-local-port:
-
-``local-port``
---------------
-- Integer
-- Default: 53
-
-Local port to bind to.
-If an address in `local-address`_ does not have an explicit port, this port is used.
-
-.. _setting-log-timestamp:
-
-``log-timestamp``
------------------
-
-.. versionadded:: 4.1.0
-
-- Bool
-- Default: yes
-
-When printing log lines to stdout, prefix them with timestamps.
-Disable this if the process supervisor timestamps these lines already.
-
-.. note::
- The systemd unit file supplied with the source code already disables timestamp printing
-
-.. _setting-non-local-bind:
-
-``non-local-bind``
-------------------
-- Boolean
-- Default: no
-
-Bind to addresses even if one or more of the `local-address`_'s do not exist on this server.
-Setting this option will enable the needed socket options to allow binding to non-local addresses.
-This feature is intended to facilitate ip-failover setups, but it may also mask configuration issues and for this reason it is disabled by default.
-
-.. _setting-loglevel:
-
-``loglevel``
-------------
-- Integer between 0 and 9
-- Default: 6
-
-Amount of logging. The higher the number, the more lines logged.
-Corresponds to "syslog" level values (e.g. 0 = emergency, 1 = alert, 2 = critical, 3 = error, 4 = warning, 5 = notice, 6 = info, 7 = debug).
-Each level includes itself plus the lower levels before it.
-Not recommended to set this below 3.
-
-.. _setting-log-common-errors:
-
-``log-common-errors``
----------------------
-- Boolean
-- Default: no
-
-Some DNS errors occur rather frequently and are no cause for alarm.
-
-``log-rpz-changes``
--------------------
-.. versionadded:: 4.1.0
-
-- Boolean
-- Default: no
-
-Log additions and removals to RPZ zones at Info (6) level instead of Debug (7).
-
-.. _setting-logging-facility:
-
-``logging-facility``
---------------------
-- Integer
-
-If set to a digit, logging is performed under this LOCAL facility.
-See :ref:`logging`.
-Do not pass names like 'local0'!
-
-.. _setting-lowercase-outgoing:
-
-``lowercase-outgoing``
-----------------------
-- Boolean
-- Default: no
-
-Set to true to lowercase the outgoing queries.
-When set to 'no' (the default) a query from a client using mixed case in the DNS labels (such as a user entering mixed-case names or `draft-vixie-dnsext-dns0x20-00 <http://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00>`_), PowerDNS preserves the case of the query.
-Broken authoritative servers might give a wrong or broken answer on this encoding.
-Setting ``lowercase-outgoing`` to 'yes' makes the PowerDNS Recursor lowercase all the labels in the query to the authoritative servers, but still return the proper case to the client requesting.
-
-.. _setting-lua-config-file:
-
-``lua-config-file``
--------------------
-- Filename
-
-If set, and Lua support is compiled in, this will load an additional configuration file for newer features and more complicated setups.
-See :doc:`lua-config/index` for the options that can be set in this file.
-
-.. _setting-lua-dns-script:
-
-``lua-dns-script``
-------------------
-- Path
-- Default: unset
-
-Path to a lua file to manipulate the Recursor's answers. See :doc:`lua-scripting/index` for more information.
-
-.. _setting-maintenance-interval:
-
-``lua-maintenance-interval``
-----------------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Default: 1
-
-
-The interval between calls to the Lua user defined `maintenance()` function in seconds.
-See :ref:`hooks-maintenance-callback`
-
-.. _setting-max-busy-dot-probes:
-
-``max-busy-dot-probes``
------------------------
-.. versionadded:: 4.7.0
-
-- Integer
-- Default: 0
-
-Limit the maximum number of simultaneous DoT probes the Recursor will schedule.
-The default value 0 means no DoT probes are scheduled.
-
-DoT probes are used to check if an authoritative server's IP address supports DoT.
-If the probe determines an IP address supports DoT, the Recursor will use DoT to contact it for subsequent queries until a failure occurs.
-After a failure, the Recursor will stop using DoT for that specific IP address for a while.
-The results of probes are remembered and can be viewed by the ``rec_control dump-dot-probe-map`` command.
-If the maximum number of pending probes is reached, no probes will be scheduled, even if no DoT status is known for an address.
-If the result of a probe is not yet available, the Recursor will contact the authoritative server in the regular way, unless an authoritative server is configured to be contacted over DoT always using :ref:`setting-dot-to-auth-names`.
-In that case no probe will be scheduled.
-
-
-.. note::
- DoT probing is an experimental feature.
- Please test thoroughly to determine if it is suitable in your specific production environment before enabling.
-
-.. _setting-max-cache-bogus-ttl:
-
-``max-cache-bogus-ttl``
------------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Default: 3600
-
-Maximum number of seconds to cache an item in the DNS cache (negative or positive) if its DNSSEC validation failed, no matter what the original TTL specified, to reduce the impact of a broken domain.
-
-.. _setting-max-cache-entries:
-
-``max-cache-entries``
----------------------
-- Integer
-- Default: 1000000
-
-Maximum number of DNS record cache entries, shared by all threads since 4.4.0.
-Each entry associates a name and type with a record set.
-The size of the negative cache is 10% of this number.
-
-.. _setting-max-cache-ttl:
-
-``max-cache-ttl``
------------------
-- Integer
-- Default: 86400
-
-Maximum number of seconds to cache an item in the DNS cache, no matter what the original TTL specified.
-This value also controls the refresh period of cached root data.
-See :ref:`handling-of-root-hints` for more information on this.
-
-.. versionchanged:: 4.1.0
-
- The minimum value of this setting is 15. i.e. setting this to lower than 15 will make this value 15.
-
-.. _setting-max-concurrent-requests-per-tcp-connection:
-
-``max-concurrent-requests-per-tcp-connection``
-----------------------------------------------
-
-.. versionadded:: 4.3.0
-
-- Integer
-- Default: 10
-
-Maximum number of incoming requests handled concurrently per tcp
-connection. This number must be larger than 0 and smaller than 65536
-and also smaller than `max-mthreads`.
-
-.. _setting-max-include-depth:
-
-``max-include-depth``
-----------------------
-
-.. versionadded:: 4.6.0
-
-- Integer
-- Default: 20
-
-Maximum number of nested ``$INCLUDE`` directives while processing a zone file.
-Zero mean no ``$INCLUDE`` directives will be accepted.
-
-.. _setting-max-generate-steps:
-
-``max-generate-steps``
-----------------------
-
-.. versionadded:: 4.3.0
-
-- Integer
-- Default: 0
-
-Maximum number of steps for a '$GENERATE' directive when parsing a
-zone file. This is a protection measure to prevent consuming a lot of
-CPU and memory when untrusted zones are loaded. Default to 0 which
-means unlimited.
-
-.. _setting-max-mthreads:
-
-``max-mthreads``
-----------------
-- Integer
-- Default: 2048
-
-Maximum number of simultaneous MTasker threads.
-
-.. _setting-max-packetcache-entries:
-
-``max-packetcache-entries``
----------------------------
-- Integer
-- Default: 500000
-
-Maximum number of Packet Cache entries. Each worker and each distributor thread has a packet cache instance.
-This number will be divided by the number of worker plus the number of distributor threads to compute the maximum number of entries per cache instance.
-
-.. _setting-max-qperq:
-
-``max-qperq``
--------------
-- Integer
-- Default: 60
-
-The maximum number of outgoing queries that will be sent out during the resolution of a single client query.
-This is used to limit endlessly chasing CNAME redirections.
-If qname-minimization is enabled, the number will be forced to be 100
-at a minimum to allow for the extra queries qname-minimization generates when the cache is empty.
-
-.. _setting-max-ns-address-qperq:
-
-``max-ns-address-qperq``
-------------------------
-.. versionadded:: 4.1.16
-.. versionadded:: 4.2.2
-.. versionadded:: 4.3.1
-
-- Integer
-- Default: 10
-
-The maximum number of outgoing queries with empty replies for
-resolving nameserver names to addresses we allow during the resolution
-of a single client query. If IPv6 is enabled, an A and a AAAA query
-for a name counts as 1. If a zone publishes more than this number of
-NS records, the limit is further reduced for that zone by lowering
-it by the number of NS records found above the
-`max-ns-address-qperq`_ value. The limit wil not be reduced to a
-number lower than 5.
-
-.. _setting-max-ns-per-resolve:
-
-``max-ns-per-resolve``
-----------------------
-.. versionadded:: 4.8.0
-.. versionadded:: 4.7.3
-.. versionadded:: 4.6.4
-.. versionadded:: 4.5.11
-
-- Integer
-- Default: 13
-
-The maximum number of NS records that will be considered to select a nameserver to contact to resolve a name.
-If a zone has more than `max-ns-per-resolve`_ NS records, a random sample of this size will be used.
-If `max-ns-per-resolve`_ is zero, no limit applies.
-
-.. _setting-max-negative-ttl:
-
-``max-negative-ttl``
---------------------
-- Integer
-- Default: 3600
-
-A query for which there is authoritatively no answer is cached to quickly deny a record's existence later on, without putting a heavy load on the remote server.
-In practice, caches can become saturated with hundreds of thousands of hosts which are tried only once.
-This setting, which defaults to 3600 seconds, puts a maximum on the amount of time negative entries are cached.
-
-.. _setting-max-recursion-depth:
-
-``max-recursion-depth``
------------------------
-- Integer
-- Default: 40
-
-Total maximum number of internal recursion calls the server may use to answer a single query.
-0 means unlimited.
-The value of `stack-size`_ should be increased together with this one to prevent the stack from overflowing.
-If `qname-minimization`_ is enabled, the fallback code in case of a failing resolve is allowed an additional `max-recursion-depth/2`.
-
-
-.. versionchanged:: 4.1.0
-
- Before 4.1.0, this settings was unlimited.
-
-.. _setting-max-tcp-clients:
-
-``max-tcp-clients``
--------------------
-- Integer
-- Default: 128
-
-Maximum number of simultaneous incoming TCP connections allowed.
-
-.. _setting-max-tcp-per-client:
-
-``max-tcp-per-client``
-----------------------
-- Integer
-- Default: 0 (unlimited)
-
-Maximum number of simultaneous incoming TCP connections allowed per client (remote IP address).
-
-.. _setting-max-tcp-queries-per-connection:
-
-``max-tcp-queries-per-connection``
-----------------------------------
-.. versionadded:: 4.1.0
-
-- Integer
-- Default: 0 (unlimited)
-
-Maximum number of DNS queries in a TCP connection.
-
-.. _setting-max-total-msec:
-
-``max-total-msec``
-------------------
-- Integer
-- Default: 7000
-
-Total maximum number of milliseconds of wallclock time the server may use to answer a single query.
-
-.. _setting-max-udp-queries-per-round:
-
-``max-udp-queries-per-round``
-----------------------------------
-.. versionadded:: 4.1.4
-
-- Integer
-- Default: 10000
-
-Under heavy load the recursor might be busy processing incoming UDP queries for a long while before there is no more of these, and might therefore
-neglect scheduling new ``mthreads``, handling responses from authoritative servers or responding to :doc:`rec_control <manpages/rec_control.1>`
-requests.
-This setting caps the maximum number of incoming UDP DNS queries processed in a single round of looping on ``recvmsg()`` after being woken up by the multiplexer, before
-returning back to normal processing and handling other events.
-
-.. _setting-minimum-ttl-override:
-
-``minimum-ttl-override``
-------------------------
-.. versionchanged:: 4.5.0
- Old versions used default 0.
-
-- Integer
-- Default: 1
-
-This setting artificially raises all TTLs to be at least this long.
-Setting this to a value greater than 1 technically is an RFC violation, but might improve performance a lot.
-Using a value of 0 impacts performance of TTL 0 records greatly, since it forces the recursor to contact
-authoritative servers each time a client requests them.
-Can be set at runtime using ``rec_control set-minimum-ttl 3600``.
-
-.. _setting-new-domain-tracking:
-
-``new-domain-tracking``
------------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: no (disabled)
-
-Whether to track newly observed domains, i.e. never seen before. This
-is a probabilistic algorithm, using a stable bloom filter to store
-records of previously seen domains. When enabled for the first time,
-all domains will appear to be newly observed, so the feature is best
-left enabled for e.g. a week or longer before using the results. Note
-that this feature is optional and must be enabled at compile-time,
-thus it may not be available in all pre-built packages.
-If protobuf is enabled and configured, then the newly observed domain
-status will appear as a flag in Response messages.
-
-.. _setting-new-domain-log:
-
-``new-domain-log``
-------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: yes (enabled)
-
-If a newly observed domain is detected, log that domain in the
-recursor log file. The log line looks something like::
-
- Jul 18 11:31:25 Newly observed domain nod=sdfoijdfio.com
-
-.. _setting-new-domain-lookup:
-
-``new-domain-lookup``
----------------------
-.. versionadded:: 4.2.0
-
-- Domain Name
-- Example: nod.powerdns.com
-
-If a domain is specified, then each time a newly observed domain is
-detected, the recursor will perform an A record lookup of "<newly
-observed domain>.<lookup domain>". For example if 'new-domain-lookup'
-is configured as 'nod.powerdns.com', and a new domain 'xyz123.tv' is
-detected, then an A record lookup will be made for
-'xyz123.tv.nod.powerdns.com'. This feature gives a way to share the
-newly observed domain with partners, vendors or security teams. The
-result of the DNS lookup will be ignored by the recursor.
-
-.. _setting-new-domain-db-size:
-
-``new-domain-db-size``
-----------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Example: 67108864
-
-The default size of the stable bloom filter used to store previously
-observed domains is 67108864. To change the number of cells, use this
-setting. For each cell, the SBF uses 1 bit of memory, and one byte of
-disk for the persistent file.
-If there are already persistent files saved to disk, this setting will
-have no effect unless you remove the existing files.
-
-.. _setting-new-domain-history-dir:
-
-``new-domain-history-dir``
---------------------------
-.. versionadded:: 4.2.0
-
-- Path
-
-This setting controls which directory is used to store the on-disk
-cache of previously observed domains.
-
-The default depends on ``LOCALSTATEDIR`` when building the software.
-Usually this comes down to ``/var/lib/pdns-recursor/nod`` or ``/usr/local/var/lib/pdns-recursor/nod``).
-
-The newly observed domain feature uses a stable bloom filter to store
-a history of previously observed domains. The data structure is
-synchronized to disk every 10 minutes, and is also initialized from
-disk on startup. This ensures that previously observed domains are
-preserved across recursor restarts.
-If you change the new-domain-db-size setting, you must remove any files
-from this directory.
-
-.. _setting-new-domain-whitelist:
-
-``new-domain-whitelist``
-------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
- Use :ref:`setting-new-domain-ignore-list`.
-
-.. _setting-new-domain-ignore-list:
-
-``new-domain-ignore-list``
---------------------------
-.. versionadded:: 4.5.0
-
-- List of Domain Names, comma separated
-- Example: xyz.com, abc.com
-
-This setting is a list of all domains (and implicitly all subdomains)
-that will never be considered a new domain. For example, if the domain
-'xyz123.tv' is in the list, then 'foo.bar.xyz123.tv' will never be
-considered a new domain. One use-case for the ignore list is to never
-reveal details of internal subdomains via the new-domain-lookup
-feature.
-
-.. _setting-new-domain-pb-tag:
-
-``new-domain-pb-tag``
----------------------
-.. versionadded:: 4.2.0
-
-- String
-- Default: pnds-nod
-
-If protobuf is configured, then this tag will be added to all protobuf response messages when
-a new domain is observed.
-
-.. _setting-network-timeout:
-
-``network-timeout``
--------------------
-- Integer
-- Default: 1500
-
-Number of milliseconds to wait for a remote authoritative server to respond.
-
-.. _setting-non-resolving-ns-max-fails:
-
-``non-resolving-ns-max-fails``
-------------------------------
-.. versionadded:: 4.5.0
-
-- Integer
-- Default: 5
-
-Number of failed address resolves of a nameserver name to start throttling it, 0 is disabled.
-Nameservers matching :ref:`setting-dont-throttle-names` will not be throttled.
-
-
-.. _setting-non-resolving-ns-throttle-time:
-
-``non-resolving-ns-max-throttle-time``
---------------------------------------
-.. versionadded:: 4.5.0
-
-- Integer
-- Default: 60
-
-Number of seconds to throttle a nameserver with a name failing to resolve.
-
-.. _setting-nothing-below-nxdomain:
-
-``nothing-below-nxdomain``
---------------------------
-.. versionadded:: 4.3.0
-
-- One of ``no``, ``dnssec``, ``yes``, String
-- Default: ``dnssec``
-
-The type of :rfc:`8020` handling using cached NXDOMAIN responses.
-This RFC specifies that NXDOMAIN means that the DNS tree under the denied name MUST be empty.
-When an NXDOMAIN exists in the cache for a shorter name than the qname, no lookup is done and an NXDOMAIN is sent to the client.
-
-For instance, when ``foo.example.net`` is negatively cached, any query
-matching ``*.foo.example.net`` will be answered with NXDOMAIN directly
-without consulting authoritative servers.
-
-``no``
- No :rfc:`8020` processing is done.
-
-``dnssec``
- :rfc:`8020` processing is only done using cached NXDOMAIN records that are
- DNSSEC validated.
-
-``yes``
- :rfc:`8020` processing is done using any non-Bogus NXDOMAIN record
- available in the cache.
-
-.. _setting-nsec3-max-iterations:
-
-``nsec3-max-iterations``
-------------------------
-.. versionadded:: 4.1.0
-
-- Integer
-- Default: 150
-
-Maximum number of iterations allowed for an NSEC3 record.
-If an answer containing an NSEC3 record with more iterations is received, its DNSSEC validation status is treated as Insecure.
-
-.. versionchanged:: 4.5.2
-
- Default is now 150, was 2500 before.
-
-.. _setting-packetcache-ttl:
-
-``packetcache-ttl``
--------------------
-- Integer
-- Default: 86400
-
-Maximum number of seconds to cache an item in the packet cache, no matter what the original TTL specified.
-
-.. versionchanged:: 4.9.0
-
- The default was changed from 3600 (1 hour) to 86400 (24 hours).
-
-.. _setting-packetcache-negative-ttl:
-
-``packetcache-negative-ttl``
-----------------------------
-.. versionadded:: 4.9.0
-
-- Integer
-- Default: 60
-
-Maximum number of seconds to cache an ``NxDomain`` or ``NoData`` answer in the packetcache.
-This setting's maximum is capped to `packetcache-ttl`_.
-i.e. setting ``packetcache-ttl=15`` and keeping ``packetcache-negative-ttl`` at the default will lower ``packetcache-negative-ttl`` to ``15``.
-
-.. _setting-packetcache-servfail-ttl:
-
-``packetcache-servfail-ttl``
-----------------------------
-- Integer
-- Default: 60
-
-Maximum number of seconds to cache an answer indicating a failure to resolve in the packet cache.
-Before version 4.6.0 only ``ServFail`` answers were considered as such. Starting with 4.6.0, all responses with a code other than ``NoError`` and ``NXDomain``, or without records in the answer and authority sections, are considered as a failure to resolve.
-Since 4.9.0, negative answers are handled separately from resolving failures.
-
-.. versionchanged:: 4.0.0
-
- This setting's maximum is capped to `packetcache-ttl`_.
- i.e. setting ``packetcache-ttl=15`` and keeping ``packetcache-servfail-ttl`` at the default will lower ``packetcache-servfail-ttl`` to ``15``.
-
-
-.. _setting-packetcache-shards:
-
-``packetcache-shards``
-------------------------
-.. versionadded:: 4.9.0
-
-- Integer
-- Default: 1024
-
-Sets the number of shards in the packet cache. If you have high contention as reported by ``packetcache-contented/packetcache-acquired``,
-you can try to enlarge this value or run with fewer threads.
-
-.. _setting-pdns-distributes-queries:
-
-``pdns-distributes-queries``
-----------------------------
-- Boolean
-- Default: no
-
-If set, PowerDNS will use distinct threads to listen to client sockets and distribute that work to worker-threads using a hash of the query.
-This feature should maximize the cache hit ratio on versions before 4.9.0.
-To use more than one thread set `distributor-threads`_ in version 4.2.0 or newer.
-Enabling should improve performance on systems where `reuseport`_ does not have the effect of
-balancing the queries evenly over multiple worker threads.
-
-.. versionchanged:: 4.9.0
-
- Default changed to ``no``, previously it was ``yes``.
-
-.. _setting-protobuf-use-kernel-timestamp:
-
-``protobuf-use-kernel-timestamp``
----------------------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: false
-
-Whether to compute the latency of responses in protobuf messages using the timestamp set by the kernel when the query packet was received (when available), instead of computing it based on the moment we start processing the query.
-
-.. _setting-proxy-protocol-from:
-
-``proxy-protocol-from``
------------------------
-.. versionadded:: 4.4.0
-
-- IP addresses or netmasks, separated by commas, negation supported
-- Default: empty
-
-Ranges that are required to send a Proxy Protocol version 2 header in front of UDP and TCP queries, to pass the original source and destination addresses and ports to the recursor, as well as custom values.
-Queries that are not prefixed with such a header will not be accepted from clients in these ranges. Queries prefixed by headers from clients that are not listed in these ranges will be dropped.
-
-Note that once a Proxy Protocol header has been received, the source address from the proxy header instead of the address of the proxy will be checked against the `allow-from`_ ACL.
-
-The dnsdist docs have `more information about the PROXY protocol <https://dnsdist.org/advanced/passing-source-address.html#proxy-protocol>`_.
-
-.. _setting-proxy-protocol-maximum-size:
-
-``proxy-protocol-maximum-size``
--------------------------------
-.. versionadded:: 4.4.0
-
-- Integer
-- Default: 512
-
-The maximum size, in bytes, of a Proxy Protocol payload (header, addresses and ports, and TLV values). Queries with a larger payload will be dropped.
-
-.. _setting-public-suffix-list-file:
-
-``public-suffix-list-file``
----------------------------
-.. versionadded:: 4.2.0
-
-- Path
-- Default: unset
-
-Path to the Public Suffix List file, if any. If set, PowerDNS will try to load the Public Suffix List from this file instead of using the built-in list. The PSL is used to group the queries by relevant domain names when displaying the top queries.
-
-.. _setting-qname-minimization:
-
-``qname-minimization``
-----------------------
-.. versionadded:: 4.3.0
-
-- Boolean
-- Default: yes
-
-Enable Query Name Minimization. This implements a relaxed form of Query Name Mimimization as
-described in :rfc:`7816`.
-
-.. _setting-query-local-address:
-
-``query-local-address``
------------------------
-.. versionchanged:: 4.4.0
- IPv6 addresses can be set with this option as well.
-
-- IP addresses, comma separated
-- Default: 0.0.0.0
-
-Send out local queries from this address, or addresses. By adding multiple
-addresses, increased spoofing resilience is achieved. When no address of a certain
-address family is configured, there are *no* queries sent with that address family.
-In the default configuration this means that IPv6 is not used for outgoing queries.
-
-.. _setting-query-local-address6:
-
-``query-local-address6``
-------------------------
-.. deprecated:: 4.4.0
- Use :ref:`setting-query-local-address` for IPv4 and IPv6.
-
-.. deprecated:: 4.5.0
- Removed, use :ref:`setting-query-local-address`.
-
-- IPv6 addresses, comma separated
-- Default: unset
-
-Send out local IPv6 queries from this address or addresses.
-Disabled by default, which also disables outgoing IPv6 support.
-
-.. _setting-quiet:
-
-``quiet``
----------
-- Boolean
-- Default: yes
-
-Don't log queries.
-
-.. _setting-record-cache-locked-ttl-perc:
-
-``record-cache-locked-ttl-perc``
---------------------------------
-.. versionadded:: 4.8.0
-
-- Integer
-- Default: 0
-
-Replace record sets in the record cache only after this percentage of the original TTL has passed.
-The PowerDNS Recursor already has several mechanisms to protect against spoofing attempts.
-This adds an extra layer of protection---as it limits the window of time cache updates are accepted---at the cost of a less efficient record cache.
-
-The default value of 0 means no extra locking occurs.
-When non-zero, record sets received (e.g. in the Additional Section) will not replace existing record sets in the record cache until the given percentage of the original TTL has expired.
-A value of 100 means only expired record sets will be replaced.
-
-There are a few cases where records will be replaced anyway:
-
-- Record sets that are expired will always be replaced.
-- Authoritative record sets will replace unauthoritative record sets unless DNSSEC validation of the new record set failed.
-- If the new record set belongs to a DNSSEC-secure zone and successfully passed validation it will replace an existing entry.
-- Record sets produced by :ref:`setting-refresh-on-ttl-perc` tasks will also replace existing record sets.
-
-.. _setting-record-cache-shards:
-
-``record-cache-shards``
-------------------------
-.. versionadded:: 4.4.0
-
-- Integer
-- Default: 1024
-
-Sets the number of shards in the record cache. If you have high
-contention as reported by
-``record-cache-contented/record-cache-acquired``, you can try to
-enlarge this value or run with fewer threads.
-
-.. _setting-refresh-on-ttl-perc:
-
-``refresh-on-ttl-perc``
------------------------
-.. versionadded:: 4.5.0
-
-- Integer
-- Default: 0
-
-Sets the "refresh almost expired" percentage of the record cache. Whenever a record is fetched from the packet or record cache
-and only ``refresh-on-ttl-perc`` percent or less of its original TTL is left, a task is queued to refetch the name/type combination to
-update the record cache. In most cases this causes future queries to always see a non-expired record cache entry.
-A typical value is 10. If the value is zero, this functionality is disabled.
-
-.. _setting-reuseport:
-
-``reuseport``
--------------
-- Boolean
-- Default: yes
-
-If ``SO_REUSEPORT`` support is available, allows multiple threads and processes to open listening sockets for the same port.
-
-Since 4.1.0, when `pdns-distributes-queries`_ is disabled and `reuseport`_ is enabled, every worker-thread will open a separate listening socket to let the kernel distribute the incoming queries instead of running a distributor thread (which could otherwise be a bottleneck) and avoiding thundering herd issues, thus leading to much higher performance on multi-core boxes.
-
-.. versionchanged:: 4.9.0
-
- The default is changed to ``yes``, previously it was ``no``.
- If ``SO_REUSEPORT`` support is not available, the setting defaults to ``no``.
-
-.. _setting-rng:
-
-``rng``
--------
-
-- String
-- Default: auto
-
-Specify which random number generator to use. Permissible choices are
- - auto - choose automatically
- - sodium - Use libsodium ``randombytes_uniform``
- - openssl - Use libcrypto ``RAND_bytes``
- - getrandom - Use libc getrandom, falls back to urandom if it does not really work
- - arc4random - Use BSD ``arc4random_uniform``
- - urandom - Use ``/dev/urandom``
- - kiss - Use simple settable deterministic RNG. **FOR TESTING PURPOSES ONLY!**
-
-.. note::
- Not all choices are available on all systems.
-
-.. _setting-root-nx-trust:
-
-``root-nx-trust``
------------------
-- Boolean
-- Default: yes
-
-If set, an NXDOMAIN from the root-servers will serve as a blanket NXDOMAIN for the entire TLD the query belonged to.
-The effect of this is far fewer queries to the root-servers.
-
-.. versionchanged:: 4.0.0
-
- Default is 'yes' now, was 'no' before 4.0.0
-
-.. _setting-save-parent-ns-set:
-
-``save-parent-ns-set``
-----------------------
-.. versionadded:: 4.7.0
-
-- Boolean
-- Default: yes
-
-If set, a parent (non-authoritative) ``NS`` set is saved if it contains more entries than a newly encountered child (authoritative) ``NS`` set for the same domain.
-The saved parent ``NS`` set is tried if resolution using the child ``NS`` set fails.
-
-.. _setting-security-poll-suffix:
-
-``security-poll-suffix``
-------------------------
-- String
-- Default: secpoll.powerdns.com.
-
-Domain name from which to query security update notifications.
-Setting this to an empty string disables secpoll.
-
-.. _setting-serve-rfc1918:
-
-``serve-rfc1918``
------------------
-- Boolean
-- Default: yes
-
-This makes the server authoritatively aware of: ``10.in-addr.arpa``, ``168.192.in-addr.arpa``, ``16-31.172.in-addr.arpa``, which saves load on the AS112 servers.
-Individual parts of these zones can still be loaded or forwarded.
-
-.. _setting-serve-stale-extensions:
-
-``serve-stale-extensions``
---------------------------
-.. versionadded:: 4.8.0
-
-- Integer
-- Default: 0
-
-Maximum number of times an expired record's TTL is extended by 30s when serving stale.
-Extension only occurs if a record cannot be refreshed.
-A value of 0 means the ``Serve Stale`` mechanism is not used.
-To allow records becoming stale to be served for an hour, use a value of 120.
-See :ref:`serve-stale` for a description of the Serve Stale mechanism.
-
-.. _setting-server-down-max-fails:
-
-``server-down-max-fails``
--------------------------
-- Integer
-- Default: 64
-
-If a server has not responded in any way this many times in a row, no longer send it any queries for `server-down-throttle-time`_ seconds.
-Afterwards, we will try a new packet, and if that also gets no response at all, we again throttle for `server-down-throttle-time`_ seconds.
-Even a single response packet will drop the block.
-
-.. _setting-server-down-throttle-time:
-
-``server-down-throttle-time``
------------------------------
-- Integer
-- Default: 60
-
-Throttle a server that has failed to respond `server-down-max-fails`_ times for this many seconds.
-
-.. _setting-server-id:
-
-``server-id``
--------------
-- String
-- Default: The hostname of the server
-
-The reply given by The PowerDNS recursor to a query for 'id.server' with its hostname, useful for in clusters.
-When a query contains the :rfc:`NSID EDNS0 Option <5001>`, this value is returned in the response as the NSID value.
-
-This setting can be used to override the answer given to these queries.
-Set to "disabled" to disable NSID and 'id.server' answers.
-
-Query example (where 192.0.2.14 is your server):
-
-.. code-block:: sh
-
- dig @192.0.2.14 CHAOS TXT id.server.
- dig @192.0.2.14 example.com IN A +nsid
-
-``setgid``, ``setuid``
-----------------------
-- String
-- Default: unset
-
-PowerDNS can change its user and group id after binding to its socket.
-Can be used for better :doc:`security <security>`.
-
-.. _setting-signature-inception-skew:
-
-``signature-inception-skew``
-----------------------------------
-.. versionadded:: 4.1.5
-
-- Integer
-- Default: 60
-
-Allow the signature inception to be off by this number of seconds. Negative values are not allowed.
-
-.. versionchanged:: 4.2.0
-
- Default is now 60, was 0 before.
-
-.. _setting-single-socket:
-
-``single-socket``
------------------
-- Boolean
-- Default: no
-
-Use only a single socket for outgoing queries.
-
-.. _setting-snmp-agent:
-
-``snmp-agent``
---------------
-.. versionadded:: 4.1.0
-
-- Boolean
-- Default: no
-
-If set to true and PowerDNS has been compiled with SNMP support, it will register as an SNMP agent to provide statistics and be able to send traps.
-
-.. _setting-snmp-master-socket:
-
-``snmp-master-socket``
-----------------------
-
-.. versionadded:: 4.1.0
-.. deprecated:: 4.5.0
- Use :ref:`setting-snmp-daemon-socket`.
-
-.. _setting-snmp-daemon-socket:
-
-``snmp-daemon-socket``
-----------------------
-.. versionadded:: 4.5.0
-
-- String
-- Default: empty
-
-If not empty and ``snmp-agent`` is set to true, indicates how PowerDNS should contact the SNMP daemon to register as an SNMP agent.
-
-.. _setting-socket-dir:
-
-``socket-dir``
---------------
-- Path
-
-Where to store the control socket and pidfile.
-The default depends on ``LOCALSTATEDIR`` or the ``--with-socketdir`` setting when building (usually ``/var/run`` or ``/run``).
-
-When using `chroot`_ the default becomes to ``/``.
-
-``socket-owner``, ``socket-group``, ``socket-mode``
----------------------------------------------------
-Owner, group and mode of the controlsocket.
-Owner and group can be specified by name, mode is in octal.
-
-.. _setting-spoof-nearmiss-max:
-
-``spoof-nearmiss-max``
-----------------------
-.. versionchanged:: 4.5.0
- Older versions used 20 as the default value.
-
-- Integer
-- Default: 1
-
-If set to non-zero, PowerDNS will assume it is being spoofed after seeing this many answers with the wrong id.
-
-.. _setting-stack-cache-size:
-
-``stack-cache-size``
---------------------
-.. versionadded:: 4.9.0
-
-- Integer
-- Default: 100
-
-Maximum number of mthread stacks that can be cached for later reuse, per thread. Caching these stacks reduces the CPU load at the cost of a slightly higher memory usage, each cached stack consuming `stack-size` bytes of memory.
-It makes no sense to cache more stacks than the value of `max-mthreads`, since there will never be more stacks than that in use at a given time.
-
-.. _setting-stack-size:
-
-``stack-size``
---------------
-- Integer
-- Default: 200000
-
-Size in bytes of the stack of each mthread.
-
-.. _setting-statistics-interval:
-
-``statistics-interval``
------------------------
-.. versionadded:: 4.1.0
-
-- Integer
-- Default: 1800
-
-Interval between logging statistical summary on recursor performance.
-Use 0 to disable.
-
-.. _setting-stats-api-blacklist:
-
-``stats-api-blacklist``
------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
- Use :ref:`setting-stats-api-disabled-list`.
-
-.. _setting-stats-api-disabled-list:
-
-``stats-api-disabled-list``
----------------------------
-.. versionadded:: 4.5.0
-
-- String
-- Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
-
-A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
-These statistics can still be retrieved individually by specifically asking for it.
-
-.. _setting-stats-carbon-blacklist:
-
-``stats-carbon-blacklist``
---------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
- Use :ref:`setting-stats-carbon-disabled-list`.
-
-.. _setting-stats-carbon-disabled-list:
-
-``stats-carbon-disabled-list``
-------------------------------
-.. versionadded:: 4.5.0
-
-- String
-- Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*"
-
-A list of comma-separated statistic names, that are prevented from being exported via carbon for performance reasons.
-
-.. _setting-stats-rec-control-blacklist:
-
-``stats-rec-control-blacklist``
--------------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
- Use :ref:`setting-stats-rec-control-disabled-list`.
-
-.. _setting-stats-rec-control-disabled-list:
-
-``stats-rec-control-disabled-list``
-------------------------------------
-.. versionadded:: 4.5.0
-
-- String
-- Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*"
-
-A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
-These statistics can still be retrieved individually.
-
-.. _setting-stats-ringbuffer-entries:
-
-``stats-ringbuffer-entries``
-----------------------------
-- Integer
-- Default: 10000
-
-Number of entries in the remotes ringbuffer, which keeps statistics on who is querying your server.
-Can be read out using ``rec_control top-remotes``.
-
-.. _setting-stats-snmp-blacklist:
-
-``stats-snmp-blacklist``
-------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
- Use :ref:`setting-stats-snmp-disabled-list`.
-
-.. _setting-stats-snmp-disabled-list:
-
-``stats-snmp-disabled-list``
-----------------------------
-.. versionadded:: 4.5.0
-
-- String
-- Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
-
-A list of comma-separated statistic names, that are prevented from being exported via SNMP, for performance reasons.
-
-.. _setting-structured-logging:
-
-``structured-logging``
-----------------------
-.. versionadded:: 4.6.0
-
-- Boolean
-- Default: yes
-
-Prefer structured logging when both an old style and a structured log messages is available.
-
-.. _setting-structured-logging-backend:
-
-``structured-logging-backend``
-------------------------------
-.. versionadded:: 4.8.0
-
-- String
-- Default: "default"
-
-The backend used for structured logging output.
-This setting must be set on the command line (``--structured-logging-backend=...``) to be effective.
-Available backends are:
-
-- ``default``: use the traditional logging system to output structured logging information.
-- ``systemd-journal``: use systemd-journal.
- When using this backend, provide ``-o verbose`` or simular output option to ``journalctl`` to view the full information.
-
-.. _setting-tcp-fast-open:
-
-``tcp-fast-open``
------------------
-.. versionadded:: 4.1.0
-
-- Integer
-- Default: 0 (Disabled)
-
-Enable TCP Fast Open support, if available, on the listening sockets.
-The numerical value supplied is used as the queue size, 0 meaning disabled. See :ref:`tcp-fast-open-support`.
-
-.. _setting-tcp-fast-open-connect:
-
-``tcp-fast-open-connect``
--------------------------
-.. versionadded:: 4.5.0
-
-- Boolean
-- Default: no (disabled)
-
-Enable TCP Fast Open Connect support, if available, on the outgoing connections to authoritative servers. See :ref:`tcp-fast-open-support`.
-
-.. _setting-tcp-out-max-idle-ms:
-
-``tcp-out-max-idle-ms``
------------------------
-.. versionadded:: 4.6.0
-
-- Integer
-- Default : 10000
-
-Time outgoing TCP/DoT connections are left idle in milliseconds or 0 if no limit. After having been idle for this time, the connection is eligible for closing.
-
-.. _setting-tcp-out-max-idle-per-auth:
-
-``tcp-out-max-idle-per-auth``
------------------------------
-.. versionadded:: 4.6.0
-
-- Integer
-- Default : 10
-
-Maximum number of idle outgoing TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open.
-
-.. _setting-tcp-out-max-queries:
-
-``tcp-out-max-queries``
------------------------
-- Integer
-- Default : 0
-
-Maximum total number of queries per outgoing TCP/DoT connection, 0 means no limit. After this number of queries, the connection is
-closed and a new one will be created if needed.
-
-.. versionadded:: 4.6.0
-
-.. _setting-tcp-out-max-idle-per-thread:
-
-``tcp-out-max-idle-per-thread``
--------------------------------
-.. versionadded:: 4.6.0
-
-- Integer
-- Default : 100
-
-Maximum number of idle outgoing TCP/DoT connections per thread, 0 means do not keep idle connections open.
-
-.. _setting-threads:
-
-``threads``
------------
-- Integer
-- Default: 2
-
-Spawn this number of threads on startup.
-
-.. _setting-trace:
-
-``trace``
----------
-- String, one of ``no``, ``yes`` or ``fail``
-- Default: ``no``
-
-If turned on, output impressive heaps of logging.
-May destroy performance under load.
-To log only queries resulting in a ``ServFail`` answer from the resolving process, this value can be set to ``fail``, but note that the performance impact is still large.
-Also note that queries that do produce a result but with a failing DNSSEC validation are not written to the log
-
-.. _setting-udp-source-port-min:
-
-``udp-source-port-min``
------------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Default: 1024
-
-This option sets the low limit of UDP port number to bind on.
-
-In combination with `udp-source-port-max`_ it configures the UDP
-port range to use. Port numbers are randomized within this range on
-initialization, and exceptions can be configured with `udp-source-port-avoid`_
-
-.. _setting-udp-source-port-max:
-
-``udp-source-port-max``
------------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Default: 65535
-
-This option sets the maximum limit of UDP port number to bind on.
-
-See `udp-source-port-min`_.
-
-.. _setting-udp-source-port-avoid:
-
-``udp-source-port-avoid``
--------------------------
-.. versionadded:: 4.2.0
-
-- String
-- Default: 11211
-
-A list of comma-separated UDP port numbers to avoid when binding.
-Ex: `5300,11211`
-
-See `udp-source-port-min`_.
-
-.. _setting-udp-truncation-threshold:
-
-``udp-truncation-threshold``
-----------------------------
-.. versionchanged:: 4.2.0
- Before 4.2.0, the default was 1680
-
-- Integer
-- Default: 1232
-
-EDNS0 allows for large UDP response datagrams, which can potentially raise performance.
-Large responses however also have downsides in terms of reflection attacks.
-This setting limits the accepted size.
-Maximum value is 65535, but values above 4096 should probably not be attempted.
-
-To know why 1232, see the note at :ref:`setting-edns-outgoing-bufsize`.
-
-.. _setting-unique-response-tracking:
-
-``unique-response-tracking``
-----------------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: no (disabled)
-
-Whether to track unique DNS responses, i.e. never seen before combinations
-of the triplet (query name, query type, RR[rrname, rrtype, rrdata]).
-This can be useful for tracking potentially suspicious domains and
-behaviour, e.g. DNS fast-flux.
-If protobuf is enabled and configured, then the Protobuf Response message
-will contain a flag with udr set to true for each RR that is considered
-unique, i.e. never seen before.
-This feature uses a probabilistic data structure (stable bloom filter) to
-track unique responses, which can have false positives as well as false
-negatives, thus it is a best-effort feature. Increasing the number of cells
-in the SBF using the unique-response-db-size setting can reduce FPs and FNs.
-
-.. _setting-unique-response-log:
-
-``unique-response-log``
------------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: no (disabled)
-
-Whether to log when a unique response is detected. The log line
-looks something like:
-
-Oct 24 12:11:27 Unique response observed: qname=foo.com qtype=A rrtype=AAAA rrname=foo.com rrcontent=1.2.3.4
-
-.. _setting-unique-response-db-size:
-
-``unique-response-db-size``
----------------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Example: 67108864
-
-The default size of the stable bloom filter used to store previously
-observed responses is 67108864. To change the number of cells, use this
-setting. For each cell, the SBF uses 1 bit of memory, and one byte of
-disk for the persistent file.
-If there are already persistent files saved to disk, this setting will
-have no effect unless you remove the existing files.
-
-.. _setting-unique-response-history-dir:
-
-``unique-response-history-dir``
--------------------------------
-.. versionadded:: 4.2.0
-
-- Path
-
-This setting controls which directory is used to store the on-disk
-cache of previously observed responses.
-
-The default depends on ``LOCALSTATEDIR`` when building the software.
-Usually this comes down to ``/var/lib/pdns-recursor/udr`` or ``/usr/local/var/lib/pdns-recursor/udr``).
-
-The newly observed domain feature uses a stable bloom filter to store
-a history of previously observed responses. The data structure is
-synchronized to disk every 10 minutes, and is also initialized from
-disk on startup. This ensures that previously observed responses are
-preserved across recursor restarts. If you change the
-unique-response-db-size, you must remove any files from this directory.
-
-.. _setting-unique-response-pb-tag:
-
-``unique-response-pb-tag``
---------------------------
-.. versionadded:: 4.2.0
-
-- String
-- Default: pnds-udr
-
-If protobuf is configured, then this tag will be added to all protobuf response messages when
-a unique DNS response is observed.
-
-.. _setting-use-incoming-edns-subnet:
-
-``use-incoming-edns-subnet``
-----------------------------
-- Boolean
-- Default: no
-
-Whether to process and pass along a received EDNS Client Subnet to authoritative servers.
-The ECS information will only be sent for netmasks and domains listed in `edns-subnet-allow-list`_ and will be truncated if the received scope exceeds `ecs-ipv4-bits`_ for IPv4 or `ecs-ipv6-bits`_ for IPv6.
-
-.. _setting-version:
-
-``version``
------------
-Print version of this binary. Useful for checking which version of the PowerDNS recursor is installed on a system.
-
-.. _setting-version-string:
-
-``version-string``
-------------------
-- String
-- Default: PowerDNS Recursor version number
-
-By default, PowerDNS replies to the 'version.bind' query with its version number.
-Security conscious users may wish to override the reply PowerDNS issues.
-
-.. _setting-webserver:
-
-``webserver``
--------------
-- Boolean
-- Default: no
-
-Start the webserver (for REST API).
-
-.. _setting-webserver-address:
-
-``webserver-address``
----------------------
-- IP Address
-- Default: 127.0.0.1
-
-IP address for the webserver to listen on.
-
-.. _setting-webserver-allow-from:
-
-``webserver-allow-from``
-------------------------
-- IP addresses or netmasks, comma separated, negation supported
-- Default: 127.0.0.1,::1
-
-.. versionchanged:: 4.1.0
-
- Default is now 127.0.0.1,::1, was 0.0.0.0/0,::/0 before.
-
-These IPs and subnets are allowed to access the webserver. Note that
-specifying an IP address without a netmask uses an implicit netmask
-of /32 or /128.
-
-.. _setting-webserver-hash-plaintext-credentials:
-
-``webserver-hash-plaintext-credentials``
-----------------------------------------
-.. versionadded:: 4.6.0
-
-- Boolean
-- Default: no
-
-Whether passwords and API keys supplied in the configuration as plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials and is thus disabled by default.
-Note that this option only applies to credentials stored in the configuration as plaintext, but hashed credentials are supported without enabling this option.
-
-.. _setting-webserver-loglevel:
-
-``webserver-loglevel``
-----------------------
-.. versionadded:: 4.2.0
-
-- String, one of "none", "normal", "detailed"
-
-The amount of logging the webserver must do. "none" means no useful webserver information will be logged.
-When set to "normal", the webserver will log a line per request that should be familiar::
-
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 "GET /api/v1/servers/localhost/bla HTTP/1.1" 404 196
-
-When set to "detailed", all information about the request and response are logged::
-
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Request Details:
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Headers:
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept-encoding: gzip, deflate
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept-language: en-US,en;q=0.5
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e connection: keep-alive
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e dnt: 1
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e host: 127.0.0.1:8081
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e upgrade-insecure-requests: 1
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e user-agent: Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e No body
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Response details:
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Headers:
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Connection: close
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Content-Length: 49
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Content-Type: text/html; charset=utf-8
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Server: PowerDNS/0.0.15896.0.gaba8bab3ab
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e Full body:
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e <!html><title>Not Found</title><h1>Not Found</h1>
- [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 "GET /api/v1/servers/localhost/bla HTTP/1.1" 404 196
-
-The value between the hooks is a UUID that is generated for each request. This can be used to find all lines related to a single request.
-
-.. note::
- The webserver logs these line on the NOTICE level. The :ref:`setting-loglevel` seting must be 5 or higher for these lines to end up in the log.
-
-.. _setting-webserver-password:
-
-``webserver-password``
-----------------------
-.. versionchanged:: 4.6.0
- This setting now accepts a hashed and salted version.
-
-- String
-- Default: unset
-
-Password required to access the webserver. Since 4.6.0 the password can be hashed and salted using ``rec_control hash-password`` instead of being present in the configuration in plaintext, but the plaintext version is still supported.
-
-.. _setting-webserver-port:
-
-``webserver-port``
-------------------
-- Integer
-- Default: 8082
-
-TCP port where the webserver should listen on.
-
-.. _setting-write-pid:
-
-``write-pid``
--------------
-- Boolean
-- Default: yes
-
-If a PID file should be written to `socket-dir`_
-
-.. _setting-xpf-allow-from:
-
-``xpf-allow-from``
-------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.7.0
-
-.. versionchanged:: 4.8.0
- This setting was removed.
-
-- IP addresses or netmasks, separated by commas
-- Default: empty
-
-.. note::
- This is an experimental implementation of `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_.
- This deprecated feature was removed in version 4.8.0.
-
-The server will trust XPF records found in queries sent from those netmasks (both IPv4 and IPv6),
-and will adjust queries' source and destination accordingly. This is especially useful when the recursor
-is placed behind a proxy like `dnsdist <https://dnsdist.org>`_.
-Note that the :ref:`setting-allow-from` setting is still applied to the original source address, and thus access restriction
-should be done on the proxy.
-
-.. _setting-xpf-rr-code:
-
-``xpf-rr-code``
----------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.7.0
-
-.. versionchanged:: 4.8.0
- This setting was removed.
-
-- Integer
-- Default: 0
-
-.. note::
- This is an experimental implementation of `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_.
- This deprecated feature was removed in version 4.8.0.
-
-This option sets the resource record code to use for XPF records, as long as an official code has not been assigned to it.
-0 means that XPF is disabled.
-
-.. _setting-x-dnssec-names:
-
-``x-dnssec-names``
-------------------
-.. versionadded:: 4.5.0
-
-- Comma separated list of domain-names
-- Default: (empty)
-
-List of names whose DNSSEC validation metrics will be counted in a separate set of metrics that start
-with ``x-dnssec-result-``.
-The names are suffix-matched.
-This can be used to not count known failing (test) name validations in the ordinary DNSSEC metrics.
Before upgrading, it is advised to read the :doc:`changelog/index`.
When upgrading several versions, please read **all** notes applying to the upgrade.
-4.8.0 to master
+5.0.3 to master
---------------
+Changed settings
+----------------
+
+For YAML settings only: the type of the :ref:`setting-yaml-incoming.edns_padding_from` and :ref:`setting-yaml-incoming.proxy_protocol_from` has been changed from ``String`` to ``Sequence of Subnet``.
+
+5.0.2 to 5.0.3, 4.9.3 to 4.9.4 and 4.8.6 to 4.8.7
+-------------------------------------------------
+
+Known Issue Solved
+^^^^^^^^^^^^^^^^^^
+The DNSSEC validation issue with the :func:`zoneToCache` function has been resolved and workarounds can be removed.
+
+5.0.1 to 5.0.2, 4.9.2 to 4.9.3 and 4.8.5 to 4.8.6
+-------------------------------------------------
+
+Known Issues
+^^^^^^^^^^^^
+The :func:`zoneToCache` function fails to perform DNSSEC validation if the zone has more than :ref:`setting-max-rrsigs-per-record` RRSIG records at its apex.
+There are two workarounds: either increase the :ref:`setting-max-rrsigs-per-record` to the number of RRSIGs in the zone's apex, or tell :func:`zoneToCache` to skip DNSSEC validation. by adding ``dnssec="ignore"``, e.g.::
+
+ zoneToCache(".", "url", "https://www.internic.net/domain/root.zone", {dnssec="ignore"})
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-max-rrsigs-per-record`, :ref:`setting-max-nsec3s-per-record`, :ref:`setting-max-signature-validations-per-query`, :ref:`setting-max-nsec3-hash-computations-per-query`, :ref:`setting-aggressive-cache-max-nsec3-hash-cost`, :ref:`setting-max-ds-per-zone` and :ref:`setting-max-dnskeys` settings have been introduced to limit the amount of work done for DNSSEC validation.
+
+4.9.0 to 5.0.0
+--------------
+
+YAML settings
+^^^^^^^^^^^^^
+Starting with version 5.0.0-alpha1 the settings file(s) can be specified using YAML syntax.
+The old-style settings files are still accepted but will be unsupported in a future release.
+When a ``recursor.yml`` settings file is encountered it will be processed instead of a ``recursor.conf`` file.
+Refer to :doc:`yamlsettings` for details and the :doc:`appendices/yamlconversion` guide for how to convert old-style settings to the new YAML format.
+
+Rust
+^^^^
+Some parts of the Recursor code are now written in Rust.
+This has impact if you do local builds or are a third-party package maintainer.
+According to `cargo msrv` the minimum version to compile the Rust code and its dependencies is 1.64.
+Some distributions ship with an older Rust compiler, see `Rustup <https://rustup.rs/>`__ for a way to install a more recent one.
+For our package builds, we install a Rust compiler from the ``Standalone`` section of `Other Rust Installation Methods <https://forge.rust-lang.org/infra/other-installation-methods.html>`__.
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-bypass-server-throttling-probability` setting has been introduced to try throttled servers once in a while.
+- The :ref:`setting-tcp-threads` setting has been introduced to set the number of threads dedicated to processing incoming queries over TCP.
+ Previously either the distributor thread(s) or the general worker threads would process TCP queries.
+- The :ref:`setting-qname-max-minimize-count` and :ref:`setting-qname-minimize-one-label` have been introduced to allow tuning of the parameters specified in :rfc:`9156`.
+- The :ref:`setting-allow-no-rd` has been introduced, default disabled, *disallowing* queries that do not have the ``Recursion Desired (RD)`` flag set.
+ This is a change in behavior compared to previous releases.
+- The setting ``ignoreDuplicates`` was added to the RPZ loading Lua functions :func:`rpzPrimary` and :func:`rpzFile`.
+ If set, duplicate records in RPZs will be allowed but ignored.
+ The default is to fail loading an RPZ with duplicate records.
+
+Changed settings
+^^^^^^^^^^^^^^^^
+- The :ref:`setting-loglevel` can now be set to a level below 3 (error).
+- The :ref:`setting-extended-resolution-errors` now defaults to enabled.
+- The :ref:`setting-nsec3-max-iterations` now defaults to 50.
+- Disabling :ref:`setting-structured-logging` has been deprecated and will be removed in a future release.
+
+4.8.0 to 4.9.0
+--------------
+
Metrics
^^^^^^^
The way metrics are collected has been changed to increase performance, especially when many thread are used.
Additionally, most ``RCodes`` and ``QTypes`` that are marked ``Unassigned``, ``Reserved`` or ``Obsolete`` by IANA are not accounted, to reduce the memory consumed by these metrics.
New settings
-~~~~~~~~~~~~
+^^^^^^^^^^^^
- The :ref:`setting-packetcache-negative-ttl` settings to control the TTL of negative (NxDomain or NoData) answers in the packet cache has been introduced.
- The :ref:`setting-stack-cache-size` setting to control the number of allocated mthread stacks has been introduced.
- The :ref:`setting-packetcache-shards` settings to control the number of shards in the packet cache has been introduced.
- The :ref:`setting-aggressive-cache-min-nsec3-hit-ratio` setting to control which NSEC3 records are stored in the aggressive NSEC cache has been introduced.
+ This setting can be used to switch off aggressive caching for NSEC3 only.
+- The :ref:`setting-dnssec-disabled-algorithms` has been introduced to not use DNSSEC algorithms disabled by the platform's security policy.
+ This applies specifically to Red Hat Enterprise Linux 9 and derivatives.
+ The default value (automatically determine the algorithms that are disabled) should work for many cases.
+- The setting ``includeSOA`` was added to the :func:`rpzPrimary` and :func:`rpzFile` Lua functions to include the SOA of the RPZ the responses modified by the RPZ.
Changed settings
-~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^
The first two settings below have effect on the way the recursor distributes queries over threads.
-In some rare cases, this can have negative performance impact.
-In those cases it might be needed to change these settings.
-See :doc:`performance`.
+In some cases, this can lead to imbalance of the number of queries process per thread.
+See :doc:`performance`, in particular the :ref:`worker_imbalance` section.
- The :ref:`setting-pdns-distributes-queries` default has been changed to ``no``.
- The :ref:`setting-reuseport` default has been changed to ``yes``.
-
- The :ref:`setting-packetcache-ttl` default has been changed to 24 hours.
+- The :ref:`setting-max-recursion-depth` default has been changed to 16. Before it was, 40, but effectively the CNAME length chain limit (fixed at 16) took precedence.
+ If you increase :ref:`setting-max-recursion-depth`, you also have to increase :ref:`setting-stack-size`.
+ A starting point of 5k per recursion depth is suggested. Add some extra safety margin to avoid running out of stack.
+- The :ref:`setting-hint-file` setting gained a new special value to disable refreshing of root hints completely. See :ref:`handling-of-root-hints`.
:program:`rec_control`
^^^^^^^^^^^^^^^^^^^^^^
Removed settings
^^^^^^^^^^^^^^^^
-- The :ref:`setting-query-local-address6` has been removed. It already was deprecated.
+- The ``query-local-address6`` setting has been removed. It already was deprecated.
4.3.x to 4.4.0
--------------
Deprecated and changed settings
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- The :ref:`setting-query-local-address` setting has been modified to be able to include both IPv4 and IPv6 addresses.
-- The :ref:`setting-query-local-address6` settings is now deprecated.
+- The ``query-local-address6`` setting is now deprecated.
New settings
^^^^^^^^^^^^
Two new settings have been added:
-- :ref:`setting-xpf-allow-from` can contain a list of IP addresses ranges from which `XPF (X-Proxied-For) <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ records will be trusted.
-- :ref:`setting-xpf-rr-code` should list the number of the XPF record to use (in lieu of an assigned code).
+- ``xpf-allow-from`` can contain a list of IP addresses ranges from which `XPF (X-Proxied-For) <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ records will be trusted.
+- ``setting-xpf-rr-code`` should list the number of the XPF record to use (in lieu of an assigned code).
4.0.x to 4.1.0
--------------
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include <limits>
-
-#include "ednsextendederror.hh"
-
-static bool getEDNSExtendedErrorOptFromStringView(const std::string_view& option, EDNSExtendedError& eee)
-{
- if (option.size() < sizeof(uint16_t)) {
- return false;
- }
- eee.infoCode = static_cast<uint8_t>(option.at(0)) * 256 + static_cast<uint8_t>(option.at(1));
-
- if (option.size() > sizeof(uint16_t)) {
- eee.extraText = std::string(&option.at(sizeof(uint16_t)), option.size() - sizeof(uint16_t));
- }
-
- return true;
-}
-
-bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee)
-{
- return getEDNSExtendedErrorOptFromStringView(std::string_view(option), eee);
-}
-
-bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee)
-{
- return getEDNSExtendedErrorOptFromStringView(std::string_view(option, len), eee);
-}
-
-string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee)
-{
- if (eee.extraText.size() > static_cast<size_t>(std::numeric_limits<uint16_t>::max() - 2)) {
- throw std::runtime_error("Trying to create an EDNS Extended Error option with an extra text of size " + std::to_string(eee.extraText.size()));
- }
-
- string ret;
- ret.reserve(sizeof(uint16_t) + eee.extraText.size());
- ret.resize(sizeof(uint16_t));
-
- ret[0] = static_cast<char>(static_cast<uint16_t>(eee.infoCode) / 256);
- ret[1] = static_cast<char>(static_cast<uint16_t>(eee.infoCode) % 256);
- ret.append(eee.extraText);
-
- return ret;
-}
--- /dev/null
+../ednsextendederror.cc
\ No newline at end of file
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-#include "namespaces.hh"
-
-struct EDNSExtendedError
-{
- enum class code : uint16_t
- {
- Other = 0,
- UnsupportedDNSKEYAlgorithm = 1,
- UnsupportedDSDigestType = 2,
- StaleAnswer = 3,
- ForgedAnswer = 4,
- DNSSECIndeterminate = 5,
- DNSSECBogus = 6,
- SignatureExpired = 7,
- SignatureNotYetValid = 8,
- DNSKEYMissing = 9,
- RRSIGsMissing = 10,
- NoZoneKeyBitSet = 11,
- NSECMissing = 12,
- CachedError = 13,
- NotReady = 14,
- Blocked = 15,
- Censored = 16,
- Filtered = 17,
- Prohibited = 18,
- StaleNXDOMAINAnswer = 19,
- NotAuthoritative = 20,
- NotSupported = 21,
- NoReachableAuthority = 22,
- NetworkError = 23,
- InvalidData = 24,
- SignatureExpiredBeforeValid = 25,
- TooEarly = 26,
- UnsupportedNSEC3IterationsValue = 27,
- UnableToConformToPolicy = 28,
- Synthesized = 29,
- };
- uint16_t infoCode;
- std::string extraText;
-};
-
-bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee);
-bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee);
-string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee);
--- /dev/null
+../ednsextendederror.hh
\ No newline at end of file
SUBDIRS = \
+ arc4random \
yahttp \
json11 \
probds
DIST_SUBDIRS = \
+ arc4random \
yahttp \
json11 \
probds
--- /dev/null
+*.la
+*.lo
+*.o
+Makefile
+Makefile.in
--- /dev/null
+../../../../ext/arc4random/Makefile.am
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/arc4random.c
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/arc4random.h
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/arc4random.hh
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/arc4random_uniform.c
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/bsd-getentropy.c
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/chacha_private.h
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/explicit_bzero.c
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/includes.h
\ No newline at end of file
--- /dev/null
+../../../../ext/arc4random/log.h
\ No newline at end of file
rpzNSDnameName("rpz-nsdname"),
rpzNSIPName("rpz-nsip");
-DNSFilterEngine::DNSFilterEngine()
-{
-}
+DNSFilterEngine::DNSFilterEngine() = default;
bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
{
bool DNSFilterEngine::Zone::findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
{
if (findExactNamedPolicy(d_propolName, qname, pol)) {
- pol.d_trigger = qname;
- pol.d_trigger.appendRawLabel(rpzNSDnameName);
+ // hitdata set by findExactNamedPolicy
+ pol.d_hitdata->d_trigger.appendRawLabel(rpzNSDnameName);
return true;
}
return false;
bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
{
- if (const auto fnd = d_propolNSAddr.lookup(addr)) {
+ if (const auto* fnd = d_propolNSAddr.lookup(addr)) {
pol = fnd->second;
- pol.d_trigger = Zone::maskToRPZ(fnd->first);
- pol.d_trigger.appendRawLabel(rpzNSIPName);
- pol.d_hit = addr.toString();
+ pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+ pol.d_hitdata->d_trigger.appendRawLabel(rpzNSIPName);
return true;
}
return false;
bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
{
- if (const auto fnd = d_postpolAddr.lookup(addr)) {
+ if (const auto* fnd = d_postpolAddr.lookup(addr)) {
pol = fnd->second;
- pol.d_trigger = Zone::maskToRPZ(fnd->first);
- pol.d_trigger.appendRawLabel(rpzIPName);
- pol.d_hit = addr.toString();
+ pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+ pol.d_hitdata->d_trigger.appendRawLabel(rpzIPName);
return true;
}
return false;
bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
{
- if (const auto fnd = d_qpolAddr.lookup(addr)) {
+ if (const auto* fnd = d_qpolAddr.lookup(addr)) {
pol = fnd->second;
- pol.d_trigger = Zone::maskToRPZ(fnd->first);
- pol.d_trigger.appendRawLabel(rpzClientIPName);
- pol.d_hit = addr.toString();
+ pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+ pol.d_hitdata->d_trigger.appendRawLabel(rpzClientIPName);
return true;
}
return false;
return true;
}
- DNSName s(qname);
- while (s.chopOff()) {
- iter = polmap.find(g_wildcarddnsname + s);
+ DNSName sub(qname);
+ while (sub.chopOff()) {
+ iter = polmap.find(g_wildcarddnsname + sub);
if (iter != polmap.end()) {
pol = iter->second;
- pol.d_trigger = iter->first;
- pol.d_hit = qname.toStringNoDot();
+ pol.setHitData(iter->first, qname.toStringNoDot());
return true;
}
}
return false;
}
- const auto& it = polmap.find(qname);
- if (it != polmap.end()) {
- pol = it->second;
- pol.d_trigger = qname;
- pol.d_hit = qname.toStringNoDot();
+ const auto iter = polmap.find(qname);
+ if (iter != polmap.end()) {
+ pol = iter->second;
+ pol.setHitData(qname, qname.toStringNoDot());
return true;
}
std::vector<bool> zoneEnabled(d_zones.size());
size_t count = 0;
bool allEmpty = true;
- for (const auto& z : d_zones) {
+ for (const auto& zone : d_zones) {
bool enabled = true;
- const auto& zoneName = z->getName();
- if (z->getPriority() >= pol.getPriority()) {
- enabled = false;
- }
- else if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+ const auto& zoneName = zone->getName();
+ if (zone->getPriority() >= pol.getPriority() || discardedPolicies.find(zoneName) != discardedPolicies.end()) {
enabled = false;
}
else {
- if (z->hasNSPolicies()) {
+ if (zone->hasNSPolicies()) {
allEmpty = false;
}
else {
/* prepare the wildcard-based names */
std::vector<DNSName> wcNames;
wcNames.reserve(qname.countLabels());
- DNSName s(qname);
- while (s.chopOff()) {
- wcNames.emplace_back(g_wildcarddnsname + s);
+ DNSName sub(qname);
+ while (sub.chopOff()) {
+ wcNames.emplace_back(g_wildcarddnsname + sub);
}
count = 0;
- for (const auto& z : d_zones) {
+ for (const auto& zone : d_zones) {
if (!zoneEnabled[count]) {
++count;
continue;
}
- if (z->findExactNSPolicy(qname, pol)) {
+ if (zone->findExactNSPolicy(qname, pol)) {
// cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
return true;
}
- for (const auto& wc : wcNames) {
- if (z->findExactNSPolicy(wc, pol)) {
+ for (const auto& wildcard : wcNames) {
+ if (zone->findExactNSPolicy(wildcard, pol)) {
// cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
// Hit is not the wildcard passed to findExactQNamePolicy but the actual qname!
- pol.d_hit = qname.toStringNoDot();
+ pol.d_hitdata->d_hit = qname.toStringNoDot();
return true;
}
}
bool DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
{
// cout<<"Got question for nameserver IP "<<address.toString()<<endl;
- for (const auto& z : d_zones) {
- if (z->getPriority() >= pol.getPriority()) {
+ for (const auto& zone : d_zones) {
+ if (zone->getPriority() >= pol.getPriority()) {
break;
}
- const auto& zoneName = z->getName();
+ const auto& zoneName = zone->getName();
if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
continue;
}
- if (z->findNSIPPolicy(address, pol)) {
+ if (zone->findNSIPPolicy(address, pol)) {
// cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
return true;
}
return false;
}
-bool DNSFilterEngine::getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
+bool DNSFilterEngine::getClientPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
{
// cout<<"Got question from "<<ca.toString()<<endl;
- for (const auto& z : d_zones) {
- if (z->getPriority() >= pol.getPriority()) {
+ for (const auto& zone : d_zones) {
+ if (zone->getPriority() >= pol.getPriority()) {
break;
}
- const auto& zoneName = z->getName();
+ const auto& zoneName = zone->getName();
if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
continue;
}
- if (z->findClientPolicy(ca, pol)) {
+ if (zone->findClientPolicy(address, pol)) {
// cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
return true;
}
std::vector<bool> zoneEnabled(d_zones.size());
size_t count = 0;
bool allEmpty = true;
- for (const auto& z : d_zones) {
+ for (const auto& zone : d_zones) {
bool enabled = true;
- if (z->getPriority() >= pol.getPriority()) {
+ if (zone->getPriority() >= pol.getPriority()) {
enabled = false;
}
else {
- const auto& zoneName = z->getName();
+ const auto& zoneName = zone->getName();
if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
enabled = false;
}
else {
- if (z->hasQNamePolicies()) {
+ if (zone->hasQNamePolicies()) {
allEmpty = false;
}
else {
/* prepare the wildcard-based names */
std::vector<DNSName> wcNames;
wcNames.reserve(qname.countLabels());
- DNSName s(qname);
- while (s.chopOff()) {
- wcNames.emplace_back(g_wildcarddnsname + s);
+ DNSName sub(qname);
+ while (sub.chopOff()) {
+ wcNames.emplace_back(g_wildcarddnsname + sub);
}
count = 0;
- for (const auto& z : d_zones) {
+ for (const auto& zone : d_zones) {
if (!zoneEnabled[count]) {
++count;
continue;
}
- if (z->findExactQNamePolicy(qname, pol)) {
+ if (zone->findExactQNamePolicy(qname, pol)) {
// cerr<<"Had a hit on the name of the query"<<endl;
return true;
}
- for (const auto& wc : wcNames) {
- if (z->findExactQNamePolicy(wc, pol)) {
+ for (const auto& wildcard : wcNames) {
+ if (zone->findExactQNamePolicy(wildcard, pol)) {
// cerr<<"Had a hit on the name of the query"<<endl;
// Hit is not the wildcard passed to findExactQNamePolicy but the actual qname!
- pol.d_hit = qname.toStringNoDot();
+ pol.d_hitdata->d_hit = qname.toStringNoDot();
return true;
}
}
bool DNSFilterEngine::getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
{
- ComboAddress ca;
+ ComboAddress address;
if (record.d_place != DNSResourceRecord::ANSWER) {
return false;
}
if (record.d_type == QType::A) {
if (auto rec = getRR<ARecordContent>(record)) {
- ca = rec->getCA();
+ address = rec->getCA();
}
}
else if (record.d_type == QType::AAAA) {
if (auto rec = getRR<AAAARecordContent>(record)) {
- ca = rec->getCA();
+ address = rec->getCA();
}
}
else {
return false;
}
- for (const auto& z : d_zones) {
- if (z->getPriority() >= pol.getPriority()) {
+ for (const auto& zone : d_zones) {
+ if (zone->getPriority() >= pol.getPriority()) {
break;
}
- const auto& zoneName = z->getName();
+ const auto& zoneName = zone->getName();
if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
return false;
}
- if (z->findResponsePolicy(ca, pol)) {
+ if (zone->findResponsePolicy(address, pol)) {
return true;
}
}
void DNSFilterEngine::assureZones(size_t zone)
{
- if (d_zones.size() <= zone)
+ if (d_zones.size() <= zone) {
d_zones.resize(zone + 1);
+ }
+}
+
+static void addCustom(DNSFilterEngine::Policy& existingPol, const DNSFilterEngine::Policy& pol)
+{
+ if (!existingPol.d_custom) {
+ existingPol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+ }
+ if (pol.d_custom) {
+ existingPol.d_custom->reserve(existingPol.d_custom->size() + pol.d_custom->size());
+ std::move(pol.d_custom->begin(), pol.d_custom->end(), std::back_inserter(*existingPol.d_custom));
+ }
}
void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
{
- auto it = map.find(n);
+ auto iter = map.find(n);
- if (it != map.end()) {
- auto& existingPol = it->second;
+ if (iter != map.end()) {
+ auto& existingPol = iter->second;
if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
+ if (d_zoneData->d_ignoreDuplicates) {
+ return;
+ }
throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following name: " + n.toLogString());
}
- if (existingPol.d_kind != PolicyKind::Custom && ignoreDuplicate) {
- throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following name: " + n.toLogString());
+ if (existingPol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
+ if (d_zoneData->d_ignoreDuplicates) {
+ return;
+ }
+ throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for for the following name: " + n.toLogString());
}
- existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
-
- std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
+ addCustom(existingPol, pol);
}
else {
auto& qpol = map.insert({n, std::move(pol)}).first->second;
}
}
-void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
+void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
{
- bool exists = nmt.has_key(nm);
+ bool exists = nmt.has_key(netmask);
if (exists) {
- // XXX NetMaskTree's node_type has a non-const second, but lookup() returns a const node_type *, so we cannot modify second
- // Should look into making lookup) return a non-const node_type *...
- auto& existingPol = const_cast<Policy&>(nmt.lookup(nm)->second);
+ auto& existingPol = nmt.lookup(netmask)->second;
if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
- throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following netmask: " + nm.toString());
+ if (d_zoneData->d_ignoreDuplicates) {
+ return;
+ }
+ throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following netmask: " + netmask.toString());
}
- if (existingPol.d_kind != PolicyKind::Custom && ignoreDuplicate) {
- throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following netmask: " + nm.toString());
+ if (existingPol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
+ if (d_zoneData->d_ignoreDuplicates) {
+ return;
+ }
+ throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following netmask: " + netmask.toString());
}
- existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
-
- std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
+ addCustom(existingPol, pol);
}
else {
pol.d_zoneData = d_zoneData;
pol.d_type = ptype;
- nmt.insert(nm).second = std::move(pol);
+ nmt.insert(netmask).second = std::move(pol);
}
}
-bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& name, const Policy& pol)
{
- auto found = map.find(n);
+ auto found = map.find(name);
if (found == map.end()) {
return false;
}
/* for custom types, we might have more than one type,
and then we need to remove only the right ones. */
bool result = false;
- for (auto& toRemove : pol.d_custom) {
- for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
- if (**it == *toRemove) {
- existing.d_custom.erase(it);
- result = true;
- break;
+ if (pol.d_custom && existing.d_custom) {
+ for (const auto& toRemove : *pol.d_custom) {
+ for (auto it = existing.d_custom->begin(); it != existing.d_custom->end(); ++it) {
+ if (**it == *toRemove) {
+ existing.d_custom->erase(it);
+ result = true;
+ break;
+ }
}
}
}
// No records left for this trigger?
- if (existing.d_custom.size() == 0) {
+ if (existing.customRecordsSize() == 0) {
map.erase(found);
return true;
}
return result;
}
-bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, const Policy& pol)
{
- bool found = nmt.has_key(nm);
+ bool found = nmt.has_key(netmask);
if (!found) {
return false;
}
- // XXX NetMaskTree's node_type has a non-const second, but lookup() returns a const node_type *, so we cannot modify second
- // Should look into making lookup) return a non-const node_type *...
- auto& existing = const_cast<Policy&>(nmt.lookup(nm)->second);
+ auto& existing = nmt.lookup(netmask)->second;
if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
- nmt.erase(nm);
+ nmt.erase(netmask);
return true;
}
and then we need to remove only the right ones. */
bool result = false;
- for (auto& toRemove : pol.d_custom) {
- for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
- if (**it == *toRemove) {
- existing.d_custom.erase(it);
- result = true;
- break;
+ if (pol.d_custom && existing.d_custom) {
+ for (const auto& toRemove : *pol.d_custom) {
+ for (auto it = existing.d_custom->begin(); it != existing.d_custom->end(); ++it) {
+ if (**it == *toRemove) {
+ existing.d_custom->erase(it);
+ result = true;
+ break;
+ }
}
}
}
// No records left for this trigger?
- if (existing.d_custom.size() == 0) {
- nmt.erase(nm);
+ if (existing.customRecordsSize() == 0) {
+ nmt.erase(netmask);
return true;
}
return result;
}
-void DNSFilterEngine::Zone::addClientTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addClientTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
{
- addNetmaskTrigger(d_qpolAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::ClientIP);
+ addNetmaskTrigger(d_qpolAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::ClientIP);
}
-void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
{
- addNetmaskTrigger(d_postpolAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::ResponseIP);
+ addNetmaskTrigger(d_postpolAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::ResponseIP);
}
-void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate)
{
- addNameTrigger(d_qpolName, n, std::move(pol), ignoreDuplicate, PolicyType::QName);
+ addNameTrigger(d_qpolName, dnsname, std::move(pol), ignoreDuplicate, PolicyType::QName);
}
-void DNSFilterEngine::Zone::addNSTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addNSTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate)
{
- addNameTrigger(d_propolName, n, std::move(pol), ignoreDuplicate, PolicyType::NSDName);
+ addNameTrigger(d_propolName, dnsname, std::move(pol), ignoreDuplicate, PolicyType::NSDName);
}
-void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
{
- addNetmaskTrigger(d_propolNSAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::NSIP);
+ addNetmaskTrigger(d_propolNSAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::NSIP);
}
-bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& nm, const Policy& pol)
+bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& netmask, const Policy& pol)
{
- return rmNetmaskTrigger(d_qpolAddr, nm, pol);
+ return rmNetmaskTrigger(d_qpolAddr, netmask, pol);
}
-bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& nm, const Policy& pol)
+bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& netmask, const Policy& pol)
{
- return rmNetmaskTrigger(d_postpolAddr, nm, pol);
+ return rmNetmaskTrigger(d_postpolAddr, netmask, pol);
}
-bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, const Policy& pol)
+bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& dnsname, const Policy& pol)
{
- return rmNameTrigger(d_qpolName, n, pol);
+ return rmNameTrigger(d_qpolName, dnsname, pol);
}
-bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& n, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& dnsname, const Policy& pol)
{
- return rmNameTrigger(d_propolName, n, pol);
+ return rmNameTrigger(d_propolName, dnsname, pol);
}
-bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& nm, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& netmask, const Policy& pol)
{
- return rmNetmaskTrigger(d_propolNSAddr, nm, pol);
+ return rmNetmaskTrigger(d_propolNSAddr, netmask, pol);
}
std::string DNSFilterEngine::Policy::getLogString() const
{
- return ": RPZ Hit; PolicyName=" + getName() + "; Trigger=" + d_trigger.toLogString() + "; Hit=" + d_hit + "; Type=" + getTypeToString(d_type) + "; Kind=" + getKindToString(d_kind);
+ return ": RPZ Hit; PolicyName=" + getName() + "; Trigger=" + getTrigger().toLogString() + "; Hit=" + getHit() + "; Type=" + getTypeToString(d_type) + "; Kind=" + getKindToString(d_kind);
}
void DNSFilterEngine::Policy::info(Logr::Priority prio, const std::shared_ptr<Logr::Logger>& log) const
{
- log->info(prio, "RPZ Hit", "policyName", Logging::Loggable(getName()), "trigger", Logging::Loggable(d_trigger),
- "hit", Logging::Loggable(d_hit), "type", Logging::Loggable(getTypeToString(d_type)),
+ log->info(prio, "RPZ Hit", "policyName", Logging::Loggable(getName()), "trigger", Logging::Loggable(getTrigger()),
+ "hit", Logging::Loggable(getHit()), "type", Logging::Loggable(getTypeToString(d_type)),
"kind", Logging::Loggable(getKindToString(d_kind)));
}
DNSRecord DNSFilterEngine::Policy::getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const
{
- DNSRecord dr;
- dr.d_name = qname;
- dr.d_type = custom->getType();
- dr.d_ttl = d_ttl;
- dr.d_class = QClass::IN;
- dr.d_place = DNSResourceRecord::ANSWER;
- dr.setContent(custom);
+ DNSRecord dnsrecord;
+ dnsrecord.d_name = qname;
+ dnsrecord.d_type = custom->getType();
+ dnsrecord.d_ttl = d_ttl;
+ dnsrecord.d_class = QClass::IN;
+ dnsrecord.d_place = DNSResourceRecord::ANSWER;
+ dnsrecord.setContent(custom);
- if (dr.d_type == QType::CNAME) {
+ if (dnsrecord.d_type == QType::CNAME) {
const auto content = std::dynamic_pointer_cast<const CNAMERecordContent>(custom);
if (content) {
DNSName target = content->getTarget();
if (target.isWildcard()) {
target.chopOff();
- dr.setContent(std::make_shared<CNAMERecordContent>(qname + target));
+ dnsrecord.setContent(std::make_shared<CNAMERecordContent>(qname + target));
}
}
}
- return dr;
+ return dnsrecord;
}
std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName& qname, uint16_t qtype) const
}
std::vector<DNSRecord> result;
+ if (customRecordsSize() == 0) {
+ return result;
+ }
- for (const auto& custom : d_custom) {
+ for (const auto& custom : *d_custom) {
if (qtype != QType::ANY && qtype != custom->getType() && custom->getType() != QType::CNAME) {
continue;
}
- DNSRecord dr;
- dr.d_name = qname;
- dr.d_type = custom->getType();
- dr.d_ttl = d_ttl;
- dr.d_class = QClass::IN;
- dr.d_place = DNSResourceRecord::ANSWER;
- dr.setContent(custom);
+ DNSRecord dnsrecord;
+ dnsrecord.d_name = qname;
+ dnsrecord.d_type = custom->getType();
+ dnsrecord.d_ttl = d_ttl;
+ dnsrecord.d_class = QClass::IN;
+ dnsrecord.d_place = DNSResourceRecord::ANSWER;
+ dnsrecord.setContent(custom);
- if (dr.d_type == QType::CNAME) {
+ if (dnsrecord.d_type == QType::CNAME) {
const auto content = std::dynamic_pointer_cast<const CNAMERecordContent>(custom);
if (content) {
DNSName target = content->getTarget();
if (target.isWildcard()) {
target.chopOff();
- dr.setContent(std::make_shared<CNAMERecordContent>(qname + target));
+ dnsrecord.setContent(std::make_shared<CNAMERecordContent>(qname + target));
}
}
}
result = getCustomRecords(qname, QType::ANY);
}
else {
- DNSRecord dr;
- dr.d_name = qname;
- dr.d_ttl = static_cast<uint32_t>(d_ttl);
- dr.d_type = QType::CNAME;
- dr.d_class = QClass::IN;
- dr.setContent(DNSRecordContent::mastermake(QType::CNAME, QClass::IN, getKindToString(d_kind)));
- result.push_back(std::move(dr));
+ DNSRecord dnsrecord;
+ dnsrecord.d_name = qname;
+ dnsrecord.d_ttl = static_cast<uint32_t>(d_ttl);
+ dnsrecord.d_type = QType::CNAME;
+ dnsrecord.d_class = QClass::IN;
+ dnsrecord.setContent(DNSRecordContent::make(QType::CNAME, QClass::IN, getKindToString(d_kind)));
+ result.push_back(std::move(dnsrecord));
}
return result;
}
-void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol)
+void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* filePtr, const DNSName& name, const Policy& pol)
{
auto records = pol.getRecords(name);
- for (const auto& dr : records) {
- fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).toString().c_str(), dr.getContent()->getZoneRepresentation().c_str());
+ for (const auto& record : records) {
+ fprintf(filePtr, "%s %" PRIu32 " IN %s %s\n", record.d_name.toString().c_str(), record.d_ttl, QType(record.d_type).toString().c_str(), record.getContent()->getZoneRepresentation().c_str());
}
}
-DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm)
+DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& netmask)
{
- int bits = nm.getBits();
+ int bits = netmask.getBits();
DNSName res(std::to_string(bits));
- const auto& addr = nm.getNetwork();
+ const auto& addr = netmask.getNetwork();
if (addr.isIPv4()) {
- const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&addr.sin4.sin_addr.s_addr);
- res += DNSName(std::to_string(bytes[3]) + "." + std::to_string(bytes[2]) + "." + std::to_string(bytes[1]) + "." + std::to_string(bytes[0]));
+ const auto* bytes = reinterpret_cast<const uint8_t*>(&addr.sin4.sin_addr.s_addr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ res += DNSName(std::to_string(bytes[3]) + "." + std::to_string(bytes[2]) + "." + std::to_string(bytes[1]) + "." + std::to_string(bytes[0])); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
}
else {
DNSName temp;
static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(uint16_t) * 8);
- auto src = reinterpret_cast<const uint16_t*>(&addr.sin6.sin6_addr.s6_addr);
- std::array<uint16_t, 8> elems;
+ const auto* src = reinterpret_cast<const uint16_t*>(&addr.sin6.sin6_addr.s6_addr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ std::array<uint16_t, 8> elems{};
// this routine was adopted from libc's inet_ntop6, written by Paul Vixie
// because the RPZ spec (https://datatracker.ietf.org/doc/html/draft-vixie-dnsop-dns-rpz-00#section-4.1.1) says:
int base, len;
} best = {-1, 0}, cur = {-1, 0};
- for (int i = 0; i < (int)elems.size(); i++) {
- elems[i] = ntohs(src[i]);
- if (elems[i] == 0) {
+ const int size = elems.size();
+ for (int i = 0; i < size; i++) {
+ elems.at(i) = ntohs(src[i]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ if (elems.at(i) == 0) {
if (cur.base == -1) { // start of a run of zeroes
cur = {i, 1};
}
return res;
}
-void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol)
+void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* filePtr, const Netmask& netmask, const DNSName& name, const Policy& pol)
{
- DNSName full = maskToRPZ(nm);
+ DNSName full = maskToRPZ(netmask);
full += name;
auto records = pol.getRecords(full);
- for (const auto& dr : records) {
- fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).toString().c_str(), dr.getContent()->getZoneRepresentation().c_str());
+ for (const auto& record : records) {
+ fprintf(filePtr, "%s %" PRIu32 " IN %s %s\n", record.d_name.toString().c_str(), record.d_ttl, QType(record.d_type).toString().c_str(), record.getContent()->getZoneRepresentation().c_str());
}
}
-void DNSFilterEngine::Zone::dump(FILE* fp) const
+void DNSFilterEngine::Zone::dump(FILE* filePtr) const
{
/* fake the SOA record */
- auto soa = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "fake.RPZ. hostmaster.fake.RPZ. " + std::to_string(d_serial) + " " + std::to_string(d_refresh) + " 600 3600000 604800");
- fprintf(fp, "%s IN SOA %s\n", d_domain.toString().c_str(), soa->getZoneRepresentation().c_str());
+ auto soa = DNSRecordContent::make(QType::SOA, QClass::IN, "fake.RPZ. hostmaster.fake.RPZ. " + std::to_string(d_serial) + " " + std::to_string(d_refresh) + " 600 3600000 604800");
+ fprintf(filePtr, "%s IN SOA %s\n", d_domain.toString().c_str(), soa->getZoneRepresentation().c_str());
for (const auto& pair : d_qpolName) {
- dumpNamedPolicy(fp, pair.first + d_domain, pair.second);
+ dumpNamedPolicy(filePtr, pair.first + d_domain, pair.second);
}
for (const auto& pair : d_propolName) {
- dumpNamedPolicy(fp, pair.first + DNSName(rpzNSDnameName) + d_domain, pair.second);
+ dumpNamedPolicy(filePtr, pair.first + DNSName(rpzNSDnameName) + d_domain, pair.second);
}
for (const auto& pair : d_qpolAddr) {
- dumpAddrPolicy(fp, pair.first, DNSName(rpzClientIPName) + d_domain, pair.second);
+ dumpAddrPolicy(filePtr, pair.first, DNSName(rpzClientIPName) + d_domain, pair.second);
}
for (const auto& pair : d_propolNSAddr) {
- dumpAddrPolicy(fp, pair.first, DNSName(rpzNSIPName) + d_domain, pair.second);
+ dumpAddrPolicy(filePtr, pair.first, DNSName(rpzNSIPName) + d_domain, pair.second);
}
for (const auto& pair : d_postpolAddr) {
- dumpAddrPolicy(fp, pair.first, DNSName(rpzIPName) + d_domain, pair.second);
+ dumpAddrPolicy(filePtr, pair.first, DNSName(rpzIPName) + d_domain, pair.second);
}
}
#include <map>
#include <unordered_map>
#include <limits>
+#include <utility>
/* This class implements a filtering policy that is able to fully implement RPZ, but is not bound to it.
In other words, it is generic enough to support RPZ, but could get its data from other places.
NSDName,
NSIP
};
- typedef uint16_t Priority;
+ using Priority = uint16_t;
static const Priority maximumPriority = std::numeric_limits<Priority>::max();
static std::string getKindToString(PolicyKind kind);
std::unordered_set<std::string> d_tags;
std::string d_name;
std::string d_extendedErrorExtra;
+ DNSRecord d_soa{};
boost::optional<uint16_t> d_extendedErrorCode{boost::none};
Priority d_priority{maximumPriority};
bool d_policyOverridesGettag{true};
+ bool d_includeSOA{false};
+ bool d_ignoreDuplicates{false};
};
struct Policy
}
Policy(PolicyKind kind, PolicyType type, int32_t ttl = 0, std::shared_ptr<PolicyZoneData> data = nullptr, const std::vector<std::shared_ptr<const DNSRecordContent>>& custom = {}) :
- d_custom(custom), d_zoneData(data), d_ttl(ttl), d_kind(kind), d_type(type)
+ d_zoneData(std::move(data)), d_custom(nullptr), d_ttl(ttl), d_kind(kind), d_type(type)
{
+ if (!custom.empty()) {
+ setCustom(custom);
+ }
+ }
+
+ ~Policy() = default;
+
+ Policy(const Policy& rhs) :
+ d_zoneData(rhs.d_zoneData),
+ d_custom(rhs.d_custom ? make_unique<CustomData>(*rhs.d_custom) : nullptr),
+ d_hitdata(rhs.d_hitdata ? make_unique<HitData>(*rhs.d_hitdata) : nullptr),
+ d_ttl(rhs.d_ttl),
+ d_kind(rhs.d_kind),
+ d_type(rhs.d_type)
+ {
+ }
+
+ Policy& operator=(const Policy& rhs)
+ {
+ if (this != &rhs) {
+ if (rhs.d_custom) {
+ d_custom = make_unique<CustomData>(*rhs.d_custom);
+ }
+ d_zoneData = rhs.d_zoneData;
+ if (rhs.d_hitdata) {
+ d_hitdata = make_unique<HitData>(*rhs.d_hitdata);
+ }
+ else {
+ d_hitdata = nullptr;
+ }
+ d_ttl = rhs.d_ttl;
+ d_kind = rhs.d_kind;
+ d_type = rhs.d_type;
+ }
+ return *this;
}
+ Policy(Policy&&) = default;
+ Policy& operator=(Policy&&) = default;
+
bool operator==(const Policy& rhs) const
{
return d_kind == rhs.d_kind && d_type == rhs.d_type && d_ttl == rhs.d_ttl && d_custom == rhs.d_custom;
}
- const std::string& getName() const
+ [[nodiscard]] const std::string& getName() const
{
static const std::string notSet;
if (d_zoneData) {
newZoneData = std::make_shared<PolicyZoneData>();
}
newZoneData->d_name = name;
- d_zoneData = newZoneData;
+ d_zoneData = std::move(newZoneData);
}
- const std::unordered_set<std::string>& getTags() const
+ [[nodiscard]] const std::unordered_set<std::string>& getTags() const
{
static const std::unordered_set<std::string> notSet;
if (d_zoneData) {
return notSet;
}
- Priority getPriority() const
+ [[nodiscard]] Priority getPriority() const
{
static Priority notSet = maximumPriority;
if (d_zoneData) {
return notSet;
}
- bool policyOverridesGettag() const
+ [[nodiscard]] bool policyOverridesGettag() const
{
if (d_zoneData) {
return d_zoneData->d_policyOverridesGettag;
return true;
}
- bool wasHit() const
+ [[nodiscard]] bool getSOA(DNSRecord& rec) const
+ {
+ if (d_zoneData) {
+ rec = d_zoneData->d_soa;
+ return true;
+ }
+ return false;
+ }
+
+ [[nodiscard]] bool includeSOA() const
+ {
+ if (d_zoneData) {
+ return d_zoneData->d_includeSOA;
+ }
+ return false;
+ }
+
+ [[nodiscard]] bool wasHit() const
{
return (d_type != DNSFilterEngine::PolicyType::None && d_kind != DNSFilterEngine::PolicyKind::NoAction);
}
- std::string getLogString() const;
+ [[nodiscard]] std::string getLogString() const;
void info(Logr::Priority prio, const std::shared_ptr<Logr::Logger>& log) const;
- std::vector<DNSRecord> getCustomRecords(const DNSName& qname, uint16_t qtype) const;
- std::vector<DNSRecord> getRecords(const DNSName& qname) const;
+ [[nodiscard]] std::vector<DNSRecord> getCustomRecords(const DNSName& qname, uint16_t qtype) const;
+ [[nodiscard]] std::vector<DNSRecord> getRecords(const DNSName& qname) const;
- std::vector<std::shared_ptr<const DNSRecordContent>> d_custom;
std::shared_ptr<PolicyZoneData> d_zoneData{nullptr};
- DNSName d_trigger;
- string d_hit;
+
+ using CustomData = std::vector<std::shared_ptr<const DNSRecordContent>>;
+ std::unique_ptr<CustomData> d_custom;
+
+ struct HitData
+ {
+ DNSName d_trigger;
+ string d_hit;
+ };
+ std::unique_ptr<HitData> d_hitdata;
/* Yup, we are currently using the same TTL for every record for a given name */
int32_t d_ttl;
PolicyKind d_kind;
PolicyType d_type;
+ void addSOAtoRPZResult(vector<DNSRecord>& ret) const
+ {
+ DNSRecord soa{};
+ if (includeSOA() && getSOA(soa)) {
+ soa.d_place = DNSResourceRecord::ADDITIONAL;
+ ret.emplace_back(soa);
+ }
+ }
+
+ void setCustom(const CustomData& custom)
+ {
+ d_custom = make_unique<CustomData>(custom);
+ }
+
+ [[nodiscard]] size_t customRecordsSize() const
+ {
+ if (d_custom) {
+ return d_custom->size();
+ }
+ return 0;
+ }
+
+ void setHitData(const DNSName& name, const string& hit)
+ {
+ HitData hitdata{name, hit};
+ d_hitdata = make_unique<HitData>(hitdata);
+ }
+
+ [[nodiscard]] DNSName getTrigger() const
+ {
+ return d_hitdata ? d_hitdata->d_trigger : DNSName();
+ }
+
+ [[nodiscard]] std::string getHit() const
+ {
+ return d_hitdata ? d_hitdata->d_hit : "";
+ }
+
private:
- DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const;
+ [[nodiscard]] DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const;
};
class Zone
{
d_zoneData->d_extendedErrorExtra = extra;
}
-
- const std::string& getName() const
+ void setSOA(DNSRecord soa)
+ {
+ d_zoneData->d_soa = std::move(soa);
+ }
+ [[nodiscard]] const std::string& getName() const
{
return d_zoneData->d_name;
}
- DNSName getDomain() const
+ [[nodiscard]] DNSName getDomain() const
{
return d_domain;
}
- uint32_t getRefresh() const
+ [[nodiscard]] uint32_t getRefresh() const
{
return d_refresh;
}
- uint32_t getSerial() const
+ [[nodiscard]] uint32_t getSerial() const
{
return d_serial;
}
- size_t size() const
+ [[nodiscard]] size_t size() const
{
return d_qpolAddr.size() + d_postpolAddr.size() + d_propolName.size() + d_propolNSAddr.size() + d_qpolName.size();
}
- void dump(FILE* fp) const;
+ void setIncludeSOA(bool flag)
+ {
+ d_zoneData->d_includeSOA = flag;
+ }
+
+ void setIgnoreDuplicates(bool flag)
+ {
+ d_zoneData->d_ignoreDuplicates = flag;
+ }
+
+ void dump(FILE* filePtr) const;
- void addClientTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
- void addQNameTrigger(const DNSName& nm, Policy&& pol, bool ignoreDuplicate = false);
- void addNSTrigger(const DNSName& dn, Policy&& pol, bool ignoreDuplicate = false);
- void addNSIPTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
- void addResponseTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
+ void addClientTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate = false);
+ void addQNameTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate = false);
+ void addNSTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate = false);
+ void addNSIPTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate = false);
+ void addResponseTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate = false);
- bool rmClientTrigger(const Netmask& nm, const Policy& pol);
- bool rmQNameTrigger(const DNSName& nm, const Policy& pol);
- bool rmNSTrigger(const DNSName& dn, const Policy& pol);
- bool rmNSIPTrigger(const Netmask& nm, const Policy& pol);
- bool rmResponseTrigger(const Netmask& nm, const Policy& pol);
+ bool rmClientTrigger(const Netmask& netmask, const Policy& pol);
+ bool rmQNameTrigger(const DNSName& dnsname, const Policy& pol);
+ bool rmNSTrigger(const DNSName& dnsname, const Policy& pol);
+ bool rmNSIPTrigger(const Netmask& netmask, const Policy& pol);
+ bool rmResponseTrigger(const Netmask& netmask, const Policy& pol);
bool findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const;
bool findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const;
bool findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
bool findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
- bool hasClientPolicies() const
+ [[nodiscard]] bool hasClientPolicies() const
{
return !d_qpolAddr.empty();
}
- bool hasQNamePolicies() const
+ [[nodiscard]] bool hasQNamePolicies() const
{
return !d_qpolName.empty();
}
- bool hasNSPolicies() const
+ [[nodiscard]] bool hasNSPolicies() const
{
return !d_propolName.empty();
}
- bool hasNSIPPolicies() const
+ [[nodiscard]] bool hasNSIPPolicies() const
{
return !d_propolNSAddr.empty();
}
- bool hasResponsePolicies() const
+ [[nodiscard]] bool hasResponsePolicies() const
{
return !d_postpolAddr.empty();
}
- Priority getPriority() const
+ [[nodiscard]] Priority getPriority() const
{
return d_zoneData->d_priority;
}
- void setPriority(Priority p)
+ void setPriority(Priority priority)
{
- d_zoneData->d_priority = p;
+ d_zoneData->d_priority = priority;
}
- static DNSName maskToRPZ(const Netmask& nm);
+ static DNSName maskToRPZ(const Netmask& netmask);
private:
void addNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype);
- void addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, Policy&& pol, bool ignoreDuplicate, PolicyType ptype);
- bool rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, const Policy& pol);
- bool rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, const Policy& pol);
+ void addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, Policy&& pol, bool ignoreDuplicate, PolicyType ptype);
+ static bool rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, const Policy& pol);
+ static bool rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, const Policy& pol);
- private:
static bool findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol);
static bool findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol);
- static void dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol);
- static void dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol);
+ static void dumpNamedPolicy(FILE* filePtr, const DNSName& name, const Policy& pol);
+ static void dumpAddrPolicy(FILE* filePtr, const Netmask& netmask, const DNSName& name, const Policy& pol);
std::unordered_map<DNSName, Policy> d_qpolName; // QNAME trigger (RPZ)
NetmaskTree<Policy> d_qpolAddr; // Source address
DNSFilterEngine();
void clear()
{
- for (auto& z : d_zones) {
- z->clear();
+ for (auto& zone : d_zones) {
+ zone->clear();
}
}
void clearZones()
{
d_zones.clear();
}
- const std::shared_ptr<Zone> getZone(size_t zoneIdx) const
+ [[nodiscard]] std::shared_ptr<Zone> getZone(size_t zoneIdx) const
{
std::shared_ptr<Zone> result{nullptr};
if (zoneIdx < d_zones.size()) {
}
return result;
}
- const std::shared_ptr<Zone> getZone(const std::string& name) const
+ [[nodiscard]] std::shared_ptr<Zone> getZone(const std::string& name) const
{
for (const auto& zone : d_zones) {
const auto& zName = zone->getName();
}
return nullptr;
}
- size_t addZone(std::shared_ptr<Zone> newZone)
+ size_t addZone(const std::shared_ptr<Zone>& newZone)
{
newZone->setPriority(d_zones.size());
d_zones.push_back(newZone);
return (d_zones.size() - 1);
}
- void setZone(size_t zoneIdx, std::shared_ptr<Zone> newZone)
+ void setZone(size_t zoneIdx, const std::shared_ptr<Zone>& newZone)
{
if (newZone) {
assureZones(zoneIdx);
}
bool getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& policy) const;
- bool getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& policy) const;
+ bool getClientPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& policy) const;
bool getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& policy) const;
bool getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& policy) const;
bool getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& policy) const;
bool getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& policy) const;
// A few convenience methods for the unit test code
- Policy getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+ [[nodiscard]] Policy getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
{
Policy policy;
policy.d_zoneData = std::make_shared<PolicyZoneData>();
- policy.d_zoneData->d_priority = p;
+ policy.d_zoneData->d_priority = priority;
getQueryPolicy(qname, discardedPolicies, policy);
return policy;
}
- Policy getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+ [[nodiscard]] Policy getClientPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
{
Policy policy;
policy.d_zoneData = std::make_shared<PolicyZoneData>();
- policy.d_zoneData->d_priority = p;
- getClientPolicy(ca, discardedPolicies, policy);
+ policy.d_zoneData->d_priority = priority;
+ getClientPolicy(address, discardedPolicies, policy);
return policy;
}
- Policy getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+ [[nodiscard]] Policy getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
{
Policy policy;
policy.d_zoneData = std::make_shared<PolicyZoneData>();
- policy.d_zoneData->d_priority = p;
+ policy.d_zoneData->d_priority = priority;
getProcessingPolicy(qname, discardedPolicies, policy);
return policy;
}
- Policy getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+ [[nodiscard]] Policy getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
{
Policy policy;
policy.d_zoneData = std::make_shared<PolicyZoneData>();
- policy.d_zoneData->d_priority = p;
+ policy.d_zoneData->d_priority = priority;
getProcessingPolicy(address, discardedPolicies, policy);
return policy;
}
- Policy getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+ [[nodiscard]] Policy getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
{
Policy policy;
policy.d_zoneData = std::make_shared<PolicyZoneData>();
- policy.d_zoneData->d_priority = p;
+ policy.d_zoneData->d_priority = priority;
getPostPolicy(records, discardedPolicies, policy);
return policy;
}
- size_t size() const
+ [[nodiscard]] size_t size() const
{
return d_zones.size();
}
#include <sys/mman.h>
#include <unistd.h>
-// On OpenBSD mem used as stack should be marked MAP_STACK
-#ifdef __OpenBSD__
+// On OpenBSD and NetBSD mem used as stack should be marked MAP_STACK
+#if defined(__OpenBSD__) || defined(__NetBSD__)
#define PDNS_MAP_STACK MAP_STACK
#else
#define PDNS_MAP_STACK 0
const auto padding = getAlignmentPadding(requestedSize, pageSize);
const size_type allocatedSize = requestedSize + padding + (pageSize * 2);
-#ifdef __OpenBSD__
- // OpenBSD does not like mmap MAP_STACK regions that have
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+ // OpenBSD and NetBSD don't like mmap MAP_STACK regions that have
// PROT_NONE, so allocate r/w and mprotect the guard pages
- // explictly.
+ // explicitly.
const int protection = PROT_READ | PROT_WRITE;
#else
const int protection = PROT_NONE;
}
char* basePointer = static_cast<char*>(p);
void* usablePointer = basePointer + pageSize;
-#ifdef __OpenBSD__
+#if defined(__OpenBSD__) || defined(__NetBSD__)
int res = mprotect(basePointer, pageSize, PROT_NONE);
if (res != 0) {
munmap(p, allocatedSize);
void Logger::logMessage(const std::string& msg, boost::optional<const std::string> err) const
{
- return logMessage(msg, Logr::Absent, err);
+ return logMessage(msg, Logr::Absent, std::move(err));
}
void Logger::logMessage(const std::string& msg, Logr::Priority p, boost::optional<const std::string> err) const
{
}
Logger::Logger(EntryLogger callback, boost::optional<std::string> name) :
- _callback(callback), _name(name)
+ _callback(callback), _name(std::move(name))
{
}
Logger::Logger(std::shared_ptr<const Logger> parent, boost::optional<std::string> name, size_t verbosity, size_t lvl, EntryLogger callback) :
- _parent(parent), _callback(callback), _name(name), _level(lvl), _verbosity(verbosity)
+ _parent(std::move(parent)), _callback(callback), _name(std::move(name)), _level(lvl), _verbosity(verbosity)
{
}
#include "lua-recursor4.hh"
#include <fstream>
#include "logger.hh"
+#include "logging.hh"
#include "dnsparser.hh"
#include "syncres.hh"
#include "namespaces.hh"
boost::optional<dnsheader> RecursorLua4::DNSQuestion::getDH() const
{
- if (dh)
+ if (dh != nullptr) {
return *dh;
- return boost::optional<dnsheader>();
+ }
+ return {};
}
vector<string> RecursorLua4::DNSQuestion::getEDNSFlags() const
{
vector<string> ret;
- if (ednsFlags) {
- if (*ednsFlags & EDNSOpts::DNSSECOK)
- ret.push_back("DO");
+ if (ednsFlags != nullptr) {
+ if ((*ednsFlags & EDNSOpts::DNSSECOK) != 0) {
+ ret.emplace_back("DO");
+ }
}
return ret;
}
-bool RecursorLua4::DNSQuestion::getEDNSFlag(string flag) const
+bool RecursorLua4::DNSQuestion::getEDNSFlag(const string& flag) const
{
- if (ednsFlags) {
- if (flag == "DO" && (*ednsFlags & EDNSOpts::DNSSECOK))
+ if (ednsFlags != nullptr) {
+ if (flag == "DO" && (*ednsFlags & EDNSOpts::DNSSECOK) != 0) {
return true;
+ }
}
return false;
}
vector<pair<uint16_t, string>> RecursorLua4::DNSQuestion::getEDNSOptions() const
{
- if (ednsOptions)
+ if (ednsOptions != nullptr) {
return *ednsOptions;
- else
- return vector<pair<uint16_t, string>>();
+ }
+ return {};
}
boost::optional<string> RecursorLua4::DNSQuestion::getEDNSOption(uint16_t code) const
{
- if (ednsOptions)
- for (const auto& o : *ednsOptions)
- if (o.first == code)
- return o.second;
-
- return boost::optional<string>();
+ if (ednsOptions != nullptr) {
+ for (const auto& option : *ednsOptions) {
+ if (option.first == code) {
+ return option.second;
+ }
+ }
+ }
+ return {};
}
boost::optional<Netmask> RecursorLua4::DNSQuestion::getEDNSSubnet() const
{
- if (ednsOptions) {
- for (const auto& o : *ednsOptions) {
- if (o.first == EDNSOptionCode::ECS) {
+ if (ednsOptions != nullptr) {
+ for (const auto& option : *ednsOptions) {
+ if (option.first == EDNSOptionCode::ECS) {
EDNSSubnetOpts eso;
- if (getEDNSSubnetOptsFromString(o.second, &eso))
+ if (getEDNSSubnetOptsFromString(option.second, &eso)) {
return eso.source;
- else
- break;
+ }
+ break;
}
}
}
- return boost::optional<Netmask>();
+ return {};
}
std::vector<std::pair<int, ProxyProtocolValue>> RecursorLua4::DNSQuestion::getProxyProtocolValues() const
{
std::vector<std::pair<int, ProxyProtocolValue>> result;
- if (proxyProtocolValues) {
- result.reserve(proxyProtocolValues->size());
-
+ if (proxyProtocolValues != nullptr) {
int idx = 1;
+ result.reserve(proxyProtocolValues->size());
for (const auto& value : *proxyProtocolValues) {
- result.push_back({idx++, value});
+ result.emplace_back(idx++, value);
}
}
-
return result;
}
{
vector<pair<int, DNSRecord>> ret;
int num = 1;
- for (const auto& r : records) {
- ret.push_back({num++, r});
+ ret.reserve(records.size());
+ for (const auto& record : records) {
+ ret.emplace_back(num++, record);
}
return ret;
}
-void RecursorLua4::DNSQuestion::setRecords(const vector<pair<int, DNSRecord>>& recs)
+
+void RecursorLua4::DNSQuestion::setRecords(const vector<pair<int, DNSRecord>>& arg)
{
records.clear();
- for (const auto& p : recs) {
- records.push_back(p.second);
+ for (const auto& pair : arg) {
+ records.push_back(pair.second);
}
}
void RecursorLua4::DNSQuestion::addRecord(uint16_t type, const std::string& content, DNSResourceRecord::Place place, boost::optional<int> ttl, boost::optional<string> name)
{
- DNSRecord dr;
- dr.d_name = name ? DNSName(*name) : qname;
- dr.d_ttl = ttl.get_value_or(3600);
- dr.d_type = type;
- dr.d_place = place;
- dr.setContent(DNSRecordContent::mastermake(type, QClass::IN, content));
- records.push_back(dr);
+ DNSRecord dnsRecord;
+ dnsRecord.d_name = name ? DNSName(*name) : qname;
+ dnsRecord.d_ttl = ttl.get_value_or(3600);
+ dnsRecord.d_type = type;
+ dnsRecord.d_place = place;
+ dnsRecord.setContent(DNSRecordContent::make(type, QClass::IN, content));
+ records.push_back(dnsRecord);
}
void RecursorLua4::DNSQuestion::addAnswer(uint16_t type, const std::string& content, boost::optional<int> ttl, boost::optional<string> name)
{
- addRecord(type, content, DNSResourceRecord::ANSWER, ttl, name);
+ addRecord(type, content, DNSResourceRecord::ANSWER, ttl, std::move(name));
}
struct DynMetric
{
std::atomic<unsigned long>* ptr;
- void inc() { (*ptr)++; }
- void incBy(unsigned int by) { (*ptr) += by; }
- unsigned long get() { return *ptr; }
- void set(unsigned long val) { *ptr = val; }
+ void inc() const { (*ptr)++; }
+ void incBy(unsigned int incr) const { (*ptr) += incr; }
+ [[nodiscard]] unsigned long get() const { return *ptr; }
+ void set(unsigned long val) const { *ptr = val; }
};
// clang-format off
void RecursorLua4::postPrepareContext()
{
- d_lw->registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName& { return dq.qname; }, [](DNSQuestion& /* dq */, const DNSName& newName) { (void) newName; });
- d_lw->registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& /* dq */, uint16_t newType) { (void) newType; });
- d_lw->registerMember<bool (DNSQuestion::*)>("isTcp", [](const DNSQuestion& dq) -> bool { return dq.isTcp; }, [](DNSQuestion& /* dq */, bool newTcp) { (void) newTcp; });
- d_lw->registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress& { return dq.local; }, [](DNSQuestion& /* dq */, const ComboAddress& newLocal) { (void) newLocal; });
- d_lw->registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress& { return dq.remote; }, [](DNSQuestion& /* dq */, const ComboAddress& newRemote) { (void) newRemote; });
- d_lw->registerMember<uint8_t (DNSQuestion::*)>("validationState", [](const DNSQuestion& dq) -> uint8_t { return (vStateIsBogus(dq.validationState) ? /* in order not to break older scripts */ static_cast<uint8_t>(255) : static_cast<uint8_t>(dq.validationState)); }, [](DNSQuestion& /* dq */, uint8_t newState) { (void) newState; });
- d_lw->registerMember<vState (DNSQuestion::*)>("detailedValidationState", [](const DNSQuestion& dq) -> vState { return dq.validationState; }, [](DNSQuestion& /* dq */, vState newState) { (void) newState; });
+ d_lw->registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dnsQuestion) -> const DNSName& { return dnsQuestion.qname; }, [](DNSQuestion& /* dnsQuestion */, const DNSName& newName) { (void) newName; });
+ d_lw->registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.qtype; }, [](DNSQuestion& /* dnsQuestion */, uint16_t newType) { (void) newType; });
+ d_lw->registerMember<bool (DNSQuestion::*)>("isTcp", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.isTcp; }, [](DNSQuestion& /* dnsQuestion */, bool newTcp) { (void) newTcp; });
+ d_lw->registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dnsQuestion) -> const ComboAddress& { return dnsQuestion.local; }, [](DNSQuestion& /* dnsQuestion */, const ComboAddress& newLocal) { (void) newLocal; });
+ d_lw->registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dnsQuestion) -> const ComboAddress& { return dnsQuestion.remote; }, [](DNSQuestion& /* dnsQuestion */, const ComboAddress& newRemote) { (void) newRemote; });
+ d_lw->registerMember<uint8_t (DNSQuestion::*)>("validationState", [](const DNSQuestion& dnsQuestion) -> uint8_t { return (vStateIsBogus(dnsQuestion.validationState) ? /* in order not to break older scripts */ static_cast<uint8_t>(255) : static_cast<uint8_t>(dnsQuestion.validationState)); }, [](DNSQuestion& /* dnsQuestion */, uint8_t newState) { (void) newState; });
+ d_lw->registerMember<vState (DNSQuestion::*)>("detailedValidationState", [](const DNSQuestion& dnsQuestion) -> vState { return dnsQuestion.validationState; }, [](DNSQuestion& /* dnsQuestion */, vState newState) { (void) newState; });
- d_lw->registerMember<bool (DNSQuestion::*)>("variable", [](const DNSQuestion& dq) -> bool { return dq.variable; }, [](DNSQuestion& dq, bool newVariable) { dq.variable = newVariable; });
- d_lw->registerMember<bool (DNSQuestion::*)>("wantsRPZ", [](const DNSQuestion& dq) -> bool { return dq.wantsRPZ; }, [](DNSQuestion& dq, bool newWantsRPZ) { dq.wantsRPZ = newWantsRPZ; });
- d_lw->registerMember<bool (DNSQuestion::*)>("logResponse", [](const DNSQuestion& dq) -> bool { return dq.logResponse; }, [](DNSQuestion& dq, bool newLogResponse) { dq.logResponse = newLogResponse; });
- d_lw->registerMember<bool (DNSQuestion::*)>("addPaddingToResponse", [](const DNSQuestion& dq) -> bool { return dq.addPaddingToResponse; }, [](DNSQuestion& dq, bool add) { dq.addPaddingToResponse = add; });
+ d_lw->registerMember<bool (DNSQuestion::*)>("variable", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.variable; }, [](DNSQuestion& dnsQuestion, bool newVariable) { dnsQuestion.variable = newVariable; });
+ d_lw->registerMember<bool (DNSQuestion::*)>("wantsRPZ", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.wantsRPZ; }, [](DNSQuestion& dnsQuestion, bool newWantsRPZ) { dnsQuestion.wantsRPZ = newWantsRPZ; });
+ d_lw->registerMember<bool (DNSQuestion::*)>("logResponse", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.logResponse; }, [](DNSQuestion& dnsQuestion, bool newLogResponse) { dnsQuestion.logResponse = newLogResponse; });
+ d_lw->registerMember<bool (DNSQuestion::*)>("addPaddingToResponse", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.addPaddingToResponse; }, [](DNSQuestion& dnsQuestion, bool add) { dnsQuestion.addPaddingToResponse = add; });
d_lw->registerMember("rcode", &DNSQuestion::rcode);
d_lw->registerMember("tag", &DNSQuestion::tag);
d_lw->registerMember("followupPrefix", &DNSQuestion::followupPrefix);
d_lw->registerMember("followupName", &DNSQuestion::followupName);
d_lw->registerMember("data", &DNSQuestion::data);
- d_lw->registerMember<uint16_t (DNSQuestion::*)>("extendedErrorCode", [](const DNSQuestion& dq) -> uint16_t {
- if (dq.extendedErrorCode && *dq.extendedErrorCode) {
- return *(*dq.extendedErrorCode);
+ d_lw->registerMember<uint16_t (DNSQuestion::*)>("extendedErrorCode", [](const DNSQuestion& dnsQuestion) -> uint16_t {
+ if (dnsQuestion.extendedErrorCode != nullptr && *dnsQuestion.extendedErrorCode) {
+ return *(*dnsQuestion.extendedErrorCode);
}
return 0;
},
- [](DNSQuestion& dq, uint16_t newCode) {
- if (dq.extendedErrorCode) {
- *dq.extendedErrorCode = newCode;
+ [](DNSQuestion& dnsQuestion, uint16_t newCode) {
+ if (dnsQuestion.extendedErrorCode != nullptr) {
+ *dnsQuestion.extendedErrorCode = newCode;
}
});
- d_lw->registerMember<std::string (DNSQuestion::*)>("extendedErrorExtra", [](const DNSQuestion& dq) -> std::string {
- if (dq.extendedErrorExtra) {
- return *dq.extendedErrorExtra;
+ d_lw->registerMember<std::string (DNSQuestion::*)>("extendedErrorExtra", [](const DNSQuestion& dnsQuestion) -> std::string {
+ if (dnsQuestion.extendedErrorExtra != nullptr) {
+ return *dnsQuestion.extendedErrorExtra;
}
return "";
},
- [](DNSQuestion& dq, const std::string& newExtra) {
- if (dq.extendedErrorExtra) {
- *dq.extendedErrorExtra = newExtra;
+ [](DNSQuestion& dnsQuestion, const std::string& newExtra) {
+ if (dnsQuestion.extendedErrorExtra != nullptr) {
+ *dnsQuestion.extendedErrorExtra = newExtra;
}
});
d_lw->registerMember("udpQuery", &DNSQuestion::udpQuery);
d_lw->registerMember("policyKind", &DNSFilterEngine::Policy::d_kind);
d_lw->registerMember("policyType", &DNSFilterEngine::Policy::d_type);
d_lw->registerMember("policyTTL", &DNSFilterEngine::Policy::d_ttl);
- d_lw->registerMember("policyTrigger", &DNSFilterEngine::Policy::d_trigger);
- d_lw->registerMember("policyHit", &DNSFilterEngine::Policy::d_hit);
+ d_lw->registerMember<DNSFilterEngine::Policy, DNSName>("policyTrigger",
+ [](const DNSFilterEngine::Policy& pol) {
+ return pol.getTrigger();
+ },
+ [](DNSFilterEngine::Policy& pol, const DNSName& dnsname) {
+ pol.setHitData(dnsname, pol.getHit());
+ });
+ d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyHit",
+ [](const DNSFilterEngine::Policy& pol) {
+ return pol.getHit();
+ },
+ [](DNSFilterEngine::Policy& pol, const std::string& hit) {
+ pol.setHitData(pol.getTrigger(), hit);
+ });
d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
[](const DNSFilterEngine::Policy& pol) -> std::string {
std::string result;
return result;
}
- for (const auto& dr : pol.d_custom) {
- if (!result.empty()) {
- result += "\n";
+ if (pol.d_custom) {
+ for (const auto& dnsRecord : *pol.d_custom) {
+ if (!result.empty()) {
+ result += "\n";
+ }
+ result += dnsRecord->getZoneRepresentation();
}
- result += dr->getZoneRepresentation();
}
return result;
},
[](DNSFilterEngine::Policy& pol, const std::string& content) {
// Only CNAMES for now, when we ever add a d_custom_type, there will be pain
- pol.d_custom.clear();
- pol.d_custom.push_back(DNSRecordContent::mastermake(QType::CNAME, QClass::IN, content));
+ pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+ pol.d_custom->push_back(DNSRecordContent::make(QType::CNAME, QClass::IN, content));
}
);
d_lw->registerFunction("getDH", &DNSQuestion::getDH);
d_lw->registerFunction<size_t(EDNSOptionView::*)()>("count", [](const EDNSOptionView& option) { return option.values.size(); });
d_lw->registerFunction<std::vector<string>(EDNSOptionView::*)()>("getValues", [] (const EDNSOptionView& option) {
std::vector<string> values;
+ values.reserve(option.values.size());
for (const auto& value : option.values) {
- values.push_back(std::string(value.content, value.size));
+ values.emplace_back(value.content, value.size);
}
return values;
});
}
return std::string(option.values.at(0).content, option.values.at(0).size); });
- d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dr) { return dr.getContent()->getZoneRepresentation(); });
- d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dr) {
+ d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dnsRecord) { return dnsRecord.getContent()->getZoneRepresentation(); });
+ d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dnsRecord) {
boost::optional<ComboAddress> ret;
- if(auto rec = getRR<ARecordContent>(dr))
- ret=rec->getCA(53);
- else if(auto aaaarec = getRR<AAAARecordContent>(dr))
- ret=aaaarec->getCA(53);
+ if (auto rec = getRR<ARecordContent>(dnsRecord)) {
+ ret = rec->getCA(53);
+ }
+ else if (auto aaaarec = getRR<AAAARecordContent>(dnsRecord)) {
+ ret = aaaarec->getCA(53);
+ }
return ret;
});
- d_lw->registerFunction<const ProxyProtocolValue, std::string()>("getContent", [](const ProxyProtocolValue& value) { return value.content; });
+ d_lw->registerFunction<const ProxyProtocolValue, std::string()>("getContent", [](const ProxyProtocolValue& value) -> std::string { return value.content; });
d_lw->registerFunction<const ProxyProtocolValue, uint8_t()>("getType", [](const ProxyProtocolValue& value) { return value.type; });
- d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.setContent(DNSRecordContent::mastermake(dr.d_type, QClass::IN, newContent)); });
+ d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dnsRecord, const std::string& newContent) { dnsRecord.setContent(DNSRecordContent::make(dnsRecord.d_type, QClass::IN, newContent)); });
d_lw->registerFunction("addAnswer", &DNSQuestion::addAnswer);
d_lw->registerFunction("addRecord", &DNSQuestion::addRecord);
d_lw->registerFunction("getRecords", &DNSQuestion::getRecords);
d_lw->registerFunction("setRecords", &DNSQuestion::setRecords);
- d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("addPolicyTag", [](DNSQuestion& dq, const std::string& tag) { if (dq.policyTags) { dq.policyTags->insert(tag); } });
- d_lw->registerFunction<void(DNSQuestion::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](DNSQuestion& dq, const std::vector<std::pair<int, std::string> >& tags) {
- if (dq.policyTags) {
- dq.policyTags->clear();
- dq.policyTags->reserve(tags.size());
+ d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("addPolicyTag", [](DNSQuestion& dnsQuestion, const std::string& tag) { if (dnsQuestion.policyTags != nullptr) { dnsQuestion.policyTags->insert(tag); } });
+ d_lw->registerFunction<void(DNSQuestion::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](DNSQuestion& dnsQuestion, const std::vector<std::pair<int, std::string> >& tags) {
+ if (dnsQuestion.policyTags != nullptr) {
+ dnsQuestion.policyTags->clear();
+ dnsQuestion.policyTags->reserve(tags.size());
for (const auto& tag : tags) {
- dq.policyTags->insert(tag.second);
+ dnsQuestion.policyTags->insert(tag.second);
}
}
});
- d_lw->registerFunction<std::vector<std::pair<int, std::string> >(DNSQuestion::*)()>("getPolicyTags", [](const DNSQuestion& dq) {
+ d_lw->registerFunction<std::vector<std::pair<int, std::string> >(DNSQuestion::*)()>("getPolicyTags", [](const DNSQuestion& dnsQuestion) {
std::vector<std::pair<int, std::string> > ret;
- if (dq.policyTags) {
+ if (dnsQuestion.policyTags != nullptr) {
int count = 1;
- ret.reserve(dq.policyTags->size());
- for (const auto& tag : *dq.policyTags) {
- ret.push_back({count++, tag});
+ ret.reserve(dnsQuestion.policyTags->size());
+ for (const auto& tag : *dnsQuestion.policyTags) {
+ ret.emplace_back(count++, tag);
}
}
return ret;
});
- d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("discardPolicy", [](DNSQuestion& dq, const std::string& policy) {
- if (dq.discardedPolicies) {
- (*dq.discardedPolicies)[policy] = true;
+ d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("discardPolicy", [](DNSQuestion& dnsQuestion, const std::string& policy) {
+ if (dnsQuestion.discardedPolicies != nullptr) {
+ (*dnsQuestion.discardedPolicies)[policy] = true;
}
});
d_lw->writeFunction("newDS", []() { return SuffixMatchNode(); });
d_lw->registerFunction<void(SuffixMatchNode::*)(boost::variant<string,DNSName, vector<pair<unsigned int,string> > >)>(
"add",
- [](SuffixMatchNode&smn, const boost::variant<string,DNSName,vector<pair<unsigned int,string> > >& in){
+ [](SuffixMatchNode&smn, const boost::variant<string,DNSName,vector<pair<unsigned int,string> > >& arg){
try {
- if(auto s = boost::get<string>(&in)) {
- smn.add(DNSName(*s));
+ if (const auto *name = boost::get<string>(&arg)) {
+ smn.add(DNSName(*name));
}
- else if(auto v = boost::get<vector<pair<unsigned int, string> > >(&in)) {
- for(const auto& entry : *v)
+ else if (const auto *vec = boost::get<vector<pair<unsigned int, string> > >(&arg)) {
+ for (const auto& entry : *vec) {
smn.add(DNSName(entry.second));
+ }
}
else {
- smn.add(boost::get<DNSName>(in));
+ smn.add(boost::get<DNSName>(arg));
}
}
catch(std::exception& e) {
- g_log <<Logger::Error<<e.what()<<endl;
+ SLOG(g_log <<Logger::Error<<e.what()<<endl,
+ g_slog->withName("lua")->error(Logr::Error, e.what(), "Error in call to DNSSuffixMatchGroup:add"));
}
}
);
{"NSIP", (int)DNSFilterEngine::PolicyType::NSIP }
}});
- for(const auto& n : QType::names)
- d_pd.push_back({n.first, n.second});
+ for(const auto& name : QType::names) {
+ d_pd.emplace_back(name.first, name.second);
+ }
d_pd.push_back({"validationstates", in_t{
{"Indeterminate", static_cast<unsigned int>(vState::Indeterminate) },
return vStateIsBogus(state);
});
- d_pd.push_back({"now", &g_now});
+ d_pd.emplace_back("now", &g_now);
d_lw->writeFunction("getMetric", [](const std::string& str, boost::optional<std::string> prometheusName) {
return DynMetric{getDynMetric(str, prometheusName ? *prometheusName : "")};
d_lw->registerMember<bool (PolicyEvent::*)>("isTcp", [](const PolicyEvent& event) -> bool { return event.isTcp; }, [](PolicyEvent& /* event */, bool newTcp) { (void) newTcp; });
d_lw->registerMember<const ComboAddress (PolicyEvent::*)>("remote", [](const PolicyEvent& event) -> const ComboAddress& { return event.remote; }, [](PolicyEvent& /* event */, const ComboAddress& newRemote) { (void) newRemote; });
d_lw->registerMember("appliedPolicy", &PolicyEvent::appliedPolicy);
- d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("addPolicyTag", [](PolicyEvent& event, const std::string& tag) { if (event.policyTags) { event.policyTags->insert(tag); } });
+ d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("addPolicyTag", [](PolicyEvent& event, const std::string& tag) { if (event.policyTags != nullptr) { event.policyTags->insert(tag); } });
d_lw->registerFunction<void(PolicyEvent::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](PolicyEvent& event, const std::vector<std::pair<int, std::string> >& tags) {
- if (event.policyTags) {
+ if (event.policyTags != nullptr) {
event.policyTags->clear();
event.policyTags->reserve(tags.size());
for (const auto& tag : tags) {
});
d_lw->registerFunction<std::vector<std::pair<int, std::string> >(PolicyEvent::*)()>("getPolicyTags", [](const PolicyEvent& event) {
std::vector<std::pair<int, std::string> > ret;
- if (event.policyTags) {
+ if (event.policyTags != nullptr) {
int count = 1;
ret.reserve(event.policyTags->size());
for (const auto& tag : *event.policyTags) {
- ret.push_back({count++, tag});
+ ret.emplace_back(count++, tag);
}
}
return ret;
});
d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("discardPolicy", [](PolicyEvent& event, const std::string& policy) {
- if (event.discardedPolicies) {
+ if (event.discardedPolicies != nullptr) {
(*event.discardedPolicies)[policy] = true;
}
});
void RecursorLua4::postLoad()
{
- d_prerpz = d_lw->readVariable<boost::optional<luacall_t>>("prerpz").get_value_or(0);
- d_preresolve = d_lw->readVariable<boost::optional<luacall_t>>("preresolve").get_value_or(0);
- d_nodata = d_lw->readVariable<boost::optional<luacall_t>>("nodata").get_value_or(0);
- d_nxdomain = d_lw->readVariable<boost::optional<luacall_t>>("nxdomain").get_value_or(0);
- d_postresolve = d_lw->readVariable<boost::optional<luacall_t>>("postresolve").get_value_or(0);
- d_preoutquery = d_lw->readVariable<boost::optional<luacall_t>>("preoutquery").get_value_or(0);
- d_maintenance = d_lw->readVariable<boost::optional<luamaintenance_t>>("maintenance").get_value_or(0);
+ d_prerpz = d_lw->readVariable<boost::optional<luacall_t>>("prerpz").get_value_or(nullptr);
+ d_preresolve = d_lw->readVariable<boost::optional<luacall_t>>("preresolve").get_value_or(nullptr);
+ d_nodata = d_lw->readVariable<boost::optional<luacall_t>>("nodata").get_value_or(nullptr);
+ d_nxdomain = d_lw->readVariable<boost::optional<luacall_t>>("nxdomain").get_value_or(nullptr);
+ d_postresolve = d_lw->readVariable<boost::optional<luacall_t>>("postresolve").get_value_or(nullptr);
+ d_preoutquery = d_lw->readVariable<boost::optional<luacall_t>>("preoutquery").get_value_or(nullptr);
+ d_maintenance = d_lw->readVariable<boost::optional<luamaintenance_t>>("maintenance").get_value_or(nullptr);
- d_ipfilter = d_lw->readVariable<boost::optional<ipfilter_t>>("ipfilter").get_value_or(0);
- d_gettag = d_lw->readVariable<boost::optional<gettag_t>>("gettag").get_value_or(0);
- d_gettag_ffi = d_lw->readVariable<boost::optional<gettag_ffi_t>>("gettag_ffi").get_value_or(0);
- d_postresolve_ffi = d_lw->readVariable<boost::optional<postresolve_ffi_t>>("postresolve_ffi").get_value_or(0);
+ d_ipfilter = d_lw->readVariable<boost::optional<ipfilter_t>>("ipfilter").get_value_or(nullptr);
+ d_gettag = d_lw->readVariable<boost::optional<gettag_t>>("gettag").get_value_or(nullptr);
+ d_gettag_ffi = d_lw->readVariable<boost::optional<gettag_ffi_t>>("gettag_ffi").get_value_or(nullptr);
+ d_postresolve_ffi = d_lw->readVariable<boost::optional<postresolve_ffi_t>>("postresolve_ffi").get_value_or(nullptr);
- d_policyHitEventFilter = d_lw->readVariable<boost::optional<policyEventFilter_t>>("policyEventFilter").get_value_or(0);
+ d_policyHitEventFilter = d_lw->readVariable<boost::optional<policyEventFilter_t>>("policyEventFilter").get_value_or(nullptr);
}
void RecursorLua4::getFeatures(Features& features)
features.emplace_back("PR8001_devicename", true);
}
-static void warnDrop(const RecursorLua4::DNSQuestion& dq)
+static void warnDrop(const RecursorLua4::DNSQuestion& dnsQuestion)
{
- if (dq.rcode == -2) {
- g_log << Logger::Error << "Returning -2 (pdns.DROP) is not supported anymore, see https://docs.powerdns.com/recursor/lua-scripting/hooks.html#hooksemantics" << endl;
+ if (dnsQuestion.rcode == -2) {
+ SLOG(g_log << Logger::Error << "Returning -2 (pdns.DROP) is not supported anymore, see https://docs.powerdns.com/recursor/lua-scripting/hooks.html#hooksemantics" << endl,
+ g_slog->withName("lua")->info(Logr::Error, "Returning -2 (pdns.DROP) is not supported anymore, see https://docs.powerdns.com/recursor/lua-scripting/hooks.html#hooksemantics"));
// We *could* set policy here, but that would also mean interfering with rcode and the return code of the hook.
// So leave it at the error message.
}
}
}
-bool RecursorLua4::prerpz(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::prerpz(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
{
if (!d_prerpz) {
return false;
}
- et.add(RecEventTrace::LuaPreRPZ);
- bool ok = genhook(d_prerpz, dq, ret);
- et.add(RecEventTrace::LuaPreRPZ, ok, false);
- warnDrop(dq);
- return ok;
+ eventTrace.add(RecEventTrace::LuaPreRPZ);
+ bool isOK = genhook(d_prerpz, dnsQuestion, ret);
+ eventTrace.add(RecEventTrace::LuaPreRPZ, isOK, false);
+ warnDrop(dnsQuestion);
+ return isOK;
}
-bool RecursorLua4::preresolve(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::preresolve(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
{
if (!d_preresolve) {
return false;
}
- et.add(RecEventTrace::LuaPreResolve);
- bool ok = genhook(d_preresolve, dq, ret);
- et.add(RecEventTrace::LuaPreResolve, ok, false);
- warnDrop(dq);
- return ok;
+ eventTrace.add(RecEventTrace::LuaPreResolve);
+ bool isOK = genhook(d_preresolve, dnsQuestion, ret);
+ eventTrace.add(RecEventTrace::LuaPreResolve, isOK, false);
+ warnDrop(dnsQuestion);
+ return isOK;
}
-bool RecursorLua4::nxdomain(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::nxdomain(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
{
if (!d_nxdomain) {
return false;
}
- et.add(RecEventTrace::LuaNXDomain);
- bool ok = genhook(d_nxdomain, dq, ret);
- et.add(RecEventTrace::LuaNXDomain, ok, false);
- warnDrop(dq);
- return ok;
+ eventTrace.add(RecEventTrace::LuaNXDomain);
+ bool isOK = genhook(d_nxdomain, dnsQuestion, ret);
+ eventTrace.add(RecEventTrace::LuaNXDomain, isOK, false);
+ warnDrop(dnsQuestion);
+ return isOK;
}
-bool RecursorLua4::nodata(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::nodata(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
{
if (!d_nodata) {
return false;
}
- et.add(RecEventTrace::LuaNoData);
- bool ok = genhook(d_nodata, dq, ret);
- et.add(RecEventTrace::LuaNoData, ok, false);
- warnDrop(dq);
- return ok;
+ eventTrace.add(RecEventTrace::LuaNoData);
+ bool isOK = genhook(d_nodata, dnsQuestion, ret);
+ eventTrace.add(RecEventTrace::LuaNoData, isOK, false);
+ warnDrop(dnsQuestion);
+ return isOK;
}
-bool RecursorLua4::postresolve(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::postresolve(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
{
if (!d_postresolve) {
return false;
}
- et.add(RecEventTrace::LuaPostResolve);
- bool ok = genhook(d_postresolve, dq, ret);
- et.add(RecEventTrace::LuaPostResolve, ok, false);
- warnDrop(dq);
- return ok;
+ eventTrace.add(RecEventTrace::LuaPostResolve);
+ bool isOK = genhook(d_postresolve, dnsQuestion, ret);
+ eventTrace.add(RecEventTrace::LuaPostResolve, isOK, false);
+ warnDrop(dnsQuestion);
+ return isOK;
}
-bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& et, const struct timeval& tv) const
+bool RecursorLua4::preoutquery(const ComboAddress& nameserver, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& eventTrace, const struct timeval& theTime) const
{
if (!d_preoutquery) {
return false;
bool wantsRPZ = false;
bool logQuery = false;
bool addPaddingToResponse = false;
- RecursorLua4::DNSQuestion dq(ns, requestor, query, qtype.getCode(), isTcp, variableAnswer, wantsRPZ, logQuery, addPaddingToResponse, tv);
- dq.currentRecords = &res;
- et.add(RecEventTrace::LuaPreOutQuery);
- bool ok = genhook(d_preoutquery, dq, ret);
- et.add(RecEventTrace::LuaPreOutQuery, ok, false);
- warnDrop(dq);
- return ok;
+ RecursorLua4::DNSQuestion dnsQuestion(nameserver, requestor, query, qtype.getCode(), isTcp, variableAnswer, wantsRPZ, logQuery, addPaddingToResponse, theTime);
+ dnsQuestion.currentRecords = &res;
+ eventTrace.add(RecEventTrace::LuaPreOutQuery);
+ bool isOK = genhook(d_preoutquery, dnsQuestion, ret);
+ eventTrace.add(RecEventTrace::LuaPreOutQuery, isOK, false);
+ warnDrop(dnsQuestion);
+ return isOK;
}
-bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader& dh, RecEventTrace& et) const
+bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader& header, RecEventTrace& eventTrace) const
{
if (!d_ipfilter) {
return false; // Do not block
}
- et.add(RecEventTrace::LuaIPFilter);
- bool ok = d_ipfilter(remote, local, dh);
- et.add(RecEventTrace::LuaIPFilter, ok, false);
- return ok;
+ eventTrace.add(RecEventTrace::LuaIPFilter);
+ bool isOK = d_ipfilter(remote, local, header);
+ eventTrace.add(RecEventTrace::LuaIPFilter, isOK, false);
+ return isOK;
}
bool RecursorLua4::policyHitEventFilter(const ComboAddress& remote, const DNSName& qname, const QType& qtype, bool tcp, DNSFilterEngine::Policy& policy, std::unordered_set<std::string>& tags, std::unordered_map<std::string, bool>& discardedPolicies) const
event.policyTags = &tags;
event.discardedPolicies = &discardedPolicies;
- if (d_policyHitEventFilter(event)) {
- return true;
- }
- else {
- return false;
- }
+ return d_policyHitEventFilter(event);
}
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const
{
if (d_gettag) {
auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp, proxyProtocolValuesMap);
- if (policyTags) {
+ if (policyTags != nullptr) {
const auto& tags = std::get<1>(ret);
if (tags) {
policyTags->reserve(policyTags->size() + tags->size());
}
}
}
- const auto dataret = std::get<2>(ret);
+ const auto& dataret = std::get<2>(ret);
if (dataret) {
data = *dataret;
}
- const auto reqIdret = std::get<3>(ret);
+ const auto& reqIdret = std::get<3>(ret);
if (reqIdret) {
requestorId = *reqIdret;
}
- const auto deviceIdret = std::get<4>(ret);
+ const auto& deviceIdret = std::get<4>(ret);
if (deviceIdret) {
deviceId = *deviceIdret;
}
- const auto deviceNameret = std::get<5>(ret);
+ const auto& deviceNameret = std::get<5>(ret);
if (deviceNameret) {
deviceName = *deviceNameret;
}
- const auto routingTarget = std::get<6>(ret);
+ const auto& routingTarget = std::get<6>(ret);
if (routingTarget) {
routingTag = *routingTarget;
}
return 0;
}
-bool RecursorLua4::genhook(const luacall_t& func, DNSQuestion& dq, int& ret) const
+bool RecursorLua4::genhook(const luacall_t& func, DNSQuestion& dnsQuestion, int& ret) const
{
- if (!func)
+ if (!func) {
return false;
+ }
- if (dq.currentRecords) {
- dq.records = *dq.currentRecords;
+ if (dnsQuestion.currentRecords != nullptr) {
+ dnsQuestion.records = *dnsQuestion.currentRecords;
}
else {
- dq.records.clear();
+ dnsQuestion.records.clear();
}
- dq.followupFunction.clear();
- dq.followupPrefix.clear();
- dq.followupName.clear();
- dq.udpQuery.clear();
- dq.udpAnswer.clear();
- dq.udpCallback.clear();
+ dnsQuestion.followupFunction.clear();
+ dnsQuestion.followupPrefix.clear();
+ dnsQuestion.followupName.clear();
+ dnsQuestion.udpQuery.clear();
+ dnsQuestion.udpAnswer.clear();
+ dnsQuestion.udpCallback.clear();
+
+ dnsQuestion.rcode = ret;
+ bool handled = func(&dnsQuestion);
- dq.rcode = ret;
- bool handled = func(&dq);
+ if (!handled) {
+ return false;
+ }
- if (handled) {
- loop:;
- ret = dq.rcode;
+ while (true) {
+ ret = dnsQuestion.rcode;
- if (!dq.followupFunction.empty()) {
- if (dq.followupFunction == "followCNAMERecords") {
- ret = followCNAMERecords(dq.records, QType(dq.qtype), ret);
+ if (!dnsQuestion.followupFunction.empty()) {
+ if (dnsQuestion.followupFunction == "followCNAMERecords") {
+ ret = followCNAMERecords(dnsQuestion.records, QType(dnsQuestion.qtype), ret);
}
- else if (dq.followupFunction == "getFakeAAAARecords") {
- ret = getFakeAAAARecords(dq.followupName, ComboAddress(dq.followupPrefix), dq.records);
+ else if (dnsQuestion.followupFunction == "getFakeAAAARecords") {
+ ret = getFakeAAAARecords(dnsQuestion.followupName, ComboAddress(dnsQuestion.followupPrefix), dnsQuestion.records);
}
- else if (dq.followupFunction == "getFakePTRRecords") {
- ret = getFakePTRRecords(dq.followupName, dq.records);
+ else if (dnsQuestion.followupFunction == "getFakePTRRecords") {
+ ret = getFakePTRRecords(dnsQuestion.followupName, dnsQuestion.records);
}
- else if (dq.followupFunction == "udpQueryResponse") {
- PacketBuffer p = GenUDPQueryResponse(dq.udpQueryDest, dq.udpQuery);
- dq.udpAnswer = std::string(reinterpret_cast<const char*>(p.data()), p.size());
- auto cbFunc = d_lw->readVariable<boost::optional<luacall_t>>(dq.udpCallback).get_value_or(0);
+ else if (dnsQuestion.followupFunction == "udpQueryResponse") {
+ PacketBuffer packetBuffer = GenUDPQueryResponse(dnsQuestion.udpQueryDest, dnsQuestion.udpQuery);
+ dnsQuestion.udpAnswer = std::string(reinterpret_cast<const char*>(packetBuffer.data()), packetBuffer.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ // coverity[auto_causes_copy] not copying produces a dangling ref
+ const auto cbFunc = d_lw->readVariable<boost::optional<luacall_t>>(dnsQuestion.udpCallback).get_value_or(nullptr);
if (!cbFunc) {
- g_log << Logger::Error << "Attempted callback for Lua UDP Query/Response which could not be found" << endl;
+ SLOG(g_log << Logger::Error << "Attempted callback for Lua UDP Query/Response which could not be found" << endl,
+ g_slog->withName("lua")->info(Logr::Error, "Attempted callback for Lua UDP Query/Response which could not be found"));
return false;
}
- bool result = cbFunc(&dq);
+ bool result = cbFunc(&dnsQuestion);
if (!result) {
return false;
}
- goto loop;
+ continue;
}
}
- if (dq.currentRecords) {
- *dq.currentRecords = dq.records;
+ if (dnsQuestion.currentRecords != nullptr) {
+ *dnsQuestion.currentRecords = dnsQuestion.records;
}
+ break;
}
// see if they added followup work for us too
- return handled;
+ return true;
}
-RecursorLua4::~RecursorLua4() {}
+RecursorLua4::~RecursorLua4() = default;
const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref)
{
return ref->remoteStr->c_str();
}
-static void pdns_ffi_comboaddress_to_raw(const ComboAddress& ca, const void** addr, size_t* addrSize)
+static void pdns_ffi_comboaddress_to_raw(const ComboAddress& address, const void** addr, size_t* addrSize)
{
- if (ca.isIPv4()) {
- *addr = &ca.sin4.sin_addr.s_addr;
- *addrSize = sizeof(ca.sin4.sin_addr.s_addr);
+ if (address.isIPv4()) {
+ *addr = &address.sin4.sin_addr.s_addr;
+ *addrSize = sizeof(address.sin4.sin_addr.s_addr);
}
else {
- *addr = &ca.sin6.sin6_addr.s6_addr;
- *addrSize = sizeof(ca.sin6.sin6_addr.s6_addr);
+ *addr = &address.sin6.sin6_addr.s6_addr;
+ *addrSize = sizeof(address.sin6.sin6_addr.s6_addr);
}
}
size_t pdns_ffi_param_get_edns_options_by_code(pdns_ffi_param_t* ref, uint16_t optionCode, const pdns_ednsoption_t** out)
{
- const auto& it = ref->params.ednsOptions.find(optionCode);
- if (it == ref->params.ednsOptions.cend() || it->second.values.empty()) {
+ const auto iter = ref->params.ednsOptions.find(optionCode);
+ if (iter == ref->params.ednsOptions.cend() || iter->second.values.empty()) {
return 0;
}
- ref->ednsOptionsVect.resize(it->second.values.size());
+ ref->ednsOptionsVect.resize(iter->second.values.size());
size_t pos = 0;
- for (const auto& entry : it->second.values) {
+ for (const auto& entry : iter->second.values) {
fill_edns_option(entry, ref->ednsOptionsVect.at(pos));
ref->ednsOptionsVect.at(pos).optionCode = optionCode;
pos++;
void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name)
{
- ref->params.deviceId = std::string(reinterpret_cast<const char*>(name), len);
+ ref->params.deviceId = std::string(reinterpret_cast<const char*>(name), len); // NOLINT: It's the API
}
-void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* rtag)
+void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* name)
{
- ref->params.routingTag = std::string(rtag);
+ ref->params.routingTag = std::string(name);
}
void pdns_ffi_param_set_variable(pdns_ffi_param_t* ref, bool variable)
bool pdns_ffi_param_add_record(pdns_ffi_param_t* ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentSize, pdns_record_place_t place)
{
try {
- DNSRecord dr;
- dr.d_name = name != nullptr ? DNSName(name) : ref->params.qname;
- dr.d_ttl = ttl;
- dr.d_type = type;
- dr.d_class = QClass::IN;
- dr.d_place = DNSResourceRecord::Place(place);
- dr.setContent(DNSRecordContent::mastermake(type, QClass::IN, std::string(content, contentSize)));
- ref->params.records.push_back(std::move(dr));
+ DNSRecord dnsRecord;
+ dnsRecord.d_name = name != nullptr ? DNSName(name) : ref->params.qname;
+ dnsRecord.d_ttl = ttl;
+ dnsRecord.d_type = type;
+ dnsRecord.d_class = QClass::IN;
+ dnsRecord.d_place = DNSResourceRecord::Place(place);
+ dnsRecord.setContent(DNSRecordContent::make(type, QClass::IN, std::string(content, contentSize)));
+ ref->params.records.push_back(std::move(dnsRecord));
return true;
}
struct pdns_postresolve_ffi_handle
{
public:
- pdns_postresolve_ffi_handle(RecursorLua4::PostResolveFFIHandle& h) :
- handle(h)
+ pdns_postresolve_ffi_handle(RecursorLua4::PostResolveFFIHandle& arg) :
+ handle(arg)
{
}
- RecursorLua4::PostResolveFFIHandle& handle;
+
auto insert(std::string&& str)
{
- const auto it = pool.insert(std::move(str)).first;
- return it;
+ const auto iter = pool.insert(std::move(str)).first;
+ return iter;
+ }
+
+ [[nodiscard]] const RecursorLua4::PostResolveFFIHandle& getHandle() const
+ {
+ return handle;
}
private:
+ RecursorLua4::PostResolveFFIHandle& handle;
std::unordered_set<std::string> pool;
};
-bool RecursorLua4::postresolve_ffi(RecursorLua4::PostResolveFFIHandle& h) const
+bool RecursorLua4::postresolve_ffi(RecursorLua4::PostResolveFFIHandle& arg) const
{
if (d_postresolve_ffi) {
- pdns_postresolve_ffi_handle_t handle(h);
+ pdns_postresolve_ffi_handle_t handle(arg);
auto ret = d_postresolve_ffi(&handle);
return ret;
const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref)
{
- auto str = ref->insert(ref->handle.d_dq.qname.toStringNoDot());
+ auto str = ref->insert(ref->getHandle().d_dq.qname.toStringNoDot());
return str->c_str();
}
void pdns_postresolve_ffi_handle_get_qname_raw(pdns_postresolve_ffi_handle_t* ref, const char** qname, size_t* qnameSize)
{
- const auto& storage = ref->handle.d_dq.qname.getStorage();
+ const auto& storage = ref->getHandle().d_dq.qname.getStorage();
*qname = storage.data();
*qnameSize = storage.size();
}
uint16_t pdns_postresolve_ffi_handle_get_qtype(const pdns_postresolve_ffi_handle_t* ref)
{
- return ref->handle.d_dq.qtype;
+ return ref->getHandle().d_dq.qtype;
}
uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref)
{
- return ref->handle.d_dq.rcode;
+ return ref->getHandle().d_dq.rcode;
}
void pdns_postresolve_ffi_handle_set_rcode(const pdns_postresolve_ffi_handle_t* ref, uint16_t rcode)
{
- ref->handle.d_dq.rcode = rcode;
+ ref->getHandle().d_dq.rcode = rcode;
}
pdns_policy_kind_t pdns_postresolve_ffi_handle_get_appliedpolicy_kind(const pdns_postresolve_ffi_handle_t* ref)
{
- return static_cast<pdns_policy_kind_t>(ref->handle.d_dq.appliedPolicy->d_kind);
+ return static_cast<pdns_policy_kind_t>(ref->getHandle().d_dq.appliedPolicy->d_kind);
}
void pdns_postresolve_ffi_handle_set_appliedpolicy_kind(pdns_postresolve_ffi_handle_t* ref, pdns_policy_kind_t kind)
{
- ref->handle.d_dq.appliedPolicy->d_kind = static_cast<DNSFilterEngine::PolicyKind>(kind);
+ ref->getHandle().d_dq.appliedPolicy->d_kind = static_cast<DNSFilterEngine::PolicyKind>(kind);
}
-bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t* record, bool raw)
+bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int index, pdns_ffi_record_t* record, bool raw)
{
- if (i >= ref->handle.d_dq.currentRecords->size()) {
+ if (index >= ref->getHandle().d_dq.currentRecords->size()) {
return false;
}
try {
- DNSRecord& r = ref->handle.d_dq.currentRecords->at(i);
+ DNSRecord& dnsRecord = ref->getHandle().d_dq.currentRecords->at(index);
if (raw) {
- const auto& storage = r.d_name.getStorage();
+ const auto& storage = dnsRecord.d_name.getStorage();
record->name = storage.data();
record->name_len = storage.size();
}
else {
- std::string name = r.d_name.toStringNoDot();
+ std::string name = dnsRecord.d_name.toStringNoDot();
record->name_len = name.size();
record->name = ref->insert(std::move(name))->c_str();
}
if (raw) {
- auto content = ref->insert(r.getContent()->serialize(r.d_name, true));
+ auto content = ref->insert(dnsRecord.getContent()->serialize(dnsRecord.d_name, true));
record->content = content->data();
record->content_len = content->size();
}
else {
- auto content = ref->insert(r.getContent()->getZoneRepresentation());
+ auto content = ref->insert(dnsRecord.getContent()->getZoneRepresentation());
record->content = content->data();
record->content_len = content->size();
}
- record->ttl = r.d_ttl;
- record->place = static_cast<pdns_record_place_t>(r.d_place);
- record->type = r.d_type;
+ record->ttl = dnsRecord.d_ttl;
+ record->place = static_cast<pdns_record_place_t>(dnsRecord.d_place);
+ record->type = dnsRecord.d_type;
}
catch (const std::exception& e) {
g_log << Logger::Error << "Error attempting to get a record from Lua via pdns_postresolve_ffi_handle_get_record: " << e.what() << endl;
return true;
}
-bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, const char* content, size_t contentLen, bool raw)
+bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int index, const char* content, size_t contentLen, bool raw)
{
- if (i >= ref->handle.d_dq.currentRecords->size()) {
+ if (index >= ref->getHandle().d_dq.currentRecords->size()) {
return false;
}
try {
- DNSRecord& r = ref->handle.d_dq.currentRecords->at(i);
+ DNSRecord& dnsRecord = ref->getHandle().d_dq.currentRecords->at(index);
if (raw) {
- r.setContent(DNSRecordContent::deserialize(r.d_name, r.d_type, string(content, contentLen)));
+ dnsRecord.setContent(DNSRecordContent::deserialize(dnsRecord.d_name, dnsRecord.d_type, string(content, contentLen)));
}
else {
- r.setContent(DNSRecordContent::mastermake(r.d_type, QClass::IN, string(content, contentLen)));
+ dnsRecord.setContent(DNSRecordContent::make(dnsRecord.d_type, QClass::IN, string(content, contentLen)));
}
return true;
void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref)
{
- ref->handle.d_dq.currentRecords->clear();
+ ref->getHandle().d_dq.currentRecords->clear();
}
bool pdns_postresolve_ffi_handle_add_record(pdns_postresolve_ffi_handle_t* ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentLen, pdns_record_place_t place, bool raw)
{
try {
- DNSRecord dr;
- dr.d_name = name != nullptr ? DNSName(name) : ref->handle.d_dq.qname;
- dr.d_ttl = ttl;
- dr.d_type = type;
- dr.d_class = QClass::IN;
- dr.d_place = DNSResourceRecord::Place(place);
+ DNSRecord dnsRecord;
+ dnsRecord.d_name = name != nullptr ? DNSName(name) : ref->getHandle().d_dq.qname;
+ dnsRecord.d_ttl = ttl;
+ dnsRecord.d_type = type;
+ dnsRecord.d_class = QClass::IN;
+ dnsRecord.d_place = DNSResourceRecord::Place(place);
if (raw) {
- dr.setContent(DNSRecordContent::deserialize(dr.d_name, dr.d_type, string(content, contentLen)));
+ dnsRecord.setContent(DNSRecordContent::deserialize(dnsRecord.d_name, dnsRecord.d_type, string(content, contentLen)));
}
else {
- dr.setContent(DNSRecordContent::mastermake(type, QClass::IN, string(content, contentLen)));
+ dnsRecord.setContent(DNSRecordContent::make(type, QClass::IN, string(content, contentLen)));
}
- ref->handle.d_dq.currentRecords->push_back(std::move(dr));
+ ref->getHandle().d_dq.currentRecords->push_back(std::move(dnsRecord));
return true;
}
const char* pdns_postresolve_ffi_handle_get_authip(pdns_postresolve_ffi_handle_t* ref)
{
- return ref->insert(ref->handle.d_dq.fromAuthIP->toString())->c_str();
+ return ref->insert(ref->getHandle().d_dq.fromAuthIP->toString())->c_str();
}
void pdns_postresolve_ffi_handle_get_authip_raw(pdns_postresolve_ffi_handle_t* ref, const void** addr, size_t* addrSize)
{
- return pdns_ffi_comboaddress_to_raw(*ref->handle.d_dq.fromAuthIP, addr, addrSize);
+ return pdns_ffi_comboaddress_to_raw(*ref->getHandle().d_dq.fromAuthIP, addr, addrSize);
}
{
public:
RecursorLua4();
- ~RecursorLua4(); // this is so unique_ptr works with an incomplete type
+ RecursorLua4(const RecursorLua4&) = delete;
+ RecursorLua4(RecursorLua4&&) = delete;
+ RecursorLua4& operator=(const RecursorLua4&) = delete;
+ RecursorLua4& operator=(RecursorLua4&&) = delete;
+ ~RecursorLua4() override; // this is so unique_ptr works with an incomplete type
struct MetaValue
{
};
struct DNSQuestion
{
+ // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
DNSQuestion(const ComboAddress& rem, const ComboAddress& loc, const DNSName& query, uint16_t type, bool tcp, bool& variable_, bool& wantsRPZ_, bool& logResponse_, bool& addPaddingToResponse_, const struct timeval& queryTime_) :
qname(query), qtype(type), local(loc), remote(rem), isTcp(tcp), variable(variable_), wantsRPZ(wantsRPZ_), logResponse(logResponse_), addPaddingToResponse(addPaddingToResponse_), queryTime(queryTime_)
{
void addAnswer(uint16_t type, const std::string& content, boost::optional<int> ttl, boost::optional<string> name);
void addRecord(uint16_t type, const std::string& content, DNSResourceRecord::Place place, boost::optional<int> ttl, boost::optional<string> name);
- vector<pair<int, DNSRecord>> getRecords() const;
- boost::optional<dnsheader> getDH() const;
- vector<pair<uint16_t, string>> getEDNSOptions() const;
- boost::optional<string> getEDNSOption(uint16_t code) const;
- boost::optional<Netmask> getEDNSSubnet() const;
- std::vector<std::pair<int, ProxyProtocolValue>> getProxyProtocolValues() const;
- vector<string> getEDNSFlags() const;
- bool getEDNSFlag(string flag) const;
- void setRecords(const vector<pair<int, DNSRecord>>& records);
+ [[nodiscard]] vector<pair<int, DNSRecord>> getRecords() const;
+ [[nodiscard]] boost::optional<dnsheader> getDH() const;
+ [[nodiscard]] vector<pair<uint16_t, string>> getEDNSOptions() const;
+ [[nodiscard]] boost::optional<string> getEDNSOption(uint16_t code) const;
+ [[nodiscard]] boost::optional<Netmask> getEDNSSubnet() const;
+ [[nodiscard]] std::vector<std::pair<int, ProxyProtocolValue>> getProxyProtocolValues() const;
+ [[nodiscard]] vector<string> getEDNSFlags() const;
+ [[nodiscard]] bool getEDNSFlag(const string& flag) const;
+ void setRecords(const vector<pair<int, DNSRecord>>& arg);
int rcode{0};
// struct dnsheader, packet length would be great
struct FFIParams
{
public:
+ // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
FFIParams(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, LuaContext::LuaObject& data_, std::unordered_set<std::string>& policyTags_, std::vector<DNSRecord>& records_, const EDNSOptionViewMap& ednsOptions_, const std::vector<ProxyProtocolValue>& proxyProtocolValues_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, std::string& routingTag_, boost::optional<int>& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_, boost::optional<uint16_t>& extendedErrorCode_, std::string& extendedErrorExtra_, bool& disablePadding_, std::map<std::string, MetaValue>& meta_) :
data(data_), qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), proxyProtocolValues(proxyProtocolValues_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), routingTag(routingTag_), extendedErrorExtra(extendedErrorExtra_), rcode(rcode_), extendedErrorCode(extendedErrorCode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), disablePadding(disablePadding_), qtype(qtype_), tcp(tcp_), meta(meta_)
{
unsigned int gettag_ffi(FFIParams&) const;
void maintenance() const;
- bool prerpz(DNSQuestion& dq, int& ret, RecEventTrace&) const;
- bool preresolve(DNSQuestion& dq, int& ret, RecEventTrace&) const;
- bool nxdomain(DNSQuestion& dq, int& ret, RecEventTrace&) const;
- bool nodata(DNSQuestion& dq, int& ret, RecEventTrace&) const;
- bool postresolve(DNSQuestion& dq, int& ret, RecEventTrace&) const;
+ bool prerpz(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
+ bool preresolve(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
+ bool nxdomain(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
+ bool nodata(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
+ bool postresolve(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
- bool preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& et, const struct timeval& tv) const;
+ bool preoutquery(const ComboAddress& nameserver, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& eventTrace, const struct timeval& theTime) const;
bool ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader&, RecEventTrace&) const;
bool policyHitEventFilter(const ComboAddress& remote, const DNSName& qname, const QType& qtype, bool tcp, DNSFilterEngine::Policy& policy, std::unordered_set<std::string>& tags, std::unordered_map<std::string, bool>& discardedPolicies) const;
- bool needDQ() const
+ [[nodiscard]] bool needDQ() const
{
return (d_prerpz || d_preresolve || d_nxdomain || d_nodata || d_postresolve);
}
- typedef std::function<std::tuple<unsigned int, boost::optional<std::unordered_map<int, string>>, boost::optional<LuaContext::LuaObject>, boost::optional<std::string>, boost::optional<std::string>, boost::optional<std::string>, boost::optional<string>>(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const EDNSOptionViewMap&, bool, const std::vector<std::pair<int, const ProxyProtocolValue*>>&)> gettag_t;
- gettag_t d_gettag; // public so you can query if we have this hooked
-
- typedef std::function<boost::optional<LuaContext::LuaObject>(pdns_ffi_param_t*)> gettag_ffi_t;
- gettag_ffi_t d_gettag_ffi;
-
struct PostResolveFFIHandle
{
- PostResolveFFIHandle(DNSQuestion& dq) :
- d_dq(dq)
+ PostResolveFFIHandle(DNSQuestion& dnsQuestion) :
+ d_dq(dnsQuestion)
{
}
DNSQuestion& d_dq;
bool d_ret{false};
};
bool postresolve_ffi(PostResolveFFIHandle&) const;
- typedef std::function<bool(pdns_postresolve_ffi_handle_t*)> postresolve_ffi_t;
- postresolve_ffi_t d_postresolve_ffi;
+
+ [[nodiscard]] bool hasGettagFunc() const
+ {
+ return static_cast<bool>(d_gettag);
+ }
+ [[nodiscard]] bool hasGettagFFIFunc() const
+ {
+ return static_cast<bool>(d_gettag_ffi);
+ }
+ [[nodiscard]] bool hasPostResolveFFIfunc() const
+ {
+ return static_cast<bool>(d_postresolve_ffi);
+ }
protected:
- virtual void postPrepareContext() override;
- virtual void postLoad() override;
- virtual void getFeatures(Features& features) override;
+ void postPrepareContext() override;
+ void postLoad() override;
+ void getFeatures(Features& features) override;
private:
- typedef std::function<void()> luamaintenance_t;
+ using gettag_t = std::function<std::tuple<unsigned int, boost::optional<std::unordered_map<int, string>>, boost::optional<LuaContext::LuaObject>, boost::optional<std::string>, boost::optional<std::string>, boost::optional<std::string>, boost::optional<string>>(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const EDNSOptionViewMap&, bool, const std::vector<std::pair<int, const ProxyProtocolValue*>>&)>;
+ gettag_t d_gettag; // public so you can query if we have this hooked
+
+ using gettag_ffi_t = std::function<boost::optional<LuaContext::LuaObject>(pdns_ffi_param_t*)>;
+ gettag_ffi_t d_gettag_ffi;
+
+ using postresolve_ffi_t = std::function<bool(pdns_postresolve_ffi_handle_t*)>;
+ postresolve_ffi_t d_postresolve_ffi;
+
+ using luamaintenance_t = std::function<void()>;
luamaintenance_t d_maintenance;
- typedef std::function<bool(DNSQuestion*)> luacall_t;
+
+ using luacall_t = std::function<bool(DNSQuestion*)>;
luacall_t d_prerpz, d_preresolve, d_nxdomain, d_nodata, d_postresolve, d_preoutquery, d_postoutquery;
- bool genhook(const luacall_t& func, DNSQuestion& dq, int& ret) const;
- typedef std::function<bool(ComboAddress, ComboAddress, struct dnsheader)> ipfilter_t;
+ bool genhook(const luacall_t& func, DNSQuestion& dnsQuestion, int& ret) const;
+
+ using ipfilter_t = std::function<bool(ComboAddress, ComboAddress, struct dnsheader)>;
ipfilter_t d_ipfilter;
- typedef std::function<bool(PolicyEvent&)> policyEventFilter_t;
+
+ using policyEventFilter_t = std::function<bool(PolicyEvent&)>;
policyEventFilter_t d_policyHitEventFilter;
};
std::shared_ptr<Logr::Logger> g_slogout;
bool g_paddingOutgoing;
-void remoteLoggerQueueData(RemoteLoggerInterface& r, const std::string& data)
+void remoteLoggerQueueData(RemoteLoggerInterface& rli, const std::string& data)
{
- auto ret = r.queueData(data);
+ auto ret = rli.queueData(data);
switch (ret) {
case RemoteLoggerInterface::Result::Queued:
break;
case RemoteLoggerInterface::Result::PipeFull: {
- const auto msg = RemoteLoggerInterface::toErrorString(ret);
- const auto name = r.name();
- SLOG(g_log << Logger::Debug << name << ": " << msg << std::endl,
- g_slog->withName(name)->info(Logr::Debug, msg));
+ const auto& msg = RemoteLoggerInterface::toErrorString(ret);
+ SLOG(g_log << Logger::Debug << rli.name() << ": " << msg << std::endl,
+ g_slog->withName(rli.name())->info(Logr::Debug, msg));
break;
}
case RemoteLoggerInterface::Result::TooLarge: {
- const auto msg = RemoteLoggerInterface::toErrorString(ret);
- const auto name = r.name();
- SLOG(g_log << Logger::Notice << name << ": " << msg << endl,
- g_slog->withName(name)->info(Logr::Debug, msg));
+ const auto& msg = RemoteLoggerInterface::toErrorString(ret);
+ SLOG(g_log << Logger::Notice << rli.name() << ": " << msg << endl,
+ g_slog->withName(rli.name())->info(Logr::Debug, msg));
break;
}
case RemoteLoggerInterface::Result::OtherError: {
- const auto msg = RemoteLoggerInterface::toErrorString(ret);
- const auto name = r.name();
- SLOG(g_log << Logger::Warning << name << ": " << msg << std::endl,
- g_slog->withName(name)->info(Logr::Warning, msg));
+ const auto& msg = RemoteLoggerInterface::toErrorString(ret);
+ SLOG(g_log << Logger::Warning << rli.name() << ": " << msg << std::endl,
+ g_slog->withName(rli.name())->info(Logr::Warning, msg));
break;
}
}
#include "dnstap.hh"
#include "fstrm_logger.hh"
-bool g_syslog;
-
static bool isEnabledForQueries(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
{
if (fstreamLoggers == nullptr) {
return false;
}
-static void logFstreamQuery(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const struct timeval& queryTime, const ComboAddress& localip, const ComboAddress& ip, DnstapMessage::ProtocolType protocol, boost::optional<const DNSName&> auth, const vector<uint8_t>& packet)
+static void logFstreamQuery(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const struct timeval& queryTime, const ComboAddress& localip, const ComboAddress& address, DnstapMessage::ProtocolType protocol, const boost::optional<const DNSName&>& auth, const vector<uint8_t>& packet)
{
if (fstreamLoggers == nullptr)
return;
struct timespec ts;
TIMEVAL_TO_TIMESPEC(&queryTime, &ts);
std::string str;
- DnstapMessage message(str, DnstapMessage::MessageType::resolver_query, SyncRes::s_serverID, &localip, &ip, protocol, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &ts, nullptr, auth);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DnstapMessage message(std::move(str), DnstapMessage::MessageType::resolver_query, SyncRes::s_serverID, &localip, &address, protocol, reinterpret_cast<const char*>(packet.data()), packet.size(), &ts, nullptr, auth);
+ str = message.getBuffer();
for (auto& logger : *fstreamLoggers) {
remoteLoggerQueueData(*logger, str);
return false;
}
-static void logFstreamResponse(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const ComboAddress& localip, const ComboAddress& ip, DnstapMessage::ProtocolType protocol, boost::optional<const DNSName&> auth, const PacketBuffer& packet, const struct timeval& queryTime, const struct timeval& replyTime)
+static void logFstreamResponse(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const ComboAddress& localip, const ComboAddress& address, DnstapMessage::ProtocolType protocol, const boost::optional<const DNSName&>& auth, const PacketBuffer& packet, const struct timeval& queryTime, const struct timeval& replyTime)
{
if (fstreamLoggers == nullptr)
return;
TIMEVAL_TO_TIMESPEC(&queryTime, &ts1);
TIMEVAL_TO_TIMESPEC(&replyTime, &ts2);
std::string str;
- DnstapMessage message(str, DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &localip, &ip, protocol, reinterpret_cast<const char*>(packet.data()), packet.size(), &ts1, &ts2, auth);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ DnstapMessage message(std::move(str), DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &localip, &address, protocol, reinterpret_cast<const char*>(packet.data()), packet.size(), &ts1, &ts2, auth);
+ str = message.getBuffer();
for (auto& logger : *fstreamLoggers) {
remoteLoggerQueueData(*logger, str);
#endif // HAVE_FSTRM
-static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, boost::optional<const boost::uuids::uuid&> initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& ip, const DNSName& domain, int type, uint16_t qid, bool doTCP, bool tls, size_t bytes, boost::optional<Netmask>& srcmask)
+static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const boost::optional<const boost::uuids::uuid&>& initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& address, const DNSName& domain, int type, uint16_t qid, bool doTCP, bool tls, size_t bytes, const boost::optional<Netmask>& srcmask)
{
if (!outgoingLoggers) {
return;
pdns::ProtoZero::Message m{buffer};
m.setType(pdns::ProtoZero::Message::MessageType::DNSOutgoingQueryType);
m.setMessageIdentity(uuid);
- m.setSocketFamily(ip.sin4.sin_family);
+ m.setSocketFamily(address.sin4.sin_family);
if (!doTCP) {
m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::UDP);
}
m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::DoT);
}
- m.setTo(ip);
+ m.setTo(address);
m.setInBytes(bytes);
m.setTime();
m.setId(qid);
m.setQuestion(domain, type, QClass::IN);
- m.setToPort(ip.getPort());
+ m.setToPort(address.getPort());
m.setServerIdentity(SyncRes::s_serverID);
if (initialRequestId) {
}
}
-static void logIncomingResponse(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, boost::optional<const boost::uuids::uuid&> initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& ip, const DNSName& domain, int type, uint16_t qid, bool doTCP, bool tls, boost::optional<Netmask>& srcmask, size_t bytes, int rcode, const std::vector<DNSRecord>& records, const struct timeval& queryTime, const std::set<uint16_t>& exportTypes)
+static void logIncomingResponse(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const boost::optional<const boost::uuids::uuid&>& initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& address, const DNSName& domain, int type, uint16_t qid, bool doTCP, bool tls, const boost::optional<Netmask>& srcmask, size_t bytes, int rcode, const std::vector<DNSRecord>& records, const struct timeval& queryTime, const std::set<uint16_t>& exportTypes)
{
if (!outgoingLoggers) {
return;
pdns::ProtoZero::RecMessage m{buffer};
m.setType(pdns::ProtoZero::Message::MessageType::DNSIncomingResponseType);
m.setMessageIdentity(uuid);
- m.setSocketFamily(ip.sin4.sin_family);
+ m.setSocketFamily(address.sin4.sin_family);
if (!doTCP) {
m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::UDP);
}
else {
m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::DoT);
}
- m.setTo(ip);
+ m.setTo(address);
m.setInBytes(bytes);
m.setTime();
m.setId(qid);
m.setQuestion(domain, type, QClass::IN);
- m.setToPort(ip.getPort());
+ m.setToPort(address.getPort());
m.setServerIdentity(SyncRes::s_serverID);
if (initialRequestId) {
/** lwr is only filled out in case 1 was returned, and even when returning 1 for 'success', lwr might contain DNS errors
Never throws!
*/
-static LWResult::Result asyncresolve(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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained, TCPOutConnectionManager::Connection& connection)
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
+static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, const ResolveContext& context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, [[maybe_unused]] const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained, TCPOutConnectionManager::Connection& connection)
{
size_t len;
size_t bufsize = g_outgoingEDNSBufsize;
// string mapped0x20=dns0x20(domain);
uint16_t qid = dns_random_uint16();
DNSPacketWriter pw(vpacket, domain, type);
- bool dnsOverTLS = SyncRes::s_dot_to_port_853 && ip.getPort() == 853;
+ bool dnsOverTLS = SyncRes::s_dot_to_port_853 && address.getPort() == 853;
pw.getHeader()->rd = sendRDQuery;
pw.getHeader()->id = qid;
if (outgoingLoggers) {
uuid = getUniqueID();
- logOutgoingQuery(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, vpacket.size(), srcmask);
+ logOutgoingQuery(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, vpacket.size(), srcmask);
}
srcmask = boost::none; // this is also our return value, even if EDNS0Level == 0
if (!doTCP) {
int queryfd;
- if (ip.sin4.sin_family == AF_INET6) {
- t_Counters.at(rec::Counter::ipv6queries)++;
- }
- ret = asendto((const char*)&*vpacket.begin(), vpacket.size(), 0, ip, qid, domain, type, weWantEDNSSubnet, &queryfd);
+ ret = asendto(vpacket.data(), vpacket.size(), 0, address, qid, domain, type, weWantEDNSSubnet, &queryfd);
if (ret != LWResult::Result::Success) {
return ret;
#ifdef HAVE_FSTRM
if (!*chained) {
if (fstrmQEnabled || fstrmREnabled) {
- localip.sin4.sin_family = ip.sin4.sin_family;
- socklen_t slen = ip.getSocklen();
- getsockname(queryfd, reinterpret_cast<sockaddr*>(&localip), &slen);
+ localip.sin4.sin_family = address.sin4.sin_family;
+ socklen_t slen = address.getSocklen();
+ (void)getsockname(queryfd, reinterpret_cast<sockaddr*>(&localip), &slen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast))
}
if (fstrmQEnabled) {
- logFstreamQuery(fstrmLoggers, queryTime, localip, ip, DnstapMessage::ProtocolType::DoUDP, context ? context->d_auth : boost::none, vpacket);
+ logFstreamQuery(fstrmLoggers, queryTime, localip, address, DnstapMessage::ProtocolType::DoUDP, context.d_auth ? context.d_auth : boost::none, vpacket);
}
}
#endif /* HAVE_FSTRM */
// sleep until we see an answer to this, interface to mtasker
- ret = arecvfrom(buf, 0, ip, &len, qid, domain, type, queryfd, now);
+ ret = arecvfrom(buf, 0, address, len, qid, domain, type, queryfd, *now);
}
else {
bool isNew;
// *will* get a new connection, so this loop is not endless.
isNew = true; // tcpconnect() might throw for new connections. In that case, we want to break the loop, scanbuild complains here, which is a false positive afaik
std::string nsName;
- if (context && !context->d_nsName.empty()) {
- nsName = context->d_nsName.toStringNoDot();
+ if (!context.d_nsName.empty()) {
+ nsName = context.d_nsName.toStringNoDot();
}
- isNew = tcpconnect(ip, connection, dnsOverTLS, nsName);
- ret = tcpsendrecv(ip, connection, localip, vpacket, len, buf);
+ isNew = tcpconnect(address, connection, dnsOverTLS, nsName);
+ ret = tcpsendrecv(address, connection, localip, vpacket, len, buf);
#ifdef HAVE_FSTRM
if (fstrmQEnabled) {
- logFstreamQuery(fstrmLoggers, queryTime, localip, ip, !dnsOverTLS ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoT, context ? context->d_auth : boost::none, vpacket);
+ logFstreamQuery(fstrmLoggers, queryTime, localip, address, !dnsOverTLS ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoT, context.d_auth, vpacket);
}
#endif /* HAVE_FSTRM */
if (ret == LWResult::Result::Success) {
if (ret != LWResult::Result::Success) { // includes 'timeout'
if (outgoingLoggers) {
- logIncomingResponse(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, srcmask, 0, -1, {}, queryTime, exportTypes);
+ logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, 0, -1, {}, queryTime, exportTypes);
}
return ret;
}
if (dnsOverTLS) {
protocol = DnstapMessage::ProtocolType::DoT;
}
- logFstreamResponse(fstrmLoggers, localip, ip, protocol, context ? context->d_auth : boost::none, buf, queryTime, *now);
+ logFstreamResponse(fstrmLoggers, localip, address, protocol, context.d_auth, buf, queryTime, *now);
}
#endif /* HAVE_FSTRM */
if (mdp.d_header.rcode == RCode::FormErr && mdp.d_qname.empty() && mdp.d_qtype == 0 && mdp.d_qclass == 0) {
if (outgoingLoggers) {
- logIncomingResponse(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
+ logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
}
lwr->d_validpacket = true;
return LWResult::Result::Success; // this is "success", the error is set in lwr->d_rcode
if (domain != mdp.d_qname) {
if (!mdp.d_qname.empty() && domain.toString().find((char)0) == string::npos /* ugly */) { // embedded nulls are too noisy, plus empty domains are too
- SLOG(g_log << Logger::Notice << "Packet purporting to come from remote server " << ip.toString() << " contained wrong answer: '" << domain << "' != '" << mdp.d_qname << "'" << endl,
+ SLOG(g_log << Logger::Notice << "Packet purporting to come from remote server " << address.toString() << " contained wrong answer: '" << domain << "' != '" << mdp.d_qname << "'" << endl,
g_slogout->info(Logr::Notice, "Packet purporting to come from remote server contained wrong answer",
- "server", Logging::Loggable(ip),
+ "server", Logging::Loggable(address),
"qname", Logging::Loggable(domain),
"onwire", Logging::Loggable(mdp.d_qname)));
}
}
if (outgoingLoggers) {
- logIncomingResponse(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
+ logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
}
lwr->d_validpacket = true;
}
catch (const std::exception& mde) {
if (::arg().mustDo("log-common-errors")) {
- SLOG(g_log << Logger::Notice << "Unable to parse packet from remote server " << ip.toString() << ": " << mde.what() << endl,
- g_slogout->error(Logr::Notice, mde.what(), "Unable to parse packet from remote server", "server", Logging::Loggable(ip),
+ SLOG(g_log << Logger::Notice << "Unable to parse packet from remote server " << address.toString() << ": " << mde.what() << endl,
+ g_slogout->error(Logr::Notice, mde.what(), "Unable to parse packet from remote server", "server", Logging::Loggable(address),
"exception", Logging::Loggable("std::exception")));
}
t_Counters.at(rec::Counter::serverParseError)++;
if (outgoingLoggers) {
- logIncomingResponse(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
+ logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
}
return LWResult::Result::Success; // success - oddly enough
}
catch (...) {
SLOG(g_log << Logger::Notice << "Unknown error parsing packet from remote server" << endl,
- g_slogout->info(Logr::Notice, "Unknown error parsing packet from remote server", "server", Logging::Loggable(ip)));
+ g_slogout->info(Logr::Notice, "Unknown error parsing packet from remote server", "server", Logging::Loggable(address)));
}
t_Counters.at(rec::Counter::serverParseError)++;
return LWResult::Result::PermanentError;
}
-LWResult::Result asyncresolve(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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained)
+LWResult::Result asyncresolve(const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, const ResolveContext& context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained)
{
TCPOutConnectionManager::Connection connection;
- auto ret = asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNS0Level, now, srcmask, context, outgoingLoggers, fstrmLoggers, exportTypes, lwr, chained, connection);
+ auto ret = asyncresolve(address, domain, type, doTCP, sendRDQuery, EDNS0Level, now, srcmask, context, outgoingLoggers, fstrmLoggers, exportTypes, lwr, chained, connection);
if (doTCP) {
if (connection.d_handler && lwr->d_validpacket) {
- t_tcp_manager.store(*now, ip, std::move(connection));
+ t_tcp_manager.store(*now, address, std::move(connection));
}
}
return ret;
class LWResult
{
public:
- LWResult() :
- d_usec(0) {}
-
enum class Result : uint8_t
{
Timeout = 0,
bool d_haveEDNS{false};
};
-LWResult::Result asendto(const char* data, size_t len, int flags, const ComboAddress& ip, uint16_t id,
- const DNSName& domain, uint16_t qtype, bool ecs, int* fd);
-LWResult::Result arecvfrom(PacketBuffer& packet, int flags, const ComboAddress& ip, size_t* d_len, uint16_t id,
- const DNSName& domain, uint16_t qtype, int fd, const struct timeval* now);
+LWResult::Result asendto(const void* data, size_t len, int flags, const ComboAddress& toAddress, uint16_t qid,
+ const DNSName& domain, uint16_t qtype, bool ecs, int* fileDesc);
+LWResult::Result arecvfrom(PacketBuffer& packet, int flags, const ComboAddress& fromAddr, size_t& len, uint16_t qid,
+ const DNSName& domain, uint16_t qtype, int fileDesc, const struct timeval& now);
-LWResult::Result asyncresolve(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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* res, bool* chained);
+LWResult::Result asyncresolve(const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, const ResolveContext& context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained);
--- /dev/null
+../../../m4/ax_compare_version.m4
\ No newline at end of file
--- /dev/null
+AC_DEFUN([PDNS_CHECK_CARGO], [
+ AC_REQUIRE([AC_PROG_SED])
+
+ AC_CHECK_PROG(CARGO, [cargo], [cargo], $CARGO)
+ AS_IF(test x$CARGO = x,
+ AC_MSG_ERROR([cargo is required])
+ )
+ minimum=$1
+ cargo_version=`$CARGO --version | $SED -e 's/^cargo //g'`
+ AX_COMPARE_VERSION([$cargo_version],[lt],[$minimum], [
+ AC_MSG_ERROR([need at least cargo version $minimum])
+ ])
+])
--- /dev/null
+../../../m4/pdns_check_secure_memset.m4
\ No newline at end of file
--- /dev/null
+../../../m4/pdns_enable_coverage.m4
\ No newline at end of file
#!/bin/sh
-
+if [ -z "$1" ] || [ -z "$2" ]; then
+ echo "Usage: $0 effective_tld_names.dat pubsuffix.cc"
+ exit 1
+fi
+set -e
(echo "const char* g_pubsuffix[]={";
- for a in $(grep -v "//" effective_tld_names.dat | grep \\. | egrep "^[.0-9a-z-]*$")
+ for a in $(grep -v "//" "$1" | grep \\. | egrep "^[.0-9a-z-]*$")
do
echo \"$a\",
done
-echo "0};") > pubsuffix.cc
+echo "0};") > "$2"
+++ /dev/null
-../mtasker.cc
\ No newline at end of file
+++ /dev/null
-../mtasker.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#include <cstdint>
+#include <ctime>
+#include <queue>
+#include <memory>
+#include <stack>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+
+#include "namespaces.hh"
+#include "misc.hh"
+#include "mtasker_context.hh"
+
+// #define MTASKERTIMING 1
+
+//! The main MTasker class
+/** The main MTasker class. See the main page for more information.
+ \tparam EventKey Type of the key with which events are to be identified. Defaults to int.
+ \tparam EventVal Type of the content or value of an event. Defaults to int. Cannot be set to void.
+ \note The EventKey needs to have an operator< defined because it is used as the key of an associative array
+*/
+
+template <class EventKey = int, class EventVal = int, class Cmp = std::less<EventKey>>
+class MTasker
+{
+public:
+ struct Waiter
+ {
+ EventKey key;
+ std::shared_ptr<pdns_ucontext_t> context;
+ struct timeval ttd
+ {
+ };
+ int tid{};
+ };
+ struct KeyTag
+ {
+ };
+
+ using waiters_t = boost::multi_index::multi_index_container<
+ Waiter,
+ boost::multi_index::indexed_by<
+ boost::multi_index::ordered_unique<boost::multi_index::member<Waiter, EventKey, &Waiter::key>, Cmp>,
+ boost::multi_index::ordered_non_unique<boost::multi_index::tag<KeyTag>, boost::multi_index::member<Waiter, struct timeval, &Waiter::ttd>>>>;
+
+ //! Constructor
+ /** Constructor with a small default stacksize. If any of your threads exceeds this stack, your application will crash.
+ This limit applies solely to the stack, the heap is not limited in any way. If threads need to allocate a lot of data,
+ the use of new/delete is suggested.
+ */
+ MTasker(size_t stacksize = static_cast<size_t>(16 * 8192), size_t stackCacheSize = 0) :
+ d_stacksize(stacksize), d_maxCachedStacks(stackCacheSize), d_waitstatus(Error)
+ {
+ initMainStackBounds();
+
+ // make sure our stack is 16-byte aligned to make all the architectures happy
+ d_stacksize = d_stacksize >> 4 << 4;
+ }
+
+ using tfunc_t = void(void*); //!< type of the pointer that starts a thread
+ int waitEvent(EventKey& key, EventVal* val = nullptr, unsigned int timeoutMsec = 0, const struct timeval* now = nullptr);
+ void yield();
+ int sendEvent(const EventKey& key, const EventVal* val = nullptr);
+ void makeThread(tfunc_t* start, void* val);
+ bool schedule(const struct timeval& now);
+
+ const waiters_t& getWaiters() const
+ {
+ return d_waiters;
+ }
+
+ //! gives access to the list of Events threads are waiting for
+ /** The kernel can call this to get a list of Events threads are waiting for. This is very useful
+ to setup 'select' or 'poll' or 'aio' events needed to satisfy these requests.
+ getEvents clears the events parameter before filling it.
+
+ \param events Vector which is to be filled with keys threads are waiting for
+ */
+ void getEvents(std::vector<EventKey>& events) const
+ {
+ events.clear();
+ for (const auto& waiter : d_waiters) {
+ events.emplace_back(waiter.key);
+ }
+ }
+
+ //! returns true if there are no processes
+ /** Call this to check if no processes are running anymore
+ \return true if no processes are left
+ */
+ [[nodiscard]] bool noProcesses() const
+ {
+ return d_threadsCount == 0;
+ }
+
+ //! returns the number of processes running
+ /** Call this to perhaps limit activities if too many threads are running
+ \return number of processes running
+ */
+ [[nodiscard]] unsigned int numProcesses() const
+ {
+ return d_threadsCount;
+ }
+
+ //! Returns the current Thread ID (tid)
+ /** Processes can call this to get a numerical representation of their current thread ID.
+ This can be useful for logging purposes.
+ */
+ [[nodiscard]] int getTid() const
+ {
+ return d_tid;
+ }
+
+ //! Returns the maximum stack usage so far of this MThread
+ [[nodiscard]] uint64_t getMaxStackUsage() const
+ {
+ return d_threads.at(d_tid).startOfStack - d_threads.at(d_tid).highestStackSeen;
+ }
+
+ //! Returns the maximum stack usage so far of this MThread
+ [[nodiscard]] unsigned int getUsec() const
+ {
+#ifdef MTASKERTIMING
+ return d_threads.at(d_tid).totTime + d_threads.at(d_tid).dt.ndiff() / 1000;
+#else
+ return 0;
+#endif
+ }
+
+private:
+ EventKey d_eventkey; // for waitEvent, contains exact key it was awoken for
+ EventVal d_waitval;
+
+ pdns_ucontext_t d_kernel;
+ std::queue<int> d_runQueue;
+ std::queue<int> d_zombiesQueue;
+
+ struct ThreadInfo
+ {
+ std::shared_ptr<pdns_ucontext_t> context;
+ std::function<void(void)> start;
+ const char* startOfStack{};
+ const char* highestStackSeen{};
+#ifdef MTASKERTIMING
+ CPUTime dt;
+ unsigned int totTime;
+#endif
+ };
+
+ using pdns_mtasker_stack_t = std::vector<char, lazy_allocator<char>>;
+ using mthreads_t = std::map<int, ThreadInfo>;
+
+ mthreads_t d_threads;
+ std::stack<pdns_mtasker_stack_t> d_cachedStacks;
+ waiters_t d_waiters;
+ size_t d_stacksize;
+ size_t d_threadsCount{0};
+ size_t d_maxCachedStacks{0};
+ int d_tid{0};
+ int d_maxtid{0};
+
+ enum waitstatusenum : int8_t
+ {
+ Error = -1,
+ TimeOut = 0,
+ Answer
+ } d_waitstatus;
+
+ std::shared_ptr<pdns_ucontext_t> getUContext();
+
+ void initMainStackBounds()
+ {
+#ifdef HAVE_FIBER_SANITIZER
+
+#ifdef HAVE_PTHREAD_GETATTR_NP
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_getattr_np(pthread_self(), &attr);
+ pthread_attr_getstack(&attr, &t_mainStack, &t_mainStackSize);
+ pthread_attr_destroy(&attr);
+#elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
+ t_mainStack = pthread_get_stackaddr_np(pthread_self());
+ t_mainStackSize = pthread_get_stacksize_np(pthread_self());
+#else
+#error Cannot determine stack size and base on this platform
+#endif
+
+#endif /* HAVE_FIBER_SANITIZER */
+ }
+};
+
+#ifdef PDNS_USE_VALGRIND
+#include <valgrind/valgrind.h>
+#endif /* PDNS_USE_VALGRIND */
+
+//! puts a thread to sleep waiting until a specified event arrives
+/** Threads can call waitEvent to register that they are waiting on an event with a certain key.
+ If so desired, the event can carry data which is returned in val in case that is non-zero.
+
+ Furthermore, a timeout can be specified in seconds.
+
+ Only one thread can be waiting on a key, results of trying to have more threads
+ waiting on the same key are undefined.
+
+ \param key Event key to wait for. Needs to match up to a key reported to sendEvent
+ \param val If non-zero, the value of the event will be stored in *val
+ \param timeout If non-zero, number of seconds to wait for an event.
+
+ \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer
+*/
+template <class EventKey, class EventVal, class Cmp>
+int MTasker<EventKey, EventVal, Cmp>::waitEvent(EventKey& key, EventVal* val, unsigned int timeoutMsec, const struct timeval* now)
+{
+ if (d_waiters.count(key)) { // there was already an exact same waiter
+ return -1;
+ }
+
+ Waiter waiter;
+ waiter.context = std::make_shared<pdns_ucontext_t>();
+ waiter.ttd.tv_sec = 0;
+ waiter.ttd.tv_usec = 0;
+ if (timeoutMsec != 0) {
+ struct timeval increment
+ {
+ };
+ increment.tv_sec = timeoutMsec / 1000;
+ increment.tv_usec = static_cast<decltype(increment.tv_usec)>(1000 * (timeoutMsec % 1000));
+ if (now != nullptr) {
+ waiter.ttd = increment + *now;
+ }
+ else {
+ struct timeval realnow
+ {
+ };
+ gettimeofday(&realnow, nullptr);
+ waiter.ttd = increment + realnow;
+ }
+ }
+
+ waiter.tid = d_tid;
+ waiter.key = key;
+
+ d_waiters.insert(waiter);
+#ifdef MTASKERTIMING
+ unsigned int diff = d_threads[d_tid].dt.ndiff() / 1000;
+ d_threads[d_tid].totTime += diff;
+#endif
+ notifyStackSwitchToKernel();
+ pdns_swapcontext(*d_waiters.find(key)->context, d_kernel); // 'A' will return here when 'key' has arrived, hands over control to kernel first
+ notifyStackSwitchDone();
+#ifdef MTASKERTIMING
+ d_threads[d_tid].dt.start();
+#endif
+ if (val && d_waitstatus == Answer) {
+ *val = d_waitval;
+ }
+ d_tid = waiter.tid;
+ if ((char*)&waiter < d_threads[d_tid].highestStackSeen) {
+ d_threads[d_tid].highestStackSeen = (char*)&waiter;
+ }
+ key = d_eventkey;
+ return d_waitstatus;
+}
+
+//! yields control to the kernel or other threads
+/** Hands over control to the kernel, allowing other processes to run, or events to arrive */
+
+template <class Key, class Val, class Cmp>
+void MTasker<Key, Val, Cmp>::yield()
+{
+ d_runQueue.push(d_tid);
+ notifyStackSwitchToKernel();
+ pdns_swapcontext(*d_threads[d_tid].context, d_kernel); // give control to the kernel
+ notifyStackSwitchDone();
+}
+
+//! reports that an event took place for which threads may be waiting
+/** From the kernel loop, sendEvent can be called to report that something occurred for which there may be waiters.
+ \param key Key of the event for which threads may be waiting
+ \param val If non-zero, pointer to the content of the event
+ \return Returns -1 in case of error, 0 if there were no waiters, 1 if a thread was woken up.
+
+ WARNING: when passing val as zero, d_waitval is undefined, and hence waitEvent will return undefined!
+*/
+template <class EventKey, class EventVal, class Cmp>
+int MTasker<EventKey, EventVal, Cmp>::sendEvent(const EventKey& key, const EventVal* val)
+{
+ typename waiters_t::iterator waiter = d_waiters.find(key);
+
+ if (waiter == d_waiters.end()) {
+ // cerr<<"Event sent nobody was waiting for! " <<key << endl;
+ return 0;
+ }
+ d_waitstatus = Answer;
+ if (val) {
+ d_waitval = *val;
+ }
+ d_tid = waiter->tid; // set tid
+ d_eventkey = waiter->key; // pass waitEvent the exact key it was woken for
+ auto userspace = std::move(waiter->context);
+ d_waiters.erase(waiter); // removes the waitpoint
+ notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
+ try {
+ pdns_swapcontext(d_kernel, *userspace); // swaps back to the above point 'A'
+ }
+ catch (...) {
+ notifyStackSwitchDone();
+ throw;
+ }
+ notifyStackSwitchDone();
+ return 1;
+}
+
+template <class Key, class Val, class Cmp>
+std::shared_ptr<pdns_ucontext_t> MTasker<Key, Val, Cmp>::getUContext()
+{
+ auto ucontext = std::make_shared<pdns_ucontext_t>();
+ if (d_cachedStacks.empty()) {
+ ucontext->uc_stack.resize(d_stacksize + 1);
+ }
+ else {
+ ucontext->uc_stack = std::move(d_cachedStacks.top());
+ d_cachedStacks.pop();
+ }
+
+ ucontext->uc_link = &d_kernel; // come back to kernel after dying
+
+#ifdef PDNS_USE_VALGRIND
+ uc->valgrind_id = VALGRIND_STACK_REGISTER(&uc->uc_stack[0],
+ &uc->uc_stack[uc->uc_stack.size() - 1]);
+#endif /* PDNS_USE_VALGRIND */
+
+ return ucontext;
+}
+
+//! launches a new thread
+/** The kernel can call this to make a new thread, which starts at the function start and gets passed the val void pointer.
+ \param start Pointer to the function which will form the start of the thread
+ \param val A void pointer that can be used to pass data to the thread
+*/
+template <class Key, class Val, class Cmp>
+void MTasker<Key, Val, Cmp>::makeThread(tfunc_t* start, void* val)
+{
+ auto ucontext = getUContext();
+
+ ++d_threadsCount;
+ auto& thread = d_threads[d_maxtid];
+ // we will get a better approximation when the task is executed, but that prevents notifying a stack at nullptr
+ // on the first invocation
+ d_threads[d_maxtid].startOfStack = &ucontext->uc_stack[ucontext->uc_stack.size() - 1];
+ thread.start = [start, val, this]() {
+ char dummy{};
+ d_threads[d_tid].startOfStack = d_threads[d_tid].highestStackSeen = &dummy;
+ auto const tid = d_tid;
+ start(val);
+ d_zombiesQueue.push(tid);
+ };
+ pdns_makecontext(*ucontext, thread.start);
+
+ thread.context = std::move(ucontext);
+ d_runQueue.push(d_maxtid++); // will run at next schedule invocation
+}
+
+//! needs to be called periodically so threads can run and housekeeping can be performed
+/** The kernel should call this function every once in a while. It makes sense
+ to call this function if you:
+ - reported an event
+ - called makeThread
+ - want to have threads running waitEvent() to get a timeout if enough time passed
+
+ \return Returns if there is more work scheduled and recalling schedule now would be useful
+
+*/
+template <class Key, class Val, class Cmp>
+bool MTasker<Key, Val, Cmp>::schedule(const struct timeval& now)
+{
+ if (!d_runQueue.empty()) {
+ d_tid = d_runQueue.front();
+#ifdef MTASKERTIMING
+ d_threads[d_tid].dt.start();
+#endif
+ notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
+ try {
+ pdns_swapcontext(d_kernel, *d_threads[d_tid].context);
+ }
+ catch (...) {
+ notifyStackSwitchDone();
+ // It is not clear if the d_runQueue.pop() should be done in this case
+ throw;
+ }
+ notifyStackSwitchDone();
+
+ d_runQueue.pop();
+ return true;
+ }
+ if (!d_zombiesQueue.empty()) {
+ auto zombi = d_zombiesQueue.front();
+ if (d_cachedStacks.size() < d_maxCachedStacks) {
+ auto thread = d_threads.find(zombi);
+ if (thread != d_threads.end()) {
+ d_cachedStacks.push(std::move(thread->second.context->uc_stack));
+ }
+ d_threads.erase(thread);
+ }
+ else {
+ d_threads.erase(zombi);
+ }
+ --d_threadsCount;
+ d_zombiesQueue.pop();
+ return true;
+ }
+ if (!d_waiters.empty()) {
+ typedef typename waiters_t::template index<KeyTag>::type waiters_by_ttd_index_t;
+ // waiters_by_ttd_index_t& ttdindex=d_waiters.template get<KeyTag>();
+ waiters_by_ttd_index_t& ttdindex = boost::multi_index::get<KeyTag>(d_waiters);
+
+ for (typename waiters_by_ttd_index_t::iterator i = ttdindex.begin(); i != ttdindex.end();) {
+ if (i->ttd.tv_sec && i->ttd < now) {
+ d_waitstatus = TimeOut;
+ d_eventkey = i->key; // pass waitEvent the exact key it was woken for
+ auto ucontext = i->context;
+ d_tid = i->tid;
+ ttdindex.erase(i++); // removes the waitpoint
+
+ notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
+ try {
+ pdns_swapcontext(d_kernel, *ucontext); // swaps back to the above point 'A'
+ }
+ catch (...) {
+ notifyStackSwitchDone();
+ throw;
+ }
+ notifyStackSwitchDone();
+ }
+ else if (i->ttd.tv_sec != 0) {
+ break;
+ }
+ else {
+ ++i;
+ }
+ }
+ }
+ return false;
+}
#endif
#if defined(HAVE_BOOST_CONTEXT)
-#include "mtasker_fcontext.cc"
+#include "mtasker_fcontext.cc" // NOLINT(bugprone-suspicious-include)
#else
-#include "mtasker_ucontext.cc"
+#include "mtasker_ucontext.cc" // NOLINT(bugprone-suspicious-include)
#endif
using boost::context::detail::make_fcontext;
#endif /* BOOST_VERSION < 106100 */
+// __CET__ is set by the compiler if relevant, so far only relevant/tested for amd64 on OpenBSD
+#if defined(__amd64__)
+#if __CET__ & 0x1
+#define CET_ENDBR __asm("endbr64")
+#else
+#define CET_ENDBR
+#endif
+#else
+#define CET_ENDBR
+#endif
+
#ifdef PDNS_USE_VALGRIND
#include <valgrind/valgrind.h>
#endif /* PDNS_USE_VALGRIND */
static_cast<fcontext_t>(args->prev_ctx), 0);
#else
transfer_t res = jump_fcontext(t.fctx, 0);
+ CET_ENDBR;
/* we got switched back from pdns_swapcontext() */
if (res.data) {
/* if res.data is not a nullptr, it holds a pointer to the context
std::rethrow_exception(origctx->exception);
#else
transfer_t res = jump_fcontext(static_cast<fcontext_t>(ctx.uc_mcontext), &octx.uc_mcontext);
+ CET_ENDBR;
if (res.data) {
/* if res.data is not a nullptr, it holds a pointer to the context
we just switched from, and we need to fill it to be able to
#else
transfer_t res = jump_fcontext(static_cast<fcontext_t>(ctx.uc_mcontext),
&args);
+ CET_ENDBR;
/* back from threadwrapper, updating the context */
ctx.uc_mcontext = res.fctx;
#endif
#include <exception>
#include <cstring>
#include <cassert>
-#include <signal.h>
+#include <csignal>
+#include <cstdint>
#include <ucontext.h>
#ifdef PDNS_USE_VALGRIND
{
size_t count = 0;
for (const auto& map : d_maps) {
- count += map.d_entriesCount;
+ count += map.getEntriesCount();
}
return count;
}
* \param ne A NegCacheEntry that is filled when there is a cache entry
* \return true if ne was filled out, false otherwise
*/
-bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne, bool serveStale, bool refresh)
+bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& negEntry, bool serveStale, bool refresh)
{
// Never deny the root.
- if (qname.isRoot())
+ if (qname.isRoot()) {
return false;
+ }
- // An 'ENT' QType entry, used as "whole name" in the neg-cache context.
- static const QType qtnull(0);
DNSName lastLabel = qname.getLastLabel();
-
- auto& map = getMap(lastLabel);
- auto content = map.lock();
-
- negcache_t::const_iterator ni = content->d_map.find(std::tie(lastLabel, qtnull));
-
- while (ni != content->d_map.end() && ni->d_name == lastLabel && ni->d_auth.isRoot() && ni->d_qtype == qtnull) {
- if (!refresh && (serveStale || ni->d_servedStale > 0) && ni->d_ttd <= now.tv_sec && ni->d_servedStale < s_maxServedStaleExtensions) {
- updateStaleEntry(now.tv_sec, ni, QType::A);
- }
- // We have something
- if (now.tv_sec < ni->d_ttd) {
- ne = *ni;
- moveCacheItemToBack<SequenceTag>(content->d_map, ni);
- return true;
- }
- if (ni->d_servedStale == 0 && !serveStale) {
- moveCacheItemToFront<SequenceTag>(content->d_map, ni);
- }
- ++ni;
+ NegCacheEntry found;
+ // An 'ENT' QType entry, used as "whole name" in the neg-cache context.
+ auto exists = get(lastLabel, QType::ENT, now, found, true, serveStale, refresh);
+ if (exists && found.d_auth.isRoot()) {
+ negEntry = std::move(found);
+ return true;
}
return false;
}
auto content = map.lock();
inserted = lruReplacingInsert<SequenceTag>(content->d_map, ne);
if (inserted) {
- ++map.d_entriesCount;
+ map.incEntriesCount();
}
}
break;
i = m->d_map.erase(i);
ret++;
- --map.d_entriesCount;
+ map.decEntriesCount();
}
}
return ret;
while (i != range.second) {
i = content->d_map.erase(i);
ret++;
- --map.d_entriesCount;
+ map.decEntriesCount();
}
return ret;
}
if (i->d_qtype == QType::ENT || i->d_qtype == qtype) {
i = content->d_map.erase(i);
++ret;
- --map.d_entriesCount;
+ map.decEntriesCount();
}
else {
++i;
for (auto& map : d_maps) {
auto m = map.lock();
m->d_map.clear();
- map.d_entriesCount = 0;
+ map.clearEntriesCount();
}
}
*
* \param maxEntries The maximum number of entries that may exist in the cache.
*/
-void NegCache::prune(size_t maxEntries)
+void NegCache::prune(time_t now, size_t maxEntries)
{
size_t cacheSize = size();
- pruneMutexCollectionsVector<SequenceTag>(*this, d_maps, maxEntries, cacheSize);
+ pruneMutexCollectionsVector<SequenceTag>(now, d_maps, maxEntries, cacheSize);
}
/*!
- * Writes the whole negative cache to fp
+ * Writes the whole negative cache to fd
*
- * \param fp A pointer to an open FILE object
+ * \param fd A pointer to an open FILE object
*/
size_t NegCache::doDump(int fd, size_t maxCacheEntries, time_t now)
{
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
close(newfd);
return 0;
}
- fprintf(fp.get(), "; negcache dump follows\n;\n");
+ fprintf(filePtr.get(), "; negcache dump follows\n;\n");
size_t ret = 0;
for (auto& mc : d_maps) {
auto m = mc.lock();
const auto shardSize = m->d_map.size();
- fprintf(fp.get(), "; negcache shard %zu; size %zu\n", shard, shardSize);
+ fprintf(filePtr.get(), "; negcache shard %zu; size %zu\n", shard, shardSize);
min = std::min(min, shardSize);
max = std::max(max, shardSize);
shard++;
for (const NegCacheEntry& ne : sidx) {
ret++;
int64_t ttl = ne.d_ttd - now;
- fprintf(fp.get(), "%s %" PRId64 " IN %s VIA %s ; (%s) origttl=%" PRIu32 " ss=%hu\n", ne.d_name.toString().c_str(), ttl, ne.d_qtype.toString().c_str(), ne.d_auth.toString().c_str(), vStateToString(ne.d_validationState).c_str(), ne.d_orig_ttl, ne.d_servedStale);
+ fprintf(filePtr.get(), "%s %" PRId64 " IN %s VIA %s ; (%s) origttl=%" PRIu32 " ss=%hu\n", ne.d_name.toString().c_str(), ttl, ne.d_qtype.toString().c_str(), ne.d_auth.toString().c_str(), vStateToString(ne.d_validationState).c_str(), ne.d_orig_ttl, ne.d_servedStale);
for (const auto& rec : ne.authoritySOA.records) {
- fprintf(fp.get(), "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.getContent()->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
+ fprintf(filePtr.get(), "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.getContent()->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
}
for (const auto& sig : ne.authoritySOA.signatures) {
- fprintf(fp.get(), "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.getContent()->getZoneRepresentation().c_str());
+ fprintf(filePtr.get(), "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.getContent()->getZoneRepresentation().c_str());
}
for (const auto& rec : ne.DNSSECRecords.records) {
- fprintf(fp.get(), "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.getContent()->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
+ fprintf(filePtr.get(), "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.getContent()->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
}
for (const auto& sig : ne.DNSSECRecords.signatures) {
- fprintf(fp.get(), "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.getContent()->getZoneRepresentation().c_str());
+ fprintf(filePtr.get(), "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.getContent()->getZoneRepresentation().c_str());
}
}
}
- fprintf(fp.get(), "; negcache size: %zu/%zu shards: %zu min/max shard size: %zu/%zu\n", size(), maxCacheEntries, d_maps.size(), min, max);
+ fprintf(filePtr.get(), "; negcache size: %zu/%zu shards: %zu min/max shard size: %zu/%zu\n", size(), maxCacheEntries, d_maps.size(), min, max);
return ret;
}
void add(const NegCacheEntry& ne);
void updateValidationStatus(const DNSName& qname, QType qtype, vState newState, boost::optional<time_t> capTTD);
bool get(const DNSName& qname, QType qtype, const struct timeval& now, NegCacheEntry& ne, bool typeMustMatch = false, bool serverStale = false, bool refresh = false);
- bool getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne, bool serveStale, bool refresh);
+ bool getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& negEntry, bool serveStale, bool refresh);
size_t count(const DNSName& qname);
size_t count(const DNSName& qname, QType qtype);
- void prune(size_t maxEntries);
+ void prune(time_t now, size_t maxEntries);
void clear();
size_t doDump(int fd, size_t maxCacheEntries, time_t now = time(nullptr));
size_t wipe(const DNSName& name, bool subtree = false);
uint64_t d_contended_count{0};
uint64_t d_acquired_count{0};
void invalidate() {}
+ void preRemoval(const NegCacheEntry& /* entry */) {}
};
- pdns::stat_t d_entriesCount{0};
LockGuardedTryHolder<MapCombo::LockedContent> lock()
{
return locked;
}
+ [[nodiscard]] auto getEntriesCount() const
+ {
+ return d_entriesCount.load();
+ }
+
+ void incEntriesCount()
+ {
+ ++d_entriesCount;
+ }
+
+ void decEntriesCount()
+ {
+ --d_entriesCount;
+ }
+
+ void clearEntriesCount()
+ {
+ d_entriesCount = 0;
+ }
+
private:
LockGuarded<LockedContent> d_content;
+ pdns::stat_t d_entriesCount{0};
};
vector<MapCombo> d_maps;
{
return d_maps.at(qname.hash() % d_maps.size());
}
-
-public:
- void preRemoval(MapCombo::LockedContent& /* map */, const NegCacheEntry& /* entry */) {}
};
// instances iterating and writing to the cache dir at the same time
bool PersistentSBF::init(bool ignore_pid)
{
- if (d_init)
- return false;
-
auto log = g_slog->withName("nod");
std::lock_guard<std::mutex> lock(d_cachedir_mutex);
if (d_cachedir.length()) {
return false;
}
}
- d_init = true;
return true;
}
void PersistentSBF::setCacheDir(const std::string& cachedir)
{
- if (!d_init) {
- filesystem::path p(cachedir);
- if (!exists(p))
- throw PDNSException("NODDB setCacheDir specified nonexistent directory: " + cachedir);
- else if (!is_directory(p))
- throw PDNSException("NODDB setCacheDir specified a file not a directory: " + cachedir);
- d_cachedir = cachedir;
+ filesystem::path path(cachedir);
+ if (!exists(path)) {
+ throw PDNSException("NODDB setCacheDir specified nonexistent directory: " + cachedir);
+ }
+ if (!is_directory(path)) {
+ throw PDNSException("NODDB setCacheDir specified a file not a directory: " + cachedir);
}
+ d_cachedir = cachedir;
}
// Dump the SBF to a file
private:
void remove_tmp_files(const boost::filesystem::path&, std::lock_guard<std::mutex>&);
- bool d_init{false};
LockGuarded<bf::stableBF> d_sbf; // Stable Bloom Filter
std::string d_cachedir;
std::string d_prefix = sbf_prefix;
+++ /dev/null
-#!/bin/sh
-### BEGIN INIT INFO
-# Provides: pdns-recursor
-# Required-Start: $network $remote_fs $syslog
-# Required-Stop: $network $remote_fs $syslog
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
-# Short-Description: PowerDNS Recursor
-### END INIT INFO
-# chkconfig: - 80 75
-# description: pdns_recursor is a versatile high performance recursing nameserver
-
-BINARYPATH=/usr/bin
-SBINARYPATH=/usr/sbin
-SOCKETPATH=/var/run
-
-pdns_server=$SBINARYPATH/pdns_recursor
-
-[ -f "$pdns_server" ] || exit 0
-
-[ -r /etc/default/pdns-recursor ] && . /etc/default/pdns-recursor
-
-[ "$START" = "no" ] && exit 0
-
-doPC()
-{
- ret=`$BINARYPATH/rec_control $EXTRAOPTS $1 $2 2> /dev/null`
-}
-
-
-doPC ping
-NOTRUNNING=$?
-
-case "$1" in
- status)
- if test "$NOTRUNNING" = "0"
- then
- echo "running"
- exit 0
- else
- echo "not running"
- # Note: 3 is a white lie. We currently don't *really*
- # know that it's not running, or if the ping failed for
- # other reasons (= 4).
- exit 3
- fi
- ;;
-
- stop)
- echo -n "Stopping PowerDNS recursing nameserver: "
- if test "$NOTRUNNING" = "0"
- then
- doPC quit
- echo $ret
- else
- echo "not running"
- exit 1
- fi
- ;;
-
-
- force-stop)
- echo -n "Stopping PowerDNS recursing nameserver: "
- killall -v -9 $pdns_server
- echo "killed"
- ;;
-
- start)
- echo -n "Starting PowerDNS recursing nameserver: "
- if test "$NOTRUNNING" = "0"
- then
- echo "already running"
- exit 1
- else
- $pdns_server --daemon
- if test "$?" = "0"
- then
- echo "started"
- fi
- fi
- ;;
-
- force-reload | restart)
- echo -n "Restarting PowerDNS recursing nameserver: "
- echo -n stopping and waiting..
- doPC quit
- sleep 3
- echo done
- $0 start
- ;;
-
- monitor)
- if test "$NOTRUNNING" = "0"
- then
- echo "already running"
- else
- $pdns_server --daemon=no --quiet=no --loglevel=9
- fi
- ;;
-
- *)
- echo pdns [start\|stop\|force-reload\|restart\|status\|monitor]
-
- ;;
-esac
-
-
Description=PowerDNS Recursor
Documentation=man:pdns_recursor(1) man:rec_control(1)
Documentation=https://doc.powerdns.com
-Wants=network-online.target nss-lookup.target
-Before=nss-lookup.target
+Wants=network-online.target
After=network-online.target time-sync.target
[Service]
thread_local ProtobufServersInfo t_protobufServers;
thread_local ProtobufServersInfo t_outgoingProtobufServers;
-thread_local std::unique_ptr<MT_t> MT; // the big MTasker
+thread_local std::unique_ptr<MT_t> g_multiTasker; // the big MTasker
std::unique_ptr<MemRecursorCache> g_recCache;
std::unique_ptr<NegCache> g_negCache;
std::unique_ptr<RecursorPacketCache> g_packetCache;
thread_local std::shared_ptr<notifyset_t> t_allowNotifyFor;
__thread struct timeval g_now; // timestamp, updated (too) frequently
-typedef map<int, ComboAddress> listenSocketsAddresses_t; // is shared across all threads right now
+using listenSocketsAddresses_t = map<int, ComboAddress>; // is shared across all threads right now
static listenSocketsAddresses_t g_listenSocketsAddresses; // is shared across all threads right now
static set<int> g_fromtosockets; // listen sockets that use 'sendfromto()' mechanism (without actually using sendfromto())
PaddingMode g_paddingMode;
uint16_t g_udpTruncationThreshold;
std::atomic<bool> g_quiet;
+bool g_allowNoRD;
bool g_logCommonErrors;
bool g_reusePort{false};
bool g_gettagNeedsEDNSOptions{false};
GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
uint64_t g_latencyStatSize;
-LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fd)
+LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fileDesc)
{
- *fd = makeClientSocket(toaddr.sin4.sin_family);
- if (*fd < 0) { // temporary error - receive exception otherwise
+ *fileDesc = makeClientSocket(toaddr.sin4.sin_family);
+ if (*fileDesc < 0) { // temporary error - receive exception otherwise
return LWResult::Result::OSLimitError;
}
- if (connect(*fd, (struct sockaddr*)(&toaddr), toaddr.getSocklen()) < 0) {
+ if (connect(*fileDesc, reinterpret_cast<const struct sockaddr*>(&toaddr), toaddr.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast))
int err = errno;
try {
- closesocket(*fd);
+ closesocket(*fileDesc);
}
catch (const PDNSException& e) {
SLOG(g_log << Logger::Error << "Error closing UDP socket after connect() failed: " << e.reason << endl,
}
// return a socket to the pool, or simply erase it
-void UDPClientSocks::returnSocket(int fd)
+void UDPClientSocks::returnSocket(int fileDesc)
{
try {
- t_fdm->removeReadFD(fd);
+ t_fdm->removeReadFD(fileDesc);
}
catch (const FDMultiplexerException& e) {
// we sometimes return a socket that has not yet been assigned to t_fdm
}
try {
- closesocket(fd);
+ closesocket(fileDesc);
}
catch (const PDNSException& e) {
SLOG(g_log << Logger::Error << "Error closing returned UDP socket: " << e.reason << endl,
#endif
ComboAddress sin;
while (--tries != 0) {
- in_port_t port;
+ in_port_t port = 0;
if (tries == 1) { // last iteration: fall back to kernel 'random'
port = 0;
}
sin = pdns::getQueryLocalAddress(family, port); // does htons for us
- if (::bind(ret, reinterpret_cast<struct sockaddr*>(&sin), sin.getSocklen()) >= 0) {
+ if (::bind(ret, reinterpret_cast<struct sockaddr*>(&sin), sin.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
break;
}
}
return ret;
}
-static void handleGenUDPQueryResponse(int fd, FDMultiplexer::funcparam_t& var)
+static void handleGenUDPQueryResponse(int fileDesc, FDMultiplexer::funcparam_t& var)
{
- std::shared_ptr<PacketID> pident = boost::any_cast<std::shared_ptr<PacketID>>(var);
+ auto pident = boost::any_cast<std::shared_ptr<PacketID>>(var);
PacketBuffer resp;
resp.resize(512);
ComboAddress fromaddr;
socklen_t addrlen = sizeof(fromaddr);
- ssize_t ret = recvfrom(fd, resp.data(), resp.size(), 0, (sockaddr*)&fromaddr, &addrlen);
+ ssize_t ret = recvfrom(fileDesc, resp.data(), resp.size(), 0, reinterpret_cast<sockaddr*>(&fromaddr), &addrlen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
if (fromaddr != pident->remote) {
SLOG(g_log << Logger::Notice << "Response received from the wrong remote host (" << fromaddr.toStringWithPort() << " instead of " << pident->remote.toStringWithPort() << "), discarding" << endl,
g_slog->withName("lua")->info(Logr::Notice, "Response received from the wrong remote host. discarding", "method", Logging::Loggable("GenUDPQueryResponse"), "fromaddr", Logging::Loggable(fromaddr), "expected", Logging::Loggable(pident->remote)));
}
- t_fdm->removeReadFD(fd);
+ t_fdm->removeReadFD(fileDesc);
if (ret >= 0) {
resp.resize(ret);
- MT->sendEvent(pident, &resp);
+ g_multiTasker->sendEvent(pident, &resp);
}
else {
PacketBuffer empty;
- MT->sendEvent(pident, &empty);
+ g_multiTasker->sendEvent(pident, &empty);
// cerr<<"Had some kind of error: "<<ret<<", "<<stringerror()<<endl;
}
}
PacketBuffer GenUDPQueryResponse(const ComboAddress& dest, const string& query)
{
- Socket s(dest.sin4.sin_family, SOCK_DGRAM);
- s.setNonBlocking();
+ Socket socket(dest.sin4.sin_family, SOCK_DGRAM);
+ socket.setNonBlocking();
ComboAddress local = pdns::getQueryLocalAddress(dest.sin4.sin_family, 0);
- s.bind(local);
- s.connect(dest);
- s.send(query);
+ socket.bind(local);
+ socket.connect(dest);
+ socket.send(query);
std::shared_ptr<PacketID> pident = std::make_shared<PacketID>();
- pident->fd = s.getHandle();
+ pident->fd = socket.getHandle();
pident->remote = dest;
pident->type = 0;
- t_fdm->addReadFD(s.getHandle(), handleGenUDPQueryResponse, pident);
+ t_fdm->addReadFD(socket.getHandle(), handleGenUDPQueryResponse, pident);
PacketBuffer data;
- int ret = MT->waitEvent(pident, &data, g_networkTimeoutMsec);
+ int ret = g_multiTasker->waitEvent(pident, &data, g_networkTimeoutMsec);
- if (!ret || ret == -1) { // timeout
- t_fdm->removeReadFD(s.getHandle());
+ if (ret == 0 || ret == -1) { // timeout
+ t_fdm->removeReadFD(socket.getHandle());
}
else if (data.empty()) { // error, EOF or other
// we could special case this
return data;
}
-static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t&);
+static void handleUDPServerResponse(int fileDesc, FDMultiplexer::funcparam_t& var);
thread_local std::unique_ptr<UDPClientSocks> t_udpclientsocks;
/* these two functions are used by LWRes */
-LWResult::Result asendto(const char* data, size_t len, int /* flags */,
- const ComboAddress& toaddr, uint16_t id, const DNSName& domain, uint16_t qtype, bool ecs, int* fd)
+LWResult::Result asendto(const void* data, size_t len, int /* flags */,
+ const ComboAddress& toAddress, uint16_t qid, const DNSName& domain, uint16_t qtype, bool ecs, int* fileDesc)
{
auto pident = std::make_shared<PacketID>();
pident->domain = domain;
- pident->remote = toaddr;
+ pident->remote = toAddress;
pident->type = qtype;
// We cannot merge ECS-enabled queries based on the ECS source only, as the scope
// See if there is an existing outstanding request we can chain on to, using partial equivalence
// function looking for the same query (qname and qtype) to the same host, but with a different
// message ID.
- auto chain = MT->d_waiters.equal_range(pident, PacketIDBirthdayCompare());
+ auto chain = g_multiTasker->getWaiters().equal_range(pident, PacketIDBirthdayCompare());
for (; chain.first != chain.second; chain.first++) {
// Line below detected an issue with the two ways of ordering PacketIDs (birthday and non-birthday)
- assert(chain.first->key->domain == pident->domain);
+ assert(chain.first->key->domain == pident->domain); // NOLINT
// don't chain onto existing chained waiter or a chain already processed
if (chain.first->key->fd > -1 && !chain.first->key->closed) {
- chain.first->key->chain.insert(id); // we can chain
- *fd = -1; // gets used in waitEvent / sendEvent later on
+ chain.first->key->chain.insert(qid); // we can chain
+ *fileDesc = -1; // gets used in waitEvent / sendEvent later on
return LWResult::Result::Success;
}
}
}
- auto ret = t_udpclientsocks->getSocket(toaddr, fd);
+ auto ret = t_udpclientsocks->getSocket(toAddress, fileDesc);
if (ret != LWResult::Result::Success) {
return ret;
}
- pident->fd = *fd;
- pident->id = id;
+ pident->fd = *fileDesc;
+ pident->id = qid;
- t_fdm->addReadFD(*fd, handleUDPServerResponse, pident);
- ssize_t sent = send(*fd, data, len, 0);
+ t_fdm->addReadFD(*fileDesc, handleUDPServerResponse, pident);
+ ssize_t sent = send(*fileDesc, data, len, 0);
int tmp = errno;
if (sent < 0) {
- t_udpclientsocks->returnSocket(*fd);
+ t_udpclientsocks->returnSocket(*fileDesc);
errno = tmp; // this is for logging purposes only
return LWResult::Result::PermanentError;
}
return LWResult::Result::Success;
}
-LWResult::Result arecvfrom(PacketBuffer& packet, int /* flags */, const ComboAddress& fromaddr, size_t* d_len,
- uint16_t id, const DNSName& domain, uint16_t qtype, int fd, const struct timeval* now)
+LWResult::Result arecvfrom(PacketBuffer& packet, int /* flags */, const ComboAddress& fromAddr, size_t& len,
+ uint16_t qid, const DNSName& domain, uint16_t qtype, int fileDesc, const struct timeval& now)
{
static const unsigned int nearMissLimit = ::arg().asNum("spoof-nearmiss-max");
auto pident = std::make_shared<PacketID>();
- pident->fd = fd;
- pident->id = id;
+ pident->fd = fileDesc;
+ pident->id = qid;
pident->domain = domain;
pident->type = qtype;
- pident->remote = fromaddr;
+ pident->remote = fromAddr;
- int ret = MT->waitEvent(pident, &packet, g_networkTimeoutMsec, now);
+ int ret = g_multiTasker->waitEvent(pident, &packet, g_networkTimeoutMsec, &now);
+ len = 0;
/* -1 means error, 0 means timeout, 1 means a result from handleUDPServerResponse() which might still be an error */
if (ret > 0) {
return LWResult::Result::PermanentError;
}
- *d_len = packet.size();
+ len = packet.size();
if (nearMissLimit > 0 && pident->nearMisses > nearMissLimit) {
/* we have received more than nearMissLimit answers on the right IP and port, from the right source (we are using connected sockets),
for the correct qname and qtype, but with an unexpected message ID. That looks like a spoofing attempt. */
- SLOG(g_log << Logger::Error << "Too many (" << pident->nearMisses << " > " << nearMissLimit << ") answers with a wrong message ID for '" << domain << "' from " << fromaddr.toString() << ", assuming spoof attempt." << endl,
+ SLOG(g_log << Logger::Error << "Too many (" << pident->nearMisses << " > " << nearMissLimit << ") answers with a wrong message ID for '" << domain << "' from " << fromAddr.toString() << ", assuming spoof attempt." << endl,
g_slogudpin->info(Logr::Error, "Too many answers with a wrong message ID, assuming spoofing attempt",
"nearmisses", Logging::Loggable(pident->nearMisses),
"nearmisslimit", Logging::Loggable(nearMissLimit),
"qname", Logging::Loggable(domain),
- "from", Logging::Loggable(fromaddr)));
+ "from", Logging::Loggable(fromAddr)));
t_Counters.at(rec::Counter::spoofCount)++;
return LWResult::Result::Spoofed;
}
return LWResult::Result::Success;
}
- else {
- /* getting there means error or timeout, it's up to us to close the socket */
- if (fd >= 0) {
- t_udpclientsocks->returnSocket(fd);
- }
+ /* getting there means error or timeout, it's up to us to close the socket */
+ if (fileDesc >= 0) {
+ t_udpclientsocks->returnSocket(fileDesc);
}
return ret == 0 ? LWResult::Result::Timeout : LWResult::Result::PermanentError;
// the idea is, only do things that depend on the *response* here. Incoming accounting is on incoming.
static void updateResponseStats(int res, const ComboAddress& remote, unsigned int packetsize, const DNSName* query, uint16_t qtype)
{
- if (packetsize > 1000 && t_largeanswerremotes)
+ if (packetsize > 1000 && t_largeanswerremotes) {
t_largeanswerremotes->push_back(remote);
+ }
switch (res) {
case RCode::ServFail:
if (t_servfailremotes) {
t_servfailremotes->push_back(remote);
- if (query && t_servfailqueryring) // packet cache
+ if (query != nullptr && t_servfailqueryring) { // packet cache
t_servfailqueryring->push_back({*query, qtype});
+ }
}
++t_Counters.at(rec::Counter::servFails);
break;
}
}
-static string makeLoginfo(const std::unique_ptr<DNSComboWriter>& dc)
+static string makeLoginfo(const std::unique_ptr<DNSComboWriter>& comboWriter)
try {
- return "(" + dc->d_mdp.d_qname.toLogString() + "/" + DNSRecordContent::NumberToType(dc->d_mdp.d_qtype) + " from " + (dc->getRemote()) + ")";
+ return "(" + comboWriter->d_mdp.d_qname.toLogString() + "/" + DNSRecordContent::NumberToType(comboWriter->d_mdp.d_qtype) + " from " + (comboWriter->getRemote()) + ")";
}
catch (...) {
return "Exception making error message for exception";
* @param res: An integer that will contain the RCODE of the lookup we do
* @param ret: A vector of DNSRecords where the result of the CNAME chase should be appended to
*/
-static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRes& sr, int& res, vector<DNSRecord>& ret)
+static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRes& resolver, int& res, vector<DNSRecord>& ret)
{
if (spoofed.d_type == QType::CNAME) {
- bool oldWantsRPZ = sr.getWantsRPZ();
- sr.setWantsRPZ(false);
+ bool oldWantsRPZ = resolver.getWantsRPZ();
+ resolver.setWantsRPZ(false);
vector<DNSRecord> ans;
- res = sr.beginResolve(DNSName(spoofed.getContent()->getZoneRepresentation()), qtype, QClass::IN, ans);
+ res = resolver.beginResolve(DNSName(spoofed.getContent()->getZoneRepresentation()), qtype, QClass::IN, ans);
for (const auto& rec : ans) {
if (rec.d_place == DNSResourceRecord::ANSWER) {
ret.push_back(rec);
}
}
// Reset the RPZ state of the SyncRes
- sr.setWantsRPZ(oldWantsRPZ);
+ resolver.setWantsRPZ(oldWantsRPZ);
}
}
-static bool addRecordToPacket(DNSPacketWriter& pw, const DNSRecord& rec, uint32_t& minTTL, uint32_t ttlCap, const uint16_t maxAnswerSize, bool& seenAuthSOA)
+static bool addRecordToPacket(DNSPacketWriter& packetWritewr, const DNSRecord& rec, uint32_t& minTTL, uint32_t ttlCap, const uint16_t maxAnswerSize, bool& seenAuthSOA)
{
- pw.startRecord(rec.d_name, rec.d_type, (rec.d_ttl > ttlCap ? ttlCap : rec.d_ttl), rec.d_class, rec.d_place);
+ packetWritewr.startRecord(rec.d_name, rec.d_type, (rec.d_ttl > ttlCap ? ttlCap : rec.d_ttl), rec.d_class, rec.d_place);
if (rec.d_type == QType::SOA && rec.d_place == DNSResourceRecord::AUTHORITY) {
seenAuthSOA = true;
}
- if (rec.d_type != QType::OPT) // their TTL ain't real
+ if (rec.d_type != QType::OPT) { // their TTL ain't real
minTTL = min(minTTL, rec.d_ttl);
+ }
- rec.getContent()->toPacket(pw);
- if (pw.size() > static_cast<size_t>(maxAnswerSize)) {
- pw.rollback();
+ rec.getContent()->toPacket(packetWritewr);
+ if (packetWritewr.size() > static_cast<size_t>(maxAnswerSize)) {
+ packetWritewr.rollback();
if (rec.d_place != DNSResourceRecord::ADDITIONAL) {
- pw.getHeader()->tc = 1;
- pw.truncate();
+ packetWritewr.getHeader()->tc = 1;
+ packetWritewr.truncate();
}
return false;
}
class RunningResolveGuard
{
public:
- RunningResolveGuard(std::unique_ptr<DNSComboWriter>& dc) :
- d_dc(dc)
+ RunningResolveGuard(const RunningResolveGuard&) = default;
+ RunningResolveGuard(RunningResolveGuard&&) = delete;
+ RunningResolveGuard& operator=(const RunningResolveGuard&) = delete;
+ RunningResolveGuard& operator=(RunningResolveGuard&&) = delete;
+ RunningResolveGuard(std::unique_ptr<DNSComboWriter>& comboWriter) :
+ d_dc(comboWriter)
{
if (d_dc->d_tcp && !d_dc->d_tcpConnection) {
throw std::runtime_error("incoming TCP case without TCP connection");
Drop
};
-static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& dc, SyncRes& sr, int& res, vector<DNSRecord>& ret, DNSPacketWriter& pw, RunningResolveGuard& tcpGuard)
+static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& comboWriter, SyncRes& resolver, int& res, vector<DNSRecord>& ret, DNSPacketWriter& packetWriter, RunningResolveGuard& tcpGuard)
{
/* don't account truncate actions for TCP queries, since they are not applied */
- if (appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !dc->d_tcp) {
+ if (appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !comboWriter->d_tcp) {
++t_Counters.at(rec::PolicyHistogram::policy).at(appliedPolicy.d_kind);
++t_Counters.at(rec::PolicyNameHits::policyName).counts[appliedPolicy.getName()];
}
- if (sr.doLog() && appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
- SLOG(g_log << Logger::Warning << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << appliedPolicy.getLogString() << endl,
- appliedPolicy.info(Logr::Warning, sr.d_slog));
+ if (resolver.doLog() && appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
+ SLOG(g_log << Logger::Warning << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << appliedPolicy.getLogString() << endl,
+ appliedPolicy.info(Logr::Warning, resolver.d_slog));
}
if (appliedPolicy.d_zoneData && appliedPolicy.d_zoneData->d_extendedErrorCode) {
- dc->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
- dc->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
+ comboWriter->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
+ comboWriter->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
}
switch (appliedPolicy.d_kind) {
case DNSFilterEngine::PolicyKind::NXDOMAIN:
ret.clear();
+ appliedPolicy.addSOAtoRPZResult(ret);
res = RCode::NXDomain;
return PolicyResult::HaveAnswer;
case DNSFilterEngine::PolicyKind::NODATA:
ret.clear();
+ appliedPolicy.addSOAtoRPZResult(ret);
res = RCode::NoError;
return PolicyResult::HaveAnswer;
case DNSFilterEngine::PolicyKind::Truncate:
- if (!dc->d_tcp) {
+ if (!comboWriter->d_tcp) {
ret.clear();
+ appliedPolicy.addSOAtoRPZResult(ret);
res = RCode::NoError;
- pw.getHeader()->tc = 1;
+ packetWriter.getHeader()->tc = 1;
return PolicyResult::HaveAnswer;
}
return PolicyResult::NoAction;
case DNSFilterEngine::PolicyKind::Custom:
res = RCode::NoError;
{
- auto spoofed = appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
- for (auto& dr : spoofed) {
- ret.push_back(dr);
+ auto spoofed = appliedPolicy.getCustomRecords(comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype);
+ for (auto& record : spoofed) {
+ ret.push_back(record);
try {
- handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
+ handleRPZCustom(record, QType(comboWriter->d_mdp.d_qtype), resolver, res, ret);
}
catch (const ImmediateServFailException& e) {
if (g_logCommonErrors) {
- SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << dc->d_mdp.d_qname << "' because: " << e.reason << endl,
- sr.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve of the custom filter policy",
- "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("ImmediateServFailException")));
+ SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because: " << e.reason << endl,
+ resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve of the custom filter policy",
+ "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("ImmediateServFailException")));
+ }
+ res = RCode::ServFail;
+ break;
+ }
+ catch (const pdns::validation::TooManySEC3IterationsException& e) {
+ if (g_logCommonErrors || (g_dnssecLogBogus && resolver.getDNSSECLimitHit())) {
+ SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl,
+ resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve of the custom filter policy",
+ "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("TooManySEC3IterationsException"), "dnsseclimithit", Logging::Loggable(resolver.getDNSSECLimitHit())));
}
res = RCode::ServFail;
break;
}
catch (const PolicyHitException& e) {
if (g_logCommonErrors) {
- SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << dc->d_mdp.d_qname << "' because another RPZ policy was hit" << endl,
- sr.d_slog->info(Logr::Notice, "Sending SERVFAIL during resolve of the custom filter policy because another RPZ policy was hit",
- "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("PolicyHitException")));
+ SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because another RPZ policy was hit" << endl,
+ resolver.d_slog->info(Logr::Notice, "Sending SERVFAIL during resolve of the custom filter policy because another RPZ policy was hit",
+ "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("PolicyHitException")));
}
res = RCode::ServFail;
break;
}
}
+ appliedPolicy.addSOAtoRPZResult(ret);
return PolicyResult::HaveAnswer;
}
}
SLOG(g_log << Logger::Notice << "Newly observed domain nod=" << dname << endl,
nodlogger->info(Logr::Notice, "New domain observed"));
}
+ t_Counters.at(rec::Counter::nodCount)++;
ret = true;
}
}
bool ret = false;
if (record.d_place == DNSResourceRecord::ANSWER || record.d_place == DNSResourceRecord::ADDITIONAL) {
// Create a string that represent a triplet of (qname, qtype and RR[type, name, content])
- std::stringstream ss;
- ss << dname.toDNSStringLC() << ":" << qtype << ":" << qtype << ":" << record.d_type << ":" << record.d_name.toDNSStringLC() << ":" << record.getContent()->getZoneRepresentation();
- if (t_udrDBp && t_udrDBp->isUniqueResponse(ss.str())) {
+ std::stringstream strStream;
+ strStream << dname.toDNSStringLC() << ":" << qtype << ":" << qtype << ":" << record.d_type << ":" << record.d_name.toDNSStringLC() << ":" << record.getContent()->getZoneRepresentation();
+ if (t_udrDBp && t_udrDBp->isUniqueResponse(strStream.str())) {
if (g_udrLog) {
// This should also probably log to a dedicated file.
SLOG(g_log << Logger::Notice << "Unique response observed: qname=" << dname << " qtype=" << QType(qtype) << " rrtype=" << QType(record.d_type) << " rrname=" << record.d_name << " rrcontent=" << record.getContent()->getZoneRepresentation() << endl,
- nodlogger->info(Logr::Debug, "New response observed",
+ nodlogger->info(Logr::Notice, "New response observed",
"qtype", Logging::Loggable(QType(qtype)),
"rrtype", Logging::Loggable(QType(record.d_type)),
"rrname", Logging::Loggable(record.d_name),
"rrcontent", Logging::Loggable(record.getContent()->getZoneRepresentation())););
}
+ t_Counters.at(rec::Counter::udrCount)++;
ret = true;
}
}
{
vector<DNSRecord> resolved;
DNSName target;
- for (const DNSRecord& rr : ret) {
- if (rr.d_type == QType::CNAME) {
- auto rec = getRR<CNAMERecordContent>(rr);
+ for (const DNSRecord& record : ret) {
+ if (record.d_type == QType::CNAME) {
+ auto rec = getRR<CNAMERecordContent>(record);
if (rec) {
target = rec->getTarget();
break;
rcode = getFakeAAAARecords(target, *g_dns64Prefix, resolved);
}
- for (DNSRecord& rr : resolved) {
- if (rr.d_place == DNSResourceRecord::ANSWER) {
- ret.push_back(std::move(rr));
+ for (DNSRecord& record : resolved) {
+ if (record.d_place == DNSResourceRecord::ANSWER) {
+ ret.push_back(std::move(record));
}
}
return rcode;
ret.erase(std::remove_if(
ret.begin(),
ret.end(),
- [&seenCNAMEs](DNSRecord& rr) {
- if (rr.d_type == QType::CNAME) {
- auto target = getRR<CNAMERecordContent>(rr);
+ [&seenCNAMEs](DNSRecord& record) {
+ if (record.d_type == QType::CNAME) {
+ auto target = getRR<CNAMERecordContent>(record);
if (target == nullptr) {
return false;
}
ret.end());
bool seenA = false;
- for (DNSRecord& rr : ret) {
- if (rr.d_type == QType::A && rr.d_place == DNSResourceRecord::ANSWER) {
- if (auto rec = getRR<ARecordContent>(rr)) {
+ for (DNSRecord& record : ret) {
+ if (record.d_type == QType::A && record.d_place == DNSResourceRecord::ANSWER) {
+ if (auto rec = getRR<ARecordContent>(record)) {
ComboAddress ipv4(rec->getCA());
memcpy(&prefix.sin6.sin6_addr.s6_addr[12], &ipv4.sin4.sin_addr.s_addr, sizeof(ipv4.sin4.sin_addr.s_addr));
- rr.setContent(std::make_shared<AAAARecordContent>(prefix));
- rr.d_type = QType::AAAA;
+ record.setContent(std::make_shared<AAAARecordContent>(prefix));
+ record.d_type = QType::AAAA;
}
seenA = true;
}
ret.erase(std::remove_if(
ret.begin(),
ret.end(),
- [](DNSRecord& rr) {
- return (rr.d_type == QType::SOA && rr.d_place == DNSResourceRecord::AUTHORITY);
+ [](DNSRecord& record) {
+ return (record.d_type == QType::SOA && record.d_place == DNSResourceRecord::AUTHORITY);
}),
ret.end());
}
}
string newquery;
- for (int n = 0; n < 4; ++n) {
- newquery += std::to_string(stoll(parts[n * 2], 0, 16) + 16 * stoll(parts[n * 2 + 1], 0, 16));
+ for (size_t octet = 0; octet < 4; ++octet) {
+ newquery += std::to_string(stoll(parts[octet * 2], nullptr, 16) + 16 * stoll(parts[octet * 2 + 1], nullptr, 16));
newquery.append(1, '.');
}
newquery += "in-addr.arpa.";
- DNSRecord rr;
- rr.d_name = qname;
- rr.d_type = QType::CNAME;
- rr.setContent(std::make_shared<CNAMERecordContent>(newquery));
- ret.push_back(rr);
-
auto log = g_slog->withName("dns64")->withValues("method", Logging::Loggable("getPTR"));
- int rcode = directResolve(DNSName(newquery), QType::PTR, QClass::IN, ret, t_pdl, log);
+ vector<DNSRecord> answers;
+ int rcode = directResolve(DNSName(newquery), QType::PTR, QClass::IN, answers, t_pdl, log);
+
+ DNSRecord record;
+ record.d_name = qname;
+ record.d_type = QType::CNAME;
+ record.setContent(std::make_shared<CNAMERecordContent>(newquery));
+ // Copy the TTL of the synthesized CNAME from the actual answer
+ record.d_ttl = (rcode == RCode::NoError && !answers.empty()) ? answers.at(0).d_ttl : SyncRes::s_minimumTTL;
+ ret.push_back(record);
+
+ ret.insert(ret.end(), answers.begin(), answers.end());
t_Counters.at(rec::Counter::dns64prefixanswers)++;
return rcode;
}
-static bool answerIsNOData(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records)
-{
- if (rcode != RCode::NoError) {
- return false;
- }
- for (const auto& rec : records) {
- if (rec.d_place != DNSResourceRecord::ANSWER) {
- /* no records in the answer section */
- return true;
- }
- if (rec.d_type == requestedType) {
- /* we have a record, of the right type, in the right section */
- return false;
- }
- }
- return true;
-}
-
// RFC 6147 section 5.1 all rcodes except NXDomain should be candidate for dns64
// for NoError, check if it is NoData
static bool dns64Candidate(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records)
{
if (rcode == RCode::NoError) {
- return answerIsNOData(requestedType, rcode, records);
+ return SyncRes::answerIsNOData(requestedType, rcode, records);
}
return rcode != RCode::NXDomain;
}
notifyset_t::const_iterator ret;
do {
ret = t_allowNotifyFor->find(qname);
- if (ret != t_allowNotifyFor->end())
+ if (ret != t_allowNotifyFor->end()) {
return true;
+ }
} while (qname.chopOff());
return false;
}
if (trace.empty()) {
return;
}
+ if (t_tracefd < 0) {
+ std::istringstream buf(trace);
+ g_log << Logger::Warning << "=== START OF FAIL TRACE ====" << endl;
+ for (string line; std::getline(buf, line);) {
+ g_log << Logger::Warning << line << endl;
+ }
+ g_log << Logger::Warning << "=== END OF FAIL TRACE ====" << endl;
+ return;
+ }
timeval now{};
Utility::gettimeofday(&now);
int traceFd = dup(t_tracefd);
return;
}
setNonBlocking(traceFd);
- auto filep = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(traceFd, "a"), &fclose);
+ auto filep = pdns::UniqueFilePtr(fdopen(traceFd, "a"));
if (!filep) {
int err = errno;
SLOG(g_log << Logger::Error << "Could not write to trace file: " << stringerror(err) << endl,
close(traceFd);
return;
}
- std::array<char, 64> timebuf;
- isoDateTimeMillis(timev, timebuf.data(), timebuf.size());
+ timebuf_t timebuf;
+ isoDateTimeMillis(timev, timebuf);
fprintf(filep.get(), " us === START OF TRACE %s ===\n", timebuf.data());
fprintf(filep.get(), "%s", trace.c_str());
- isoDateTimeMillis(now, timebuf.data(), timebuf.size());
- fprintf(filep.get(), "=== END OF TRACE %s ===\n", timebuf.data());
- if (ferror(filep.get())) {
+ isoDateTimeMillis(now, timebuf);
+ if (ferror(filep.get()) != 0) {
int err = errno;
SLOG(g_log << Logger::Error << "Problems writing to trace file: " << stringerror(err) << endl,
g_slog->withName("trace")->error(Logr::Error, err, "Problems writing to trace file"));
+ // There's no guarantee the message below will end up in the stream, but we try our best
+ clearerr(filep.get());
+ fprintf(filep.get(), "=== TRACE %s TRUNCATED; USE FILE ARGUMENT INSTEAD OF `-' ===\n", timebuf.data());
+ }
+ else {
+ fprintf(filep.get(), "=== END OF TRACE %s ===\n", timebuf.data());
}
// fclose by unique_ptr does implicit flush
}
return ttl;
}
-void startDoResolve(void* p)
+static void addPolicyTagsToPBMessageIfNeeded(DNSComboWriter& comboWriter, pdns::ProtoZero::RecMessage& pbMessage)
{
- auto dc = std::unique_ptr<DNSComboWriter>(reinterpret_cast<DNSComboWriter*>(p));
- SyncRes sr(dc->d_now);
+ /* we do _not_ want to store policy tags set by the gettag hook into the packet cache,
+ since the call to gettag for subsequent queries could yield the same PC tag but different policy tags */
+ if (!comboWriter.d_gettagPolicyTags.empty()) {
+ for (const auto& tag : comboWriter.d_gettagPolicyTags) {
+ comboWriter.d_policyTags.erase(tag);
+ }
+ }
+ if (!comboWriter.d_policyTags.empty()) {
+ pbMessage.addPolicyTags(comboWriter.d_policyTags);
+ }
+}
+
+void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
+{
+ auto comboWriter = std::unique_ptr<DNSComboWriter>(static_cast<DNSComboWriter*>(arg));
+ SyncRes resolver(comboWriter->d_now);
try {
- if (t_queryring)
- t_queryring->push_back({dc->d_mdp.d_qname, dc->d_mdp.d_qtype});
+ if (t_queryring) {
+ t_queryring->push_back({comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype});
+ }
- uint16_t maxanswersize = dc->d_tcp ? 65535 : min(static_cast<uint16_t>(512), g_udpTruncationThreshold);
+ uint16_t maxanswersize = comboWriter->d_tcp ? 65535 : min(static_cast<uint16_t>(512), g_udpTruncationThreshold);
EDNSOpts edo;
std::vector<pair<uint16_t, string>> ednsOpts;
- bool variableAnswer = dc->d_variable;
+ bool variableAnswer = comboWriter->d_variable;
bool haveEDNS = false;
bool paddingAllowed = false;
bool addPaddingToResponse = false;
bool hasUDR = false;
std::shared_ptr<Logr::Logger> nodlogger{nullptr};
if (g_udrEnabled || g_nodEnabled) {
- nodlogger = g_slog->withName("nod")->v(1)->withValues("qname", Logging::Loggable(dc->d_mdp.d_qname));
+ nodlogger = g_slog->withName("nod")->v(1)->withValues("qname", Logging::Loggable(comboWriter->d_mdp.d_qname));
}
#endif /* NOD_ENABLED */
DNSPacketWriter::optvect_t returnedEdnsOptions; // Here we stuff all the options for the return packet
uint8_t ednsExtRCode = 0;
- if (getEDNSOpts(dc->d_mdp, &edo)) {
+ if (getEDNSOpts(comboWriter->d_mdp, &edo)) {
haveEDNS = true;
if (edo.d_version != 0) {
ednsExtRCode = ERCode::BADVERS;
}
- if (!dc->d_tcp) {
+ if (!comboWriter->d_tcp) {
/* rfc6891 6.2.3:
"Values lower than 512 MUST be treated as equal to 512."
*/
ednsOpts = edo.d_options;
maxanswersize -= 11; // EDNS header size
- if (!dc->d_responsePaddingDisabled && g_paddingFrom.match(dc->d_remote)) {
+ if (!comboWriter->d_responsePaddingDisabled && g_paddingFrom.match(comboWriter->d_remote)) {
paddingAllowed = true;
if (g_paddingMode == PaddingMode::Always) {
addPaddingToResponse = true;
}
}
- for (const auto& o : edo.d_options) {
- if (o.first == EDNSOptionCode::ECS && g_useIncomingECS && !dc->d_ecsParsed) {
- dc->d_ecsFound = getEDNSSubnetOptsFromString(o.second, &dc->d_ednssubnet);
+ for (const auto& option : edo.d_options) {
+ if (option.first == EDNSOptionCode::ECS && g_useIncomingECS && !comboWriter->d_ecsParsed) {
+ comboWriter->d_ecsFound = getEDNSSubnetOptsFromString(option.second, &comboWriter->d_ednssubnet);
}
- else if (o.first == EDNSOptionCode::NSID) {
+ else if (option.first == EDNSOptionCode::NSID) {
const static string mode_server_id = ::arg()["server-id"];
if (mode_server_id != "disabled" && !mode_server_id.empty() && maxanswersize > (EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size())) {
returnedEdnsOptions.emplace_back(EDNSOptionCode::NSID, mode_server_id);
maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size();
}
}
- else if (paddingAllowed && !addPaddingToResponse && g_paddingMode == PaddingMode::PaddedQueries && o.first == EDNSOptionCode::PADDING) {
+ else if (paddingAllowed && !addPaddingToResponse && g_paddingMode == PaddingMode::PaddedQueries && option.first == EDNSOptionCode::PADDING) {
addPaddingToResponse = true;
}
}
/* the lookup will be done _before_ knowing whether the query actually
has a padding option, so we need to use the separate tag even when the
query does not have padding, as long as it is from an allowed source */
- if (paddingAllowed && dc->d_tag == 0) {
- dc->d_tag = g_paddingTag;
+ if (paddingAllowed && comboWriter->d_tag == 0) {
+ comboWriter->d_tag = g_paddingTag;
}
/* perhaps there was no EDNS or no ECS but by now we looked */
- dc->d_ecsParsed = true;
+ comboWriter->d_ecsParsed = true;
vector<DNSRecord> ret;
vector<uint8_t> packet;
pdns::ProtoZero::RecMessage pbMessage;
if (checkProtobufExport(luaconfsLocal)) {
pbMessage.reserve(128, 128); // It's a bit of a guess...
- pbMessage.setResponse(dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass);
+ pbMessage.setResponse(comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass);
pbMessage.setServerIdentity(SyncRes::s_serverID);
// RRSets added below
checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
#endif
- DNSPacketWriter pw(packet, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass, dc->d_mdp.d_header.opcode);
+ DNSPacketWriter packetWriter(packet, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass, comboWriter->d_mdp.d_header.opcode);
- pw.getHeader()->aa = 0;
- pw.getHeader()->ra = 1;
- pw.getHeader()->qr = 1;
- pw.getHeader()->tc = 0;
- pw.getHeader()->id = dc->d_mdp.d_header.id;
- pw.getHeader()->rd = dc->d_mdp.d_header.rd;
- pw.getHeader()->cd = dc->d_mdp.d_header.cd;
+ packetWriter.getHeader()->aa = 0;
+ packetWriter.getHeader()->ra = 1;
+ packetWriter.getHeader()->qr = 1;
+ packetWriter.getHeader()->tc = 0;
+ packetWriter.getHeader()->id = comboWriter->d_mdp.d_header.id;
+ packetWriter.getHeader()->rd = comboWriter->d_mdp.d_header.rd;
+ packetWriter.getHeader()->cd = comboWriter->d_mdp.d_header.cd;
/* This is the lowest TTL seen in the records of the response,
so we can't cache it for longer than this value.
If we have a TTL cap, this value can't be larger than the
cap no matter what. */
- uint32_t minTTL = dc->d_ttlCap;
+ uint32_t minTTL = comboWriter->d_ttlCap;
bool seenAuthSOA = false;
- sr.d_eventTrace = std::move(dc->d_eventTrace);
- sr.setId(MT->getTid());
+ resolver.d_eventTrace = std::move(comboWriter->d_eventTrace);
+ resolver.setId(g_multiTasker->getTid());
bool DNSSECOK = false;
- if (dc->d_luaContext) {
- sr.setLuaEngine(dc->d_luaContext);
+ if (comboWriter->d_luaContext) {
+ resolver.setLuaEngine(comboWriter->d_luaContext);
}
if (g_dnssecmode != DNSSECMode::Off) {
- sr.setDoDNSSEC(true);
+ resolver.setDoDNSSEC(true);
// Does the requestor want DNSSEC records?
- if (edo.d_extFlags & EDNSOpts::DNSSECOK) {
+ if ((edo.d_extFlags & EDNSOpts::DNSSECOK) != 0) {
DNSSECOK = true;
t_Counters.at(rec::Counter::dnssecQueries)++;
}
- if (dc->d_mdp.d_header.cd) {
+ if (comboWriter->d_mdp.d_header.cd) {
/* Per rfc6840 section 5.9, "When processing a request with
the Checking Disabled (CD) bit set, a resolver SHOULD attempt
to return all response data, even data that has failed DNSSEC
validation. */
++t_Counters.at(rec::Counter::dnssecCheckDisabledQueries);
}
- if (dc->d_mdp.d_header.ad) {
+ if (comboWriter->d_mdp.d_header.ad) {
/* Per rfc6840 section 5.7, "the AD bit in a query as a signal
indicating that the requester understands and is interested in the
value of the AD bit in the response. This allows a requester to
}
else {
// Ignore the client-set CD flag
- pw.getHeader()->cd = 0;
+ packetWriter.getHeader()->cd = 0;
}
- sr.setDNSSECValidationRequested(g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || ((dc->d_mdp.d_header.ad || DNSSECOK) && g_dnssecmode == DNSSECMode::Process));
+ resolver.setDNSSECValidationRequested(g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || ((comboWriter->d_mdp.d_header.ad || DNSSECOK) && g_dnssecmode == DNSSECMode::Process));
- sr.setInitialRequestId(dc->d_uuid);
- sr.setOutgoingProtobufServers(t_outgoingProtobufServers.servers);
+ resolver.setInitialRequestId(comboWriter->d_uuid);
+ resolver.setOutgoingProtobufServers(t_outgoingProtobufServers.servers);
#ifdef HAVE_FSTRM
- sr.setFrameStreamServers(t_frameStreamServersInfo.servers);
+ resolver.setFrameStreamServers(t_frameStreamServersInfo.servers);
#endif
bool useMapped = true;
// If proxy by table is active and had a match, we only want to use the mapped address if it also has a domain match
// (if a domain suffix match table is present in the config)
- if (t_proxyMapping && dc->d_source != dc->d_mappedSource) {
- if (auto it = t_proxyMapping->lookup(dc->d_source)) {
- if (it->second.suffixMatchNode) {
- if (!it->second.suffixMatchNode->check(dc->d_mdp.d_qname)) {
+ if (t_proxyMapping && comboWriter->d_source != comboWriter->d_mappedSource) {
+ if (const auto* iter = t_proxyMapping->lookup(comboWriter->d_source)) {
+ if (iter->second.suffixMatchNode) {
+ if (!iter->second.suffixMatchNode->check(comboWriter->d_mdp.d_qname)) {
// No match in domains, use original source
useMapped = false;
}
else {
- ++it->second.stats.suffixMatches;
+ ++iter->second.stats.suffixMatches;
}
}
// No suffix match node defined, use mapped address
}
// lookup failing cannot happen as dc->d_source != dc->d_mappedSource
}
- sr.setQuerySource(useMapped ? dc->d_mappedSource : dc->d_source, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
+ resolver.setQuerySource(useMapped ? comboWriter->d_mappedSource : comboWriter->d_source, g_useIncomingECS && !comboWriter->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(comboWriter->d_ednssubnet) : boost::none);
- sr.setQueryReceivedOverTCP(dc->d_tcp);
+ resolver.setQueryReceivedOverTCP(comboWriter->d_tcp);
bool tracedQuery = false; // we could consider letting Lua know about this too
bool shouldNotValidate = false;
int res = RCode::NoError;
DNSFilterEngine::Policy appliedPolicy;
- RecursorLua4::DNSQuestion dq(dc->d_source, dc->d_destination, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_tcp, variableAnswer, wantsRPZ, dc->d_logResponse, addPaddingToResponse, (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec != 0) ? dc->d_kernelTimestamp : dc->d_now);
- dq.ednsFlags = &edo.d_extFlags;
- dq.ednsOptions = &ednsOpts;
- dq.tag = dc->d_tag;
- dq.discardedPolicies = &sr.d_discardedPolicies;
- dq.policyTags = &dc->d_policyTags;
- dq.appliedPolicy = &appliedPolicy;
- dq.currentRecords = &ret;
- dq.dh = &dc->d_mdp.d_header;
- dq.data = dc->d_data;
- dq.requestorId = dc->d_requestorId;
- dq.deviceId = dc->d_deviceId;
- dq.deviceName = dc->d_deviceName;
- dq.proxyProtocolValues = &dc->d_proxyProtocolValues;
- dq.extendedErrorCode = &dc->d_extendedErrorCode;
- dq.extendedErrorExtra = &dc->d_extendedErrorExtra;
- dq.meta = std::move(dc->d_meta);
- dq.fromAuthIP = &sr.d_fromAuthIP;
-
- sr.d_slog = sr.d_slog->withValues("qname", Logging::Loggable(dc->d_mdp.d_qname),
- "qtype", Logging::Loggable(QType(dc->d_mdp.d_qtype)),
- "remote", Logging::Loggable(dc->getRemote()),
- "proto", Logging::Loggable(dc->d_tcp ? "tcp" : "udp"),
- "ecs", Logging::Loggable(dc->d_ednssubnet.source.empty() ? "" : dc->d_ednssubnet.source.toString()),
- "mtid", Logging::Loggable(MT->getTid()));
- RunningResolveGuard tcpGuard(dc);
-
- if (ednsExtRCode != 0 || dc->d_mdp.d_header.opcode == Opcode::Notify) {
- goto sendit;
- }
-
- if (dc->d_mdp.d_qtype == QType::ANY && !dc->d_tcp && g_anyToTcp) {
- pw.getHeader()->tc = 1;
+ RecursorLua4::DNSQuestion dnsQuestion(comboWriter->d_source, comboWriter->d_destination, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_tcp, variableAnswer, wantsRPZ, comboWriter->d_logResponse, addPaddingToResponse, (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) ? comboWriter->d_kernelTimestamp : comboWriter->d_now);
+ dnsQuestion.ednsFlags = &edo.d_extFlags;
+ dnsQuestion.ednsOptions = &ednsOpts;
+ dnsQuestion.tag = comboWriter->d_tag;
+ dnsQuestion.discardedPolicies = &resolver.d_discardedPolicies;
+ dnsQuestion.policyTags = &comboWriter->d_policyTags;
+ dnsQuestion.appliedPolicy = &appliedPolicy;
+ dnsQuestion.currentRecords = &ret;
+ dnsQuestion.dh = &comboWriter->d_mdp.d_header;
+ dnsQuestion.data = comboWriter->d_data;
+ dnsQuestion.requestorId = comboWriter->d_requestorId;
+ dnsQuestion.deviceId = comboWriter->d_deviceId;
+ dnsQuestion.deviceName = comboWriter->d_deviceName;
+ dnsQuestion.proxyProtocolValues = &comboWriter->d_proxyProtocolValues;
+ dnsQuestion.extendedErrorCode = &comboWriter->d_extendedErrorCode;
+ dnsQuestion.extendedErrorExtra = &comboWriter->d_extendedErrorExtra;
+ dnsQuestion.meta = std::move(comboWriter->d_meta);
+ dnsQuestion.fromAuthIP = &resolver.d_fromAuthIP;
+
+ resolver.d_slog = resolver.d_slog->withValues("qname", Logging::Loggable(comboWriter->d_mdp.d_qname),
+ "qtype", Logging::Loggable(QType(comboWriter->d_mdp.d_qtype)),
+ "remote", Logging::Loggable(comboWriter->getRemote()),
+ "proto", Logging::Loggable(comboWriter->d_tcp ? "tcp" : "udp"),
+ "ecs", Logging::Loggable(comboWriter->d_ednssubnet.source.empty() ? "" : comboWriter->d_ednssubnet.source.toString()),
+ "mtid", Logging::Loggable(g_multiTasker->getTid()));
+ RunningResolveGuard tcpGuard(comboWriter);
+
+ if (ednsExtRCode != 0 || comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) {
+ goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
+ }
+
+ if (comboWriter->d_mdp.d_qtype == QType::ANY && !comboWriter->d_tcp && g_anyToTcp) {
+ packetWriter.getHeader()->tc = 1;
res = 0;
variableAnswer = true;
- goto sendit;
+ goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
}
- if (t_traceRegex && t_traceRegex->match(dc->d_mdp.d_qname.toString())) {
- sr.setLogMode(SyncRes::Store);
+ if (t_traceRegex && t_traceRegex->match(comboWriter->d_mdp.d_qname.toString())) {
+ resolver.setLogMode(SyncRes::Store);
tracedQuery = true;
}
if (!g_quiet || tracedQuery) {
if (!g_slogStructured) {
- g_log << Logger::Warning << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] " << (dc->d_tcp ? "TCP " : "") << "question for '" << dc->d_mdp.d_qname << "|"
- << QType(dc->d_mdp.d_qtype) << "' from " << dc->getRemote();
- if (!dc->d_ednssubnet.source.empty()) {
- g_log << " (ecs " << dc->d_ednssubnet.source.toString() << ")";
+ g_log << Logger::Warning << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] " << (comboWriter->d_tcp ? "TCP " : "") << "question for '" << comboWriter->d_mdp.d_qname << "|"
+ << QType(comboWriter->d_mdp.d_qtype) << "' from " << comboWriter->getRemote();
+ if (!comboWriter->d_ednssubnet.source.empty()) {
+ g_log << " (ecs " << comboWriter->d_ednssubnet.source.toString() << ")";
}
g_log << endl;
}
else {
- sr.d_slog->info(Logr::Info, "Question");
+ resolver.d_slog->info(Logr::Info, "Question");
}
}
- if (!dc->d_mdp.d_header.rd) {
- sr.setCacheOnly();
+ if (!comboWriter->d_mdp.d_header.rd) {
+ if (g_allowNoRD) {
+ resolver.setCacheOnly();
+ }
+ else {
+ ret.clear();
+ res = RCode::Refused;
+ goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
+ }
}
- if (dc->d_luaContext) {
- dc->d_luaContext->prerpz(dq, res, sr.d_eventTrace);
+ if (comboWriter->d_luaContext) {
+ comboWriter->d_luaContext->prerpz(dnsQuestion, res, resolver.d_eventTrace);
}
// Check if the client has a policy attached to it
if (wantsRPZ && !appliedPolicy.wasHit()) {
- if (luaconfsLocal->dfe.getClientPolicy(dc->d_source, sr.d_discardedPolicies, appliedPolicy)) {
- mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
+ if (luaconfsLocal->dfe.getClientPolicy(comboWriter->d_source, resolver.d_discardedPolicies, appliedPolicy)) {
+ mergePolicyTags(comboWriter->d_policyTags, appliedPolicy.getTags());
}
}
/* If we already have an answer generated from gettag_ffi, let's see if the filtering policies
should be applied to it */
- if (dc->d_rcode != boost::none) {
+ if (comboWriter->d_rcode != boost::none) {
bool policyOverride = false;
/* Unless we already matched on the client IP, time to check the qname.
}
else {
// no match on the client IP, check the qname
- if (luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, sr.d_discardedPolicies, appliedPolicy)) {
+ if (luaconfsLocal->dfe.getQueryPolicy(comboWriter->d_mdp.d_qname, resolver.d_discardedPolicies, appliedPolicy)) {
// got a match
- mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
+ mergePolicyTags(comboWriter->d_policyTags, appliedPolicy.getTags());
}
}
if (!policyOverride) {
/* No RPZ or gettag overrides it anyway */
- ret = std::move(dc->d_records);
- res = *dc->d_rcode;
- if (res == RCode::NoError && dc->d_followCNAMERecords) {
- res = followCNAMERecords(ret, QType(dc->d_mdp.d_qtype), res);
+ ret = std::move(comboWriter->d_records);
+ res = *comboWriter->d_rcode;
+ if (res == RCode::NoError && comboWriter->d_followCNAMERecords) {
+ res = followCNAMERecords(ret, QType(comboWriter->d_mdp.d_qtype), res);
}
- goto haveAnswer;
+ goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
}
}
// if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
- if (!dc->d_luaContext || !dc->d_luaContext->preresolve(dq, res, sr.d_eventTrace)) {
+ if (!comboWriter->d_luaContext || !comboWriter->d_luaContext->preresolve(dnsQuestion, res, resolver.d_eventTrace)) {
- if (!g_dns64PrefixReverse.empty() && dq.qtype == QType::PTR && dq.qname.isPartOf(g_dns64PrefixReverse)) {
- res = getFakePTRRecords(dq.qname, ret);
- goto haveAnswer;
+ if (!g_dns64PrefixReverse.empty() && dnsQuestion.qtype == QType::PTR && dnsQuestion.qname.isPartOf(g_dns64PrefixReverse)) {
+ res = getFakePTRRecords(dnsQuestion.qname, ret);
+ goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
}
- sr.setWantsRPZ(wantsRPZ);
+ resolver.setWantsRPZ(wantsRPZ);
if (wantsRPZ && appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
- if (dc->d_luaContext && dc->d_luaContext->policyHitEventFilter(dc->d_source, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_tcp, appliedPolicy, dc->d_policyTags, sr.d_discardedPolicies)) {
+ if (comboWriter->d_luaContext && comboWriter->d_luaContext->policyHitEventFilter(comboWriter->d_source, comboWriter->d_mdp.d_qname, QType(comboWriter->d_mdp.d_qtype), comboWriter->d_tcp, appliedPolicy, comboWriter->d_policyTags, resolver.d_discardedPolicies)) {
/* reset to no match */
appliedPolicy = DNSFilterEngine::Policy();
}
else {
- auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+ auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
if (policyResult == PolicyResult::HaveAnswer) {
- if (g_dns64Prefix && dq.qtype == QType::AAAA && dns64Candidate(dc->d_mdp.d_qtype, res, ret)) {
- res = getFakeAAAARecords(dq.qname, *g_dns64Prefix, ret);
+ if (g_dns64Prefix && dnsQuestion.qtype == QType::AAAA && dns64Candidate(comboWriter->d_mdp.d_qtype, res, ret)) {
+ res = getFakeAAAARecords(dnsQuestion.qname, *g_dns64Prefix, ret);
shouldNotValidate = true;
}
- goto haveAnswer;
+ goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
}
else if (policyResult == PolicyResult::Drop) {
return;
// Query did not get handled for Client IP or QNAME Policy reasons, now actually go out to find an answer
try {
- sr.d_appliedPolicy = appliedPolicy;
- sr.d_policyTags = std::move(dc->d_policyTags);
+ resolver.d_appliedPolicy = appliedPolicy;
+ resolver.d_policyTags = std::move(comboWriter->d_policyTags);
- if (!dc->d_routingTag.empty()) {
- sr.d_routingTag = dc->d_routingTag;
+ if (!comboWriter->d_routingTag.empty()) {
+ resolver.d_routingTag = comboWriter->d_routingTag;
}
ret.clear(); // policy might have filled it with custom records but we decided not to use them
- res = sr.beginResolve(dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_mdp.d_qclass, ret);
- shouldNotValidate = sr.wasOutOfBand();
+ res = resolver.beginResolve(comboWriter->d_mdp.d_qname, QType(comboWriter->d_mdp.d_qtype), comboWriter->d_mdp.d_qclass, ret);
+ shouldNotValidate = resolver.wasOutOfBand();
}
catch (const ImmediateQueryDropException& e) {
// XXX We need to export a protobuf message (and do a NOD lookup) if requested!
t_Counters.at(rec::Counter::policyDrops)++;
- SLOG(g_log << Logger::Debug << "Dropping query because of a filtering policy " << makeLoginfo(dc) << endl,
- sr.d_slog->info(Logr::Debug, "Dropping query because of a filtering policy"));
+ SLOG(g_log << Logger::Debug << "Dropping query because of a filtering policy " << makeLoginfo(comboWriter) << endl,
+ resolver.d_slog->info(Logr::Debug, "Dropping query because of a filtering policy"));
return;
}
catch (const ImmediateServFailException& e) {
if (g_logCommonErrors) {
- SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of '" << dc->d_mdp.d_qname << "' because: " << e.reason << endl,
- sr.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve"));
+ SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of '" << comboWriter->d_mdp.d_qname << "' because: " << e.reason << endl,
+ resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve"));
+ }
+ res = RCode::ServFail;
+ }
+ catch (const pdns::validation::TooManySEC3IterationsException& e) {
+ if (g_logCommonErrors) {
+ SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl,
+ resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve", "dnsseclimithit", Logging::Loggable(true)));
}
res = RCode::ServFail;
}
catch (const SendTruncatedAnswerException& e) {
ret.clear();
+ resolver.d_appliedPolicy.addSOAtoRPZResult(ret);
res = RCode::NoError;
- pw.getHeader()->tc = 1;
+ packetWriter.getHeader()->tc = 1;
}
catch (const PolicyHitException& e) {
res = -2;
}
- dq.validationState = sr.getValidationState();
- appliedPolicy = sr.d_appliedPolicy;
- dc->d_policyTags = std::move(sr.d_policyTags);
+ dnsQuestion.validationState = resolver.getValidationState();
+ appliedPolicy = resolver.d_appliedPolicy;
+ comboWriter->d_policyTags = std::move(resolver.d_policyTags);
if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None && appliedPolicy.d_zoneData && appliedPolicy.d_zoneData->d_extendedErrorCode) {
- dc->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
- dc->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
+ comboWriter->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
+ comboWriter->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
}
// During lookup, an NSDNAME or NSIP trigger was hit in RPZ
if (appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction) {
throw PDNSException("NoAction policy returned while a NSDNAME or NSIP trigger was hit");
}
- auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+ auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
if (policyResult == PolicyResult::HaveAnswer) {
- goto haveAnswer;
+ goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
}
else if (policyResult == PolicyResult::Drop) {
return;
}
bool luaHookHandled = false;
- if (dc->d_luaContext) {
+ if (comboWriter->d_luaContext) {
PolicyResult policyResult = PolicyResult::NoAction;
- if (answerIsNOData(dc->d_mdp.d_qtype, res, ret)) {
- if (dc->d_luaContext->nodata(dq, res, sr.d_eventTrace)) {
+ if (SyncRes::answerIsNOData(comboWriter->d_mdp.d_qtype, res, ret)) {
+ if (comboWriter->d_luaContext->nodata(dnsQuestion, res, resolver.d_eventTrace)) {
luaHookHandled = true;
shouldNotValidate = true;
- policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+ policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
}
}
- else if (res == RCode::NXDomain && dc->d_luaContext->nxdomain(dq, res, sr.d_eventTrace)) {
+ else if (res == RCode::NXDomain && comboWriter->d_luaContext->nxdomain(dnsQuestion, res, resolver.d_eventTrace)) {
luaHookHandled = true;
shouldNotValidate = true;
- policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+ policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
}
if (policyResult == PolicyResult::HaveAnswer) {
- goto haveAnswer;
+ goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
}
else if (policyResult == PolicyResult::Drop) {
return;
}
} // dc->d_luaContext
- if (!luaHookHandled && g_dns64Prefix && dc->d_mdp.d_qtype == QType::AAAA && (shouldNotValidate || !sr.isDNSSECValidationRequested() || !vStateIsBogus(dq.validationState)) && dns64Candidate(dc->d_mdp.d_qtype, res, ret)) {
- res = getFakeAAAARecords(dq.qname, *g_dns64Prefix, ret);
+ if (!luaHookHandled && g_dns64Prefix && comboWriter->d_mdp.d_qtype == QType::AAAA && (shouldNotValidate || !resolver.isDNSSECValidationRequested() || !vStateIsBogus(dnsQuestion.validationState)) && dns64Candidate(comboWriter->d_mdp.d_qtype, res, ret)) {
+ res = getFakeAAAARecords(dnsQuestion.qname, *g_dns64Prefix, ret);
shouldNotValidate = true;
}
- if (dc->d_luaContext) {
+ if (comboWriter->d_luaContext) {
PolicyResult policyResult = PolicyResult::NoAction;
- if (dc->d_luaContext->d_postresolve_ffi) {
- RecursorLua4::PostResolveFFIHandle handle(dq);
- sr.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI);
- bool pr = dc->d_luaContext->postresolve_ffi(handle);
- sr.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI, pr, false);
- if (pr) {
+ if (comboWriter->d_luaContext->hasPostResolveFFIfunc()) {
+ RecursorLua4::PostResolveFFIHandle handle(dnsQuestion);
+ resolver.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI);
+ bool prResult = comboWriter->d_luaContext->postresolve_ffi(handle);
+ resolver.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI, prResult, false);
+ if (prResult) {
shouldNotValidate = true;
- policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+ policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
}
}
- else if (dc->d_luaContext->postresolve(dq, res, sr.d_eventTrace)) {
+ else if (comboWriter->d_luaContext->postresolve(dnsQuestion, res, resolver.d_eventTrace)) {
shouldNotValidate = true;
- policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+ policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
}
if (policyResult == PolicyResult::HaveAnswer) {
- goto haveAnswer;
+ goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
}
else if (policyResult == PolicyResult::Drop) {
return;
}
} // dc->d_luaContext
}
- else if (dc->d_luaContext) {
+ else if (comboWriter->d_luaContext) {
// preresolve returned true
shouldNotValidate = true;
- auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+ auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
// haveAnswer case redundant
if (policyResult == PolicyResult::Drop) {
return;
}
haveAnswer:;
- if (tracedQuery || res == -1 || res == RCode::ServFail || pw.getHeader()->rcode == RCode::ServFail) {
- dumpTrace(sr.getTrace(), sr.d_fixednow);
+ if (tracedQuery || res == -1 || res == RCode::ServFail || packetWriter.getHeader()->rcode == static_cast<unsigned>(RCode::ServFail)) {
+ dumpTrace(resolver.getTrace(), resolver.d_fixednow);
}
if (res == -1) {
- pw.getHeader()->rcode = RCode::ServFail;
+ packetWriter.getHeader()->rcode = RCode::ServFail;
// no commit here, because no record
++t_Counters.at(rec::Counter::servFails);
}
else {
- pw.getHeader()->rcode = res;
+ packetWriter.getHeader()->rcode = res;
// Does the validation mode or query demand validation?
- if (!shouldNotValidate && sr.isDNSSECValidationRequested()) {
+ if (!shouldNotValidate && resolver.isDNSSECValidationRequested()) {
try {
- auto state = sr.getValidationState();
+ auto state = resolver.getValidationState();
string x_marker;
std::shared_ptr<Logr::Logger> log;
- if (sr.doLog() || vStateIsBogus(state)) {
+ if (resolver.doLog() || vStateIsBogus(state)) {
// Only create logging object if needed below, beware if you change the logging logic!
- log = sr.d_slog->withValues("vstate", Logging::Loggable(state));
+ log = resolver.d_slog->withValues("vstate", Logging::Loggable(state));
+ if (resolver.getDNSSECLimitHit()) {
+ log = log->withValues("dnsseclimithit", Logging::Loggable(true));
+ }
auto xdnssec = g_xdnssec.getLocal();
- if (xdnssec->check(dc->d_mdp.d_qname)) {
+ if (xdnssec->check(comboWriter->d_mdp.d_qname)) {
log = log->withValues("in-x-dnssec-names", Logging::Loggable(1));
x_marker = " [in x-dnssec-names]";
}
}
if (state == vState::Secure) {
- if (sr.doLog()) {
- SLOG(g_log << Logger::Warning << "Answer to " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " for " << dc->getRemote() << " validates correctly" << endl,
+ if (resolver.doLog()) {
+ SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates correctly" << endl,
log->info(Logr::Info, "Validates Correctly"));
}
// Is the query source interested in the value of the ad-bit?
- if (dc->d_mdp.d_header.ad || DNSSECOK)
- pw.getHeader()->ad = 1;
+ if (comboWriter->d_mdp.d_header.ad || DNSSECOK) {
+ packetWriter.getHeader()->ad = 1;
+ }
}
else if (state == vState::Insecure) {
- if (sr.doLog()) {
- SLOG(g_log << Logger::Warning << "Answer to " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " for " << dc->getRemote() << " validates as Insecure" << endl,
+ if (resolver.doLog()) {
+ SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates as Insecure" << endl,
log->info(Logr::Info, "Validates as Insecure"));
}
- pw.getHeader()->ad = 0;
+ packetWriter.getHeader()->ad = 0;
}
else if (vStateIsBogus(state)) {
- if (t_bogusremotes)
- t_bogusremotes->push_back(dc->d_source);
- if (t_bogusqueryring)
- t_bogusqueryring->push_back({dc->d_mdp.d_qname, dc->d_mdp.d_qtype});
- if (g_dnssecLogBogus || sr.doLog() || g_dnssecmode == DNSSECMode::ValidateForLog) {
- SLOG(g_log << Logger::Warning << "Answer to " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " for " << dc->getRemote() << " validates as " << vStateToString(state) << endl,
+ if (t_bogusremotes) {
+ t_bogusremotes->push_back(comboWriter->d_source);
+ }
+ if (t_bogusqueryring) {
+ t_bogusqueryring->push_back({comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype});
+ }
+ if (g_dnssecLogBogus || resolver.doLog() || g_dnssecmode == DNSSECMode::ValidateForLog) {
+ SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates as " << vStateToString(state) << endl,
log->info(Logr::Notice, "Validates as Bogus"));
}
// Does the query or validation mode sending out a SERVFAIL on validation errors?
- if (!pw.getHeader()->cd && (g_dnssecmode == DNSSECMode::ValidateAll || dc->d_mdp.d_header.ad || DNSSECOK)) {
- if (sr.doLog()) {
- SLOG(g_log << Logger::Warning << "Sending out SERVFAIL for " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << " because recursor or query demands it for Bogus results" << endl,
+ if (!packetWriter.getHeader()->cd && (g_dnssecmode == DNSSECMode::ValidateAll || comboWriter->d_mdp.d_header.ad || DNSSECOK)) {
+ if (resolver.doLog()) {
+ SLOG(g_log << Logger::Warning << "Sending out SERVFAIL for " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << " because recursor or query demands it for Bogus results" << endl,
log->info(Logr::Notice, "Sending out SERVFAIL because recursor or query demands it for Bogus results"));
}
- pw.getHeader()->rcode = RCode::ServFail;
- goto sendit;
+ packetWriter.getHeader()->rcode = RCode::ServFail;
+ goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
}
else {
- if (sr.doLog()) {
- SLOG(g_log << Logger::Warning << "Not sending out SERVFAIL for " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " Bogus validation since neither config nor query demands this" << endl,
+ if (resolver.doLog()) {
+ SLOG(g_log << Logger::Warning << "Not sending out SERVFAIL for " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " Bogus validation since neither config nor query demands this" << endl,
log->info(Logr::Notice, "Sending out SERVFAIL because recursor or query demands it for Bogus results"));
}
}
}
}
catch (const ImmediateServFailException& e) {
- if (g_logCommonErrors)
- SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during validation of '" << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << "' because: " << e.reason << endl,
- sr.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during validation", "exception", Logging::Loggable("ImmediateServFailException")));
- goto sendit;
+ if (g_logCommonErrors) {
+ SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during validation of '" << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << "' because: " << e.reason << endl,
+ resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during validation", "exception", Logging::Loggable("ImmediateServFailException")));
+ }
+ goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
+ }
+ catch (const pdns::validation::TooManySEC3IterationsException& e) {
+ if (g_logCommonErrors || (g_dnssecLogBogus && resolver.getDNSSECLimitHit())) {
+ SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during validation of '" << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << "' because: " << e.what() << endl,
+ resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during validation", "exception", Logging::Loggable("TooManySEC3IterationsException"), "dnsseclimithit", Logging::Loggable(resolver.getDNSSECLimitHit())));
+ }
+ goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
}
}
- if (ret.size()) {
+ if (!ret.empty()) {
pdns::orderAndShuffle(ret, false);
- if (auto sl = luaconfsLocal->sortlist.getOrderCmp(dc->d_source)) {
- stable_sort(ret.begin(), ret.end(), *sl);
+ if (auto listToSort = luaconfsLocal->sortlist.getOrderCmp(comboWriter->d_source)) {
+ stable_sort(ret.begin(), ret.end(), *listToSort);
variableAnswer = true;
}
}
bool needCommit = false;
- for (auto i = ret.cbegin(); i != ret.cend(); ++i) {
- if (!DNSSECOK && (i->d_type == QType::NSEC3 || ((i->d_type == QType::RRSIG || i->d_type == QType::NSEC) && ((dc->d_mdp.d_qtype != i->d_type && dc->d_mdp.d_qtype != QType::ANY) || (i->d_place != DNSResourceRecord::ANSWER && i->d_place != DNSResourceRecord::ADDITIONAL))))) {
+ for (const auto& record : ret) {
+ if (!DNSSECOK && (record.d_type == QType::NSEC3 || ((record.d_type == QType::RRSIG || record.d_type == QType::NSEC) && ((comboWriter->d_mdp.d_qtype != record.d_type && comboWriter->d_mdp.d_qtype != QType::ANY) || (record.d_place != DNSResourceRecord::ANSWER && record.d_place != DNSResourceRecord::ADDITIONAL))))) {
continue;
}
- if (!addRecordToPacket(pw, *i, minTTL, dc->d_ttlCap, maxanswersize, seenAuthSOA)) {
+ if (!addRecordToPacket(packetWriter, record, minTTL, comboWriter->d_ttlCap, maxanswersize, seenAuthSOA)) {
needCommit = false;
break;
}
bool udr = false;
#ifdef NOD_ENABLED
if (g_udrEnabled) {
- udr = udrCheckUniqueDNSRecord(nodlogger, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, *i);
+ udr = udrCheckUniqueDNSRecord(nodlogger, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, record);
if (!hasUDR && udr) {
hasUDR = true;
}
// If a single answer causes a too big protobuf message, it wil be dropped by queueData()
// But note addRR has code to prevent that
if (pbMessage.size() < std::numeric_limits<uint16_t>::max() / 2) {
- pbMessage.addRR(*i, luaconfsLocal->protobufExportConfig.exportTypes, udr);
+ pbMessage.addRR(record, luaconfsLocal->protobufExportConfig.exportTypes, udr);
}
}
}
if (needCommit) {
- pw.commit();
+ packetWriter.commit();
}
#ifdef NOD_ENABLED
#ifdef HAVE_FSTRM
if (hasUDR) {
if (isEnabledForUDRs(t_nodFrameStreamServersInfo.servers)) {
- struct timespec ts;
+ struct timespec timeSpec
+ {
+ };
std::string str;
- if (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec) {
- TIMEVAL_TO_TIMESPEC(&(dc->d_kernelTimestamp), &ts);
+ if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+ TIMEVAL_TO_TIMESPEC(&comboWriter->d_kernelTimestamp, &timeSpec); // NOLINT
}
else {
- TIMEVAL_TO_TIMESPEC(&(dc->d_now), &ts);
+ TIMEVAL_TO_TIMESPEC(&comboWriter->d_now, &timeSpec); // NOLINT
}
- DnstapMessage message(str, DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &dc->d_source, &dc->d_destination, dc->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &ts, nullptr, dc->d_mdp.d_qname);
-
+ DnstapMessage message(std::move(str), DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &comboWriter->d_source, &comboWriter->d_destination, comboWriter->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &timeSpec, nullptr, comboWriter->d_mdp.d_qname); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ str = message.getBuffer();
for (auto& logger : *(t_nodFrameStreamServersInfo.servers)) {
if (logger->logUDRs()) {
remoteLoggerQueueData(*logger, str);
}
sendit:;
- if (g_useIncomingECS && dc->d_ecsFound && !sr.wasVariable() && !variableAnswer) {
- EDNSSubnetOpts eo;
- eo.source = dc->d_ednssubnet.source;
- ComboAddress sa;
- sa.reset();
- sa.sin4.sin_family = eo.source.getNetwork().sin4.sin_family;
- eo.scope = Netmask(sa, 0);
- auto ecsPayload = makeEDNSSubnetOptsString(eo);
+ if (g_useIncomingECS && comboWriter->d_ecsFound && !resolver.wasVariable() && !variableAnswer) {
+ EDNSSubnetOpts ednsOptions;
+ ednsOptions.source = comboWriter->d_ednssubnet.source;
+ ComboAddress sourceAddr;
+ sourceAddr.reset();
+ sourceAddr.sin4.sin_family = ednsOptions.source.getNetwork().sin4.sin_family;
+ ednsOptions.scope = Netmask(sourceAddr, 0);
+ auto ecsPayload = makeEDNSSubnetOptsString(ednsOptions);
// if we don't have enough space available let's just not set that scope of zero,
// it will prevent some caching, mostly from dnsdist, but that's fine
- if (pw.size() < maxanswersize && (maxanswersize - pw.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size())) {
+ if (packetWriter.size() < maxanswersize && (maxanswersize - packetWriter.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size())) {
maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size();
}
if (haveEDNS && addPaddingToResponse) {
- size_t currentSize = pw.getSizeWithOpts(returnedEdnsOptions);
+ size_t currentSize = packetWriter.getSizeWithOpts(returnedEdnsOptions);
/* we don't use maxawnswersize because it accounts for some EDNS options, but
not all of them (for example ECS) */
size_t maxSize = min(static_cast<uint16_t>(edo.d_packetsize >= 512 ? edo.d_packetsize : 512), g_udpTruncationThreshold);
}
if (haveEDNS) {
- auto state = sr.getValidationState();
- if (dc->d_extendedErrorCode || sr.d_extendedError || (SyncRes::s_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
- EDNSExtendedError::code code;
+ auto state = resolver.getValidationState();
+ if (comboWriter->d_extendedErrorCode || resolver.d_extendedError || (SyncRes::s_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
+ EDNSExtendedError::code code = EDNSExtendedError::code::Other;
std::string extra;
- if (dc->d_extendedErrorCode) {
- code = static_cast<EDNSExtendedError::code>(*dc->d_extendedErrorCode);
- extra = std::move(dc->d_extendedErrorExtra);
+ if (comboWriter->d_extendedErrorCode) {
+ code = static_cast<EDNSExtendedError::code>(*comboWriter->d_extendedErrorCode);
+ extra = std::move(comboWriter->d_extendedErrorExtra);
}
- else if (sr.d_extendedError) {
- code = static_cast<EDNSExtendedError::code>(sr.d_extendedError->infoCode);
- extra = std::move(sr.d_extendedError->extraText);
+ else if (resolver.d_extendedError) {
+ code = static_cast<EDNSExtendedError::code>(resolver.d_extendedError->infoCode);
+ extra = std::move(resolver.d_extendedError->extraText);
}
else {
switch (state) {
code = EDNSExtendedError::code::NoZoneKeyBitSet;
break;
case vState::BogusRevokedDNSKEY:
- code = EDNSExtendedError::code::DNSSECBogus;
- break;
case vState::BogusInvalidDNSKEYProtocol:
code = EDNSExtendedError::code::DNSSECBogus;
break;
eee.infoCode = static_cast<uint16_t>(code);
eee.extraText = std::move(extra);
- if (pw.size() < maxanswersize && (maxanswersize - pw.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + sizeof(eee.infoCode) + eee.extraText.size())) {
+ if (packetWriter.size() < maxanswersize && (maxanswersize - packetWriter.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + sizeof(eee.infoCode) + eee.extraText.size())) {
returnedEdnsOptions.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee));
}
}
OPT record. This MUST also occur when a truncated response (using
the DNS header's TC bit) is returned."
*/
- pw.addOpt(512, ednsExtRCode, DNSSECOK ? EDNSOpts::DNSSECOK : 0, returnedEdnsOptions);
- pw.commit();
+ packetWriter.addOpt(512, ednsExtRCode, DNSSECOK ? EDNSOpts::DNSSECOK : 0, returnedEdnsOptions);
+ packetWriter.commit();
}
- t_Counters.at(rec::ResponseStats::responseStats).submitResponse(dc->d_mdp.d_qtype, packet.size(), pw.getHeader()->rcode);
- updateResponseStats(res, dc->d_source, packet.size(), &dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+ t_Counters.at(rec::ResponseStats::responseStats).submitResponse(comboWriter->d_mdp.d_qtype, packet.size(), packetWriter.getHeader()->rcode);
+ updateResponseStats(res, comboWriter->d_source, packet.size(), &comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype);
#ifdef NOD_ENABLED
bool nod = false;
if (g_nodEnabled) {
- if (nodCheckNewDomain(nodlogger, dc->d_mdp.d_qname)) {
+ if (nodCheckNewDomain(nodlogger, comboWriter->d_mdp.d_qname)) {
nod = true;
#ifdef HAVE_FSTRM
if (isEnabledForNODs(t_nodFrameStreamServersInfo.servers)) {
- struct timespec ts;
+ struct timespec timeSpec
+ {
+ };
std::string str;
- if (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec) {
- TIMEVAL_TO_TIMESPEC(&(dc->d_kernelTimestamp), &ts);
+ if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+ TIMEVAL_TO_TIMESPEC(&comboWriter->d_kernelTimestamp, &timeSpec); // NOLINT
}
else {
- TIMEVAL_TO_TIMESPEC(&(dc->d_now), &ts);
+ TIMEVAL_TO_TIMESPEC(&comboWriter->d_now, &timeSpec); // NOLINT
}
- DnstapMessage message(str, DnstapMessage::MessageType::client_query, SyncRes::s_serverID, &dc->d_source, &dc->d_destination, dc->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, nullptr, 0, &ts, nullptr, dc->d_mdp.d_qname);
+ DnstapMessage message(std::move(str), DnstapMessage::MessageType::client_query, SyncRes::s_serverID, &comboWriter->d_source, &comboWriter->d_destination, comboWriter->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, nullptr, 0, &timeSpec, nullptr, comboWriter->d_mdp.d_qname);
+ str = message.getBuffer();
for (auto& logger : *(t_nodFrameStreamServersInfo.servers)) {
if (logger->logNODs()) {
}
#endif /* NOD_ENABLED */
- if (variableAnswer || sr.wasVariable()) {
+ if (variableAnswer || resolver.wasVariable()) {
t_Counters.at(rec::Counter::variableResponses)++;
}
- if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && dc->d_policyTags.empty())) {
+ if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && comboWriter->d_policyTags.empty())) {
// Start constructing embedded DNSResponse object
- pbMessage.setResponseCode(pw.getHeader()->rcode);
+ pbMessage.setResponseCode(packetWriter.getHeader()->rcode);
if (!appliedPolicy.getName().empty()) {
pbMessage.setAppliedPolicy(appliedPolicy.getName());
pbMessage.setAppliedPolicyType(appliedPolicy.d_type);
- pbMessage.setAppliedPolicyTrigger(appliedPolicy.d_trigger);
- pbMessage.setAppliedPolicyHit(appliedPolicy.d_hit);
+ pbMessage.setAppliedPolicyTrigger(appliedPolicy.getTrigger());
+ pbMessage.setAppliedPolicyHit(appliedPolicy.getHit());
pbMessage.setAppliedPolicyKind(appliedPolicy.d_kind);
}
- pbMessage.addPolicyTags(dc->d_policyTags);
pbMessage.setInBytes(packet.size());
- pbMessage.setValidationState(sr.getValidationState());
+ pbMessage.setValidationState(resolver.getValidationState());
+ // See if we want to store the policyTags into the PC
+ addPolicyTagsToPBMessageIfNeeded(*comboWriter, pbMessage);
// Take s snap of the current protobuf buffer state to store in the PC
pbDataForCache = boost::make_optional(RecursorPacketCache::PBData{
pbMessage.getMessageBuf(),
pbMessage.getResponseBuf(),
- !appliedPolicy.getName().empty() || !dc->d_policyTags.empty()});
+ !appliedPolicy.getName().empty() || !comboWriter->d_policyTags.empty()});
#ifdef NOD_ENABLED
// if (g_udrEnabled) ??
pbMessage.clearUDR(pbDataForCache->d_response);
#endif
}
- if (g_packetCache && !variableAnswer && !sr.wasVariable()) {
- 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,
- string((const char*)&*packet.begin(), packet.size()),
+ const bool intoPC = g_packetCache && !variableAnswer && !resolver.wasVariable();
+ if (intoPC) {
+ minTTL = capPacketCacheTTL(*packetWriter.getHeader(), minTTL, seenAuthSOA);
+ g_packetCache->insertResponsePacket(comboWriter->d_tag, comboWriter->d_qhash, std::move(comboWriter->d_query), comboWriter->d_mdp.d_qname,
+ comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass,
+ string(reinterpret_cast<const char*>(&*packet.begin()), packet.size()), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
g_now.tv_sec,
minTTL,
- dq.validationState,
- std::move(pbDataForCache), dc->d_tcp);
+ dnsQuestion.validationState,
+ std::move(pbDataForCache), comboWriter->d_tcp);
}
if (g_regressionTestMode) {
t_Counters.updateSnap(g_regressionTestMode);
}
- if (!dc->d_tcp) {
- struct msghdr msgh;
- struct iovec iov;
- cmsgbuf_aligned cbuf;
- fillMSGHdr(&msgh, &iov, &cbuf, 0, (char*)&*packet.begin(), packet.size(), &dc->d_remote);
- msgh.msg_control = NULL;
-
- if (g_fromtosockets.count(dc->d_socket)) {
- addCMsgSrcAddr(&msgh, &cbuf, &dc->d_local, 0);
- }
- int sendErr = sendOnNBSocket(dc->d_socket, &msgh);
- if (sendErr && g_logCommonErrors) {
- SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << dc->getRemote() << " failed with: "
- << strerror(sendErr) << endl,
+ if (!comboWriter->d_tcp) {
+ struct msghdr msgh
+ {
+ };
+ struct iovec iov
+ {
+ };
+ cmsgbuf_aligned cbuf{};
+ fillMSGHdr(&msgh, &iov, &cbuf, 0, reinterpret_cast<char*>(&*packet.begin()), packet.size(), &comboWriter->d_remote); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ msgh.msg_control = nullptr;
+
+ if (g_fromtosockets.count(comboWriter->d_socket) > 0) {
+ addCMsgSrcAddr(&msgh, &cbuf, &comboWriter->d_local, 0);
+ }
+ int sendErr = sendOnNBSocket(comboWriter->d_socket, &msgh);
+ if (sendErr != 0 && g_logCommonErrors) {
+ SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << comboWriter->getRemote() << " failed with: "
+ << stringerror(sendErr) << endl,
g_slogudpin->error(Logr::Warning, sendErr, "Sending UDP reply to client failed"));
}
}
else {
- bool hadError = sendResponseOverTCP(dc, packet);
- finishTCPReply(dc, hadError, true);
+ bool hadError = sendResponseOverTCP(comboWriter, packet);
+ finishTCPReply(comboWriter, hadError, true);
tcpGuard.setHandled();
}
- sr.d_eventTrace.add(RecEventTrace::AnswerSent);
+ resolver.d_eventTrace.add(RecEventTrace::AnswerSent);
// Now do the per query changing part ot the protobuf message
- if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && dc->d_policyTags.empty())) {
+ if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && comboWriter->d_policyTags.empty())) {
// Below are the fields that are not stored in the packet cache and will be appended here and on a cache hit
- if (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec) {
- pbMessage.setQueryTime(dc->d_kernelTimestamp.tv_sec, dc->d_kernelTimestamp.tv_usec);
+ if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+ pbMessage.setQueryTime(comboWriter->d_kernelTimestamp.tv_sec, comboWriter->d_kernelTimestamp.tv_usec);
}
else {
- pbMessage.setQueryTime(dc->d_now.tv_sec, dc->d_now.tv_usec);
+ pbMessage.setQueryTime(comboWriter->d_now.tv_sec, comboWriter->d_now.tv_usec);
}
- pbMessage.setMessageIdentity(dc->d_uuid);
- pbMessage.setSocketProtocol(dc->d_tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
+ pbMessage.setMessageIdentity(comboWriter->d_uuid);
+ pbMessage.setSocketProtocol(comboWriter->d_tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
- pbMessage.setSocketFamily(dc->d_source.sin4.sin_family);
- Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ pbMessage.setSocketFamily(comboWriter->d_source.sin4.sin_family);
+ Netmask requestorNM(comboWriter->d_source, comboWriter->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
ComboAddress requestor = requestorNM.getMaskedNetwork();
pbMessage.setFrom(requestor);
- pbMessage.setFromPort(dc->d_source.getPort());
+ pbMessage.setFromPort(comboWriter->d_source.getPort());
}
else {
- pbMessage.setSocketFamily(dc->d_mappedSource.sin4.sin_family);
- Netmask requestorNM(dc->d_mappedSource, dc->d_mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ pbMessage.setSocketFamily(comboWriter->d_mappedSource.sin4.sin_family);
+ Netmask requestorNM(comboWriter->d_mappedSource, comboWriter->d_mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
ComboAddress requestor = requestorNM.getMaskedNetwork();
pbMessage.setFrom(requestor);
- pbMessage.setFromPort(dc->d_mappedSource.getPort());
+ pbMessage.setFromPort(comboWriter->d_mappedSource.getPort());
}
- pbMessage.setTo(dc->d_destination);
- pbMessage.setId(dc->d_mdp.d_header.id);
+ pbMessage.setTo(comboWriter->d_destination);
+ pbMessage.setId(comboWriter->d_mdp.d_header.id);
pbMessage.setTime();
- pbMessage.setEDNSSubnet(dc->d_ednssubnet.source, dc->d_ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- pbMessage.setRequestorId(dq.requestorId);
- pbMessage.setDeviceId(dq.deviceId);
- pbMessage.setDeviceName(dq.deviceName);
- pbMessage.setToPort(dc->d_destination.getPort());
+ pbMessage.setEDNSSubnet(comboWriter->d_ednssubnet.source, comboWriter->d_ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ pbMessage.setRequestorId(dnsQuestion.requestorId);
+ pbMessage.setDeviceId(dnsQuestion.deviceId);
+ pbMessage.setDeviceName(dnsQuestion.deviceName);
+ pbMessage.setToPort(comboWriter->d_destination.getPort());
+ pbMessage.addPolicyTags(comboWriter->d_gettagPolicyTags);
- for (const auto& m : dq.meta) {
- pbMessage.setMeta(m.first, m.second.stringVal, m.second.intVal);
+ for (const auto& metaValue : dnsQuestion.meta) {
+ pbMessage.setMeta(metaValue.first, metaValue.second.stringVal, metaValue.second.intVal);
}
#ifdef NOD_ENABLED
if (g_nodEnabled) {
}
}
#endif /* NOD_ENABLED */
- if (sr.d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) {
- pbMessage.addEvents(sr.d_eventTrace);
+ if (resolver.d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) != 0) {
+ pbMessage.addEvents(resolver.d_eventTrace);
}
- if (dc->d_logResponse) {
+ if (comboWriter->d_logResponse) {
protobufLogResponse(pbMessage);
}
}
- if (sr.d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
- SLOG(g_log << Logger::Info << sr.d_eventTrace.toString() << endl,
- sr.d_slog->info(Logr::Info, sr.d_eventTrace.toString())); // Maybe we want it to be more fancy?
+ if (resolver.d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) != 0) {
+ SLOG(g_log << Logger::Info << resolver.d_eventTrace.toString() << endl,
+ resolver.d_slog->info(Logr::Info, resolver.d_eventTrace.toString())); // Maybe we want it to be more fancy?
}
// Originally this code used a mix of floats, doubles, uint64_t with different units.
// Now it always uses an integral number of microseconds, except for averages, which use doubles
- uint64_t spentUsec = uSec(sr.getNow() - dc->d_now);
+ uint64_t spentUsec = uSec(resolver.getNow() - comboWriter->d_now);
if (!g_quiet) {
if (!g_slogStructured) {
- g_log << Logger::Error << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] answer to " << (dc->d_mdp.d_header.rd ? "" : "non-rd ") << "question '" << dc->d_mdp.d_qname << "|" << DNSRecordContent::NumberToType(dc->d_mdp.d_qtype);
- g_log << "': " << ntohs(pw.getHeader()->ancount) << " answers, " << ntohs(pw.getHeader()->arcount) << " additional, took " << sr.d_outqueries << " packets, " << sr.d_totUsec / 1000.0 << " netw ms, " << spentUsec / 1000.0 << " tot ms, " << sr.d_throttledqueries << " throttled, " << sr.d_timeouts << " timeouts, " << sr.d_tcpoutqueries << "/" << sr.d_dotoutqueries << " tcp/dot connections, rcode=" << res;
+ g_log << Logger::Error << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] answer to " << (comboWriter->d_mdp.d_header.rd ? "" : "non-rd ") << "question '" << comboWriter->d_mdp.d_qname << "|" << DNSRecordContent::NumberToType(comboWriter->d_mdp.d_qtype);
+ g_log << "': " << ntohs(packetWriter.getHeader()->ancount) << " answers, " << ntohs(packetWriter.getHeader()->arcount) << " additional, took " << resolver.d_outqueries << " packets, " << resolver.d_totUsec / 1000.0 << " netw ms, " << static_cast<double>(spentUsec) / 1000.0 << " tot ms, " << resolver.d_throttledqueries << " throttled, " << resolver.d_timeouts << " timeouts, " << resolver.d_tcpoutqueries << "/" << resolver.d_dotoutqueries << " tcp/dot connections, rcode=" << res;
- if (!shouldNotValidate && sr.isDNSSECValidationRequested()) {
- g_log << ", dnssec=" << sr.getValidationState();
+ if (!shouldNotValidate && resolver.isDNSSECValidationRequested()) {
+ g_log << ", dnssec=" << resolver.getValidationState();
}
+ g_log << " answer-is-variable=" << resolver.wasVariable() << ", into-packetcache=" << intoPC;
+ g_log << " maxdepth=" << resolver.d_maxdepth;
g_log << endl;
}
else {
- sr.d_slog->info(Logr::Info, "Answer", "rd", Logging::Loggable(dc->d_mdp.d_header.rd),
- "answers", Logging::Loggable(ntohs(pw.getHeader()->ancount)),
- "additional", Logging::Loggable(ntohs(pw.getHeader()->arcount)),
- "outqueries", Logging::Loggable(sr.d_outqueries),
- "netms", Logging::Loggable(sr.d_totUsec / 1000.0),
- "totms", Logging::Loggable(spentUsec / 1000.0),
- "throttled", Logging::Loggable(sr.d_throttledqueries),
- "timeouts", Logging::Loggable(sr.d_timeouts),
- "tcpout", Logging::Loggable(sr.d_tcpoutqueries),
- "dotout", Logging::Loggable(sr.d_dotoutqueries),
- "rcode", Logging::Loggable(res),
- "validationState", Logging::Loggable(sr.getValidationState()));
- }
- }
-
- if (dc->d_mdp.d_header.opcode == Opcode::Query) {
- if (sr.d_outqueries || sr.d_authzonequeries) {
- g_recCache->cacheMisses++;
+ resolver.d_slog->info(Logr::Info, "Answer", "rd", Logging::Loggable(comboWriter->d_mdp.d_header.rd),
+ "answers", Logging::Loggable(ntohs(packetWriter.getHeader()->ancount)),
+ "additional", Logging::Loggable(ntohs(packetWriter.getHeader()->arcount)),
+ "outqueries", Logging::Loggable(resolver.d_outqueries),
+ "netms", Logging::Loggable(resolver.d_totUsec / 1000.0),
+ "totms", Logging::Loggable(static_cast<double>(spentUsec) / 1000.0),
+ "throttled", Logging::Loggable(resolver.d_throttledqueries),
+ "timeouts", Logging::Loggable(resolver.d_timeouts),
+ "tcpout", Logging::Loggable(resolver.d_tcpoutqueries),
+ "dotout", Logging::Loggable(resolver.d_dotoutqueries),
+ "rcode", Logging::Loggable(res),
+ "validationState", Logging::Loggable(resolver.getValidationState()),
+ "answer-is-variable", Logging::Loggable(resolver.wasVariable()),
+ "into-packetcache", Logging::Loggable(intoPC),
+ "maxdepth", Logging::Loggable(resolver.d_maxdepth));
+ }
+ }
+
+ if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Query)) {
+ if (resolver.d_outqueries != 0 || resolver.d_throttledqueries != 0 || resolver.d_authzonequeries != 0) {
+ g_recCache->incCacheMisses();
}
else {
- g_recCache->cacheHits++;
+ g_recCache->incCacheHits();
}
}
t_Counters.at(rec::Histogram::answers)(spentUsec);
t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
- double newLat = spentUsec;
+ auto newLat = static_cast<double>(spentUsec);
newLat = min(newLat, g_networkTimeoutMsec * 1000.0); // outliers of several minutes exist..
t_Counters.at(rec::DoubleWAvgCounter::avgLatencyUsec).addToRollingAvg(newLat, g_latencyStatSize);
// no worries, we do this for packet cache hits elsewhere
- if (spentUsec >= sr.d_totUsec) {
- uint64_t ourtime = spentUsec - sr.d_totUsec;
+ if (spentUsec >= resolver.d_totUsec) {
+ uint64_t ourtime = spentUsec - resolver.d_totUsec;
t_Counters.at(rec::Histogram::ourtime)(ourtime);
- newLat = ourtime; // usec
+ newLat = static_cast<double>(ourtime); // usec
t_Counters.at(rec::DoubleWAvgCounter::avgLatencyOursUsec).addToRollingAvg(newLat, g_latencyStatSize);
}
#ifdef NOD_ENABLED
if (nod) {
- sendNODLookup(nodlogger, dc->d_mdp.d_qname);
+ sendNODLookup(nodlogger, comboWriter->d_mdp.d_qname);
}
#endif /* NOD_ENABLED */
// cout<<dc->d_mdp.d_qname<<"\t"<<MT->getUsec()<<"\t"<<sr.d_outqueries<<endl;
}
catch (const PDNSException& ae) {
- SLOG(g_log << Logger::Error << "startDoResolve problem " << makeLoginfo(dc) << ": " << ae.reason << endl,
- sr.d_slog->error(Logr::Error, ae.reason, "startDoResolve problem", "exception", Logging::Loggable("PDNSException")));
+ SLOG(g_log << Logger::Error << "startDoResolve problem " << makeLoginfo(comboWriter) << ": " << ae.reason << endl,
+ resolver.d_slog->error(Logr::Error, ae.reason, "startDoResolve problem", "exception", Logging::Loggable("PDNSException")));
}
catch (const MOADNSException& mde) {
- SLOG(g_log << Logger::Error << "DNS parser error " << makeLoginfo(dc) << ": " << dc->d_mdp.d_qname << ", " << mde.what() << endl,
- sr.d_slog->error(Logr::Error, mde.what(), "DNS parser error"));
+ SLOG(g_log << Logger::Error << "DNS parser error " << makeLoginfo(comboWriter) << ": " << comboWriter->d_mdp.d_qname << ", " << mde.what() << endl,
+ resolver.d_slog->error(Logr::Error, mde.what(), "DNS parser error"));
}
catch (const std::exception& e) {
- SLOG(g_log << Logger::Error << "STL error " << makeLoginfo(dc) << ": " << e.what(),
- sr.d_slog->error(Logr::Error, e.what(), "Exception in resolver context ", "exception", Logging::Loggable("std::exception")));
+ SLOG(g_log << Logger::Error << "STL error " << makeLoginfo(comboWriter) << ": " << e.what(),
+ resolver.d_slog->error(Logr::Error, e.what(), "Exception in resolver context", "exception", Logging::Loggable("std::exception")));
// Luawrapper nests the exception from Lua, so we unnest it here
try {
}
catch (const std::exception& ne) {
SLOG(g_log << ". Extra info: " << ne.what(),
- sr.d_slog->error(Logr::Error, ne.what(), "Nested exception in resolver context", Logging::Loggable("std::exception")));
+ resolver.d_slog->error(Logr::Error, ne.what(), "Nested exception in resolver context", Logging::Loggable("std::exception")));
}
catch (...) {
}
}
}
catch (...) {
- SLOG(g_log << Logger::Error << "Any other exception in a resolver context " << makeLoginfo(dc) << endl,
- sr.d_slog->info(Logr::Error, "Any other exception in a resolver context"));
+ SLOG(g_log << Logger::Error << "Any other exception in a resolver context " << makeLoginfo(comboWriter) << endl,
+ resolver.d_slog->info(Logr::Error, "Any other exception in a resolver context"));
}
runTaskOnce(g_logCommonErrors);
static const size_t stackSizeThreshold = 9 * ::arg().asNum("stack-size") / 10;
- if (MT->getMaxStackUsage() >= stackSizeThreshold) {
- SLOG(g_log << Logger::Error << "Reached mthread stack usage of 90%: " << MT->getMaxStackUsage() << " " << makeLoginfo(dc) << " after " << sr.d_outqueries << " out queries, " << sr.d_tcpoutqueries << " TCP out queries, " << sr.d_dotoutqueries << " DoT out queries" << endl,
- sr.d_slog->info(Logr::Error, "Reached mthread stack usage of 90%",
- "stackUsage", Logging::Loggable(MT->getMaxStackUsage()),
- "outqueries", Logging::Loggable(sr.d_outqueries),
- "netms", Logging::Loggable(sr.d_totUsec / 1000.0),
- "throttled", Logging::Loggable(sr.d_throttledqueries),
- "timeouts", Logging::Loggable(sr.d_timeouts),
- "tcpout", Logging::Loggable(sr.d_tcpoutqueries),
- "dotout", Logging::Loggable(sr.d_dotoutqueries),
- "validationState", Logging::Loggable(sr.getValidationState())));
- }
- t_Counters.at(rec::Counter::maxMThreadStackUsage) = max(MT->getMaxStackUsage(), t_Counters.at(rec::Counter::maxMThreadStackUsage));
+ if (g_multiTasker->getMaxStackUsage() >= stackSizeThreshold) {
+ SLOG(g_log << Logger::Error << "Reached mthread stack usage of 90%: " << g_multiTasker->getMaxStackUsage() << " " << makeLoginfo(comboWriter) << " after " << resolver.d_outqueries << " out queries, " << resolver.d_tcpoutqueries << " TCP out queries, " << resolver.d_dotoutqueries << " DoT out queries" << endl,
+ resolver.d_slog->info(Logr::Error, "Reached mthread stack usage of 90%",
+ "stackUsage", Logging::Loggable(g_multiTasker->getMaxStackUsage()),
+ "outqueries", Logging::Loggable(resolver.d_outqueries),
+ "netms", Logging::Loggable(resolver.d_totUsec / 1000.0),
+ "throttled", Logging::Loggable(resolver.d_throttledqueries),
+ "timeouts", Logging::Loggable(resolver.d_timeouts),
+ "tcpout", Logging::Loggable(resolver.d_tcpoutqueries),
+ "dotout", Logging::Loggable(resolver.d_dotoutqueries),
+ "validationState", Logging::Loggable(resolver.getValidationState())));
+ }
+ t_Counters.at(rec::Counter::maxMThreadStackUsage) = max(g_multiTasker->getMaxStackUsage(), t_Counters.at(rec::Counter::maxMThreadStackUsage));
t_Counters.updateSnap(g_regressionTestMode);
}
{
const bool lookForECS = ednssubnet != nullptr;
const dnsheader_aligned dnshead(question.data());
- const dnsheader* dh = dnshead.get();
+ const dnsheader* dhPointer = dnshead.get();
size_t questionLen = question.length();
unsigned int consumed = 0;
- *dnsname = DNSName(question.c_str(), questionLen, sizeof(dnsheader), false, qtype, qclass, &consumed);
+ *dnsname = DNSName(question.c_str(), static_cast<int>(questionLen), sizeof(dnsheader), false, qtype, qclass, &consumed);
size_t pos = sizeof(dnsheader) + consumed + 4;
const size_t headerSize = /* root */ 1 + sizeof(dnsrecordheader);
- const uint16_t arcount = ntohs(dh->arcount);
+ const uint16_t arcount = ntohs(dhPointer->arcount);
for (uint16_t arpos = 0; arpos < arcount && questionLen > (pos + headerSize) && (lookForECS && !foundECS); arpos++) {
if (question.at(pos) != 0) {
}
pos += 1;
- const dnsrecordheader* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos));
+ const auto* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
pos += sizeof(dnsrecordheader);
if (pos >= questionLen) {
/* OPT root label (1) followed by type (2) */
if (lookForECS && ntohs(drh->d_type) == QType::OPT) {
- if (!options) {
+ if (options == nullptr) {
size_t ecsStartPosition = 0;
size_t ecsLen = 0;
/* we need to pass the record len */
- int res = getEDNSOption(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + sizeof(drh->d_clen), EDNSOptionCode::ECS, &ecsStartPosition, &ecsLen);
+ int res = getEDNSOption(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + sizeof(drh->d_clen), EDNSOptionCode::ECS, &ecsStartPosition, &ecsLen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
if (res == 0 && ecsLen > 4) {
EDNSSubnetOpts eso;
if (getEDNSSubnetOptsFromString(&question.at(pos - sizeof(drh->d_clen) + ecsStartPosition + 4), ecsLen - 4, &eso)) {
}
else {
/* we need to pass the record len */
- int res = getEDNSOptions(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + (sizeof(drh->d_clen)), *options);
+ int res = getEDNSOptions(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + (sizeof(drh->d_clen)), *options); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
if (res == 0) {
- const auto& it = options->find(EDNSOptionCode::ECS);
- if (it != options->end() && !it->second.values.empty() && it->second.values.at(0).content != nullptr && it->second.values.at(0).size > 0) {
+ const auto& iter = options->find(EDNSOptionCode::ECS);
+ if (iter != options->end() && !iter->second.values.empty() && iter->second.values.at(0).content != nullptr && iter->second.values.at(0).size > 0) {
EDNSSubnetOpts eso;
- if (getEDNSSubnetOptsFromString(it->second.values.at(0).content, it->second.values.at(0).size, &eso)) {
+ if (getEDNSSubnetOptsFromString(iter->second.values.at(0).content, iter->second.values.at(0).size, &eso)) {
*ednssubnet = eso;
foundECS = true;
}
return false;
}
bool cacheHit = false;
- uint32_t age;
- vState valState;
+ uint32_t age = 0;
+ vState valState = vState::Indeterminate;
if (qnameParsed) {
cacheHit = g_packetCache->getResponsePacket(tag, data, qname, qtype, qclass, now.tv_sec, &response, &age, &valState, &qhash, &pbData, tcp);
{
// send a message to the handler thread asking it
// to wipe all of the caches
- ThreadMSG* tmsg = new ThreadMSG();
+ ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: pointer owner
tmsg->func = [=] { return pleaseWipeCaches(canon, true, 0xffff); };
tmsg->wantAnswer = false;
- if (write(RecThreadInfo::info(0).pipes.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
- delete tmsg;
+ if (write(RecThreadInfo::info(0).getPipes().writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // NOLINT: correct sizeof
+ delete tmsg; // NOLINT: pointer owner
unixDie("write to thread pipe returned wrong size or error");
}
// source: the address we assume the query is coming from, might be set by proxy protocol
// destination: the address we assume the query was sent to, might be set by proxy protocol
// mappedSource: the address we assume the query is coming from. Differs from source if table based mapping has been applied
-static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, const ComboAddress& mappedSource, struct timeval tv, int fd, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace)
+static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, const ComboAddress& mappedSource, struct timeval tval, int fileDesc, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
{
- ++(RecThreadInfo::self().numberOfDistributedQueries);
+ RecThreadInfo::self().incNumberOfDistributedQueries();
gettimeofday(&g_now, nullptr);
- if (tv.tv_sec) {
- struct timeval diff = g_now - tv;
- double delta = (diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
+ if (tval.tv_sec != 0) {
+ struct timeval diff = g_now - tval;
+ double delta = (static_cast<double>(diff.tv_sec) * 1000 + static_cast<double>(diff.tv_usec) / 1000.0);
if (delta > 1000.0) {
t_Counters.at(rec::Counter::tooOldDrops)++;
++t_Counters.at(rec::Counter::qcounter);
- if (fromaddr.sin4.sin_family == AF_INET6)
+ if (fromaddr.sin4.sin_family == AF_INET6) {
t_Counters.at(rec::Counter::ipv6qcounter)++;
+ }
string response;
const dnsheader_aligned headerdata(question.data());
- const dnsheader* dh = headerdata.get();
+ const dnsheader* dnsheader = headerdata.get();
unsigned int ctag = 0;
uint32_t qhash = 0;
bool needECS = false;
string routingTag;
bool logQuery = false;
bool logResponse = false;
- boost::uuids::uuid uniqueId;
+ boost::uuids::uuid uniqueId{};
auto luaconfsLocal = g_luaconfs.getLocal();
const auto pbExport = checkProtobufExport(luaconfsLocal);
const auto outgoingbExport = checkOutgoingProtobufExport(luaconfsLocal);
#endif
// We do not have a SyncRes specific Lua context at this point yet, so ok to use t_pdl
- if (needECS || (t_pdl && (t_pdl->d_gettag || t_pdl->d_gettag_ffi)) || dh->opcode == Opcode::Notify) {
+ if (needECS || (t_pdl && (t_pdl->hasGettagFunc() || t_pdl->hasGettagFFIFunc())) || dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
try {
EDNSOptionViewMap ednsOptions;
if (t_pdl) {
try {
- if (t_pdl->d_gettag_ffi) {
+ if (t_pdl->hasGettagFFIFunc()) {
RecursorLua4::FFIParams params(qname, qtype, destination, source, ednssubnet.source, data, policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, false, logQuery, logResponse, followCNAMEs, extendedErrorCode, extendedErrorExtra, responsePaddingDisabled, meta);
eventTrace.add(RecEventTrace::LuaGetTagFFI);
ctag = t_pdl->gettag_ffi(params);
eventTrace.add(RecEventTrace::LuaGetTagFFI, ctag, false);
}
- else if (t_pdl->d_gettag) {
+ else if (t_pdl->hasGettagFunc()) {
eventTrace.add(RecEventTrace::LuaGetTag);
ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName, routingTag, proxyProtocolValues);
eventTrace.add(RecEventTrace::LuaGetTag, ctag, false);
RecursorPacketCache::OptPBData pbData{boost::none};
if (t_protobufServers.servers) {
if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && policyTags.empty())) {
- protobufLogQuery(luaconfsLocal, uniqueId, source, destination, mappedSource, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta);
+ protobufLogQuery(luaconfsLocal, uniqueId, source, destination, mappedSource, ednssubnet.source, false, dnsheader->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta);
}
}
ctag = g_paddingTag;
}
- if (dh->opcode == Opcode::Query) {
+ if (dnsheader->opcode == static_cast<unsigned>(Opcode::Query)) {
/* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable,
but it means that the hash would not be computed. If some script decides at a later time to mark back the answer
as cacheable we would cache it with a wrong tag, so better safe than sorry. */
"qname", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype)),
"source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
}
- struct msghdr msgh;
- struct iovec iov;
- cmsgbuf_aligned cbuf;
- fillMSGHdr(&msgh, &iov, &cbuf, 0, (char*)response.c_str(), response.length(), const_cast<ComboAddress*>(&fromaddr));
- msgh.msg_control = NULL;
-
- if (g_fromtosockets.count(fd)) {
+ struct msghdr msgh
+ {
+ };
+ struct iovec iov
+ {
+ };
+ cmsgbuf_aligned cbuf{};
+ fillMSGHdr(&msgh, &iov, &cbuf, 0, reinterpret_cast<char*>(response.data()), response.length(), const_cast<ComboAddress*>(&fromaddr)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
+ msgh.msg_control = nullptr;
+
+ if (g_fromtosockets.count(fileDesc) != 0) {
addCMsgSrcAddr(&msgh, &cbuf, &destaddr, 0);
}
- int sendErr = sendOnNBSocket(fd, &msgh);
+ int sendErr = sendOnNBSocket(fileDesc, &msgh);
eventTrace.add(RecEventTrace::AnswerSent);
- if (t_protobufServers.servers && logResponse && !(luaconfsLocal->protobufExportConfig.taggedOnly && pbData && !pbData->d_tagged)) {
- protobufLogResponse(dh, luaconfsLocal, pbData, tv, false, source, destination, mappedSource, ednssubnet, uniqueId, requestorId, deviceId, deviceName, meta, eventTrace);
+ if (t_protobufServers.servers && logResponse && (!luaconfsLocal->protobufExportConfig.taggedOnly || !pbData || pbData->d_tagged)) {
+ protobufLogResponse(dnsheader, luaconfsLocal, pbData, tval, false, source, destination, mappedSource, ednssubnet, uniqueId, requestorId, deviceId, deviceName, meta, eventTrace, policyTags);
}
- if (eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
+ if (eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) != 0) {
SLOG(g_log << Logger::Info << eventTrace.toString() << endl,
g_slogudpin->info(Logr::Info, eventTrace.toString())); // Do we want more fancy logging here?
}
- if (sendErr && g_logCommonErrors) {
+ if (sendErr != 0 && g_logCommonErrors) {
SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << source.toStringWithPort()
<< (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " failed with: "
- << strerror(sendErr) << endl,
+ << stringerror(sendErr) << endl,
g_slogudpin->error(Logr::Error, sendErr, "Sending UDP reply to client failed", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
}
- struct timeval now;
+ struct timeval now
+ {
+ };
Utility::gettimeofday(&now, nullptr);
- uint64_t spentUsec = uSec(now - tv);
+ uint64_t spentUsec = uSec(now - tval);
t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
t_Counters.updateSnap(g_regressionTestMode);
- return 0;
+ return nullptr;
}
}
}
SLOG(g_log << Logger::Error << "Error processing or aging answer packet: " << e.what() << endl,
g_slogudpin->error(Logr::Error, e.what(), "Error processing or aging answer packet", "exception", Logging::Loggable("std::exception")));
}
- return 0;
+ return nullptr;
}
if (t_pdl) {
- bool ipf = t_pdl->ipfilter(source, destination, *dh, eventTrace);
+ bool ipf = t_pdl->ipfilter(source, destination, *dnsheader, eventTrace);
if (ipf) {
if (!g_quiet) {
- SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " based on policy" << endl,
+ SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " based on policy" << endl,
g_slogudpin->info(Logr::Notice, "Dropped question based on policy", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
}
t_Counters.at(rec::Counter::policyDrops)++;
- return 0;
+ return nullptr;
}
}
- if (dh->opcode == Opcode::Notify) {
+ if (dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
if (!isAllowNotifyForZone(qname)) {
if (!g_quiet) {
- SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP NOTIFY from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl,
+ SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP NOTIFY from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl,
g_slogudpin->info(Logr::Notice, "Dropping UDP NOTIFY, zone not matched by allow-notify-for", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
}
t_Counters.at(rec::Counter::zoneDisallowedNotify)++;
- return 0;
+ return nullptr;
}
if (!g_quiet) {
SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl,
g_slogudpin->info(Logr::Notice, "Got NOTIFY", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr), "qname", Logging::Loggable(qname)));
}
-
- requestWipeCaches(qname);
+ if (!notifyRPZTracker(qname)) {
+ // It wasn't an RPZ
+ requestWipeCaches(qname);
+ }
// the operation will now be treated as a Query, generating
// a normal response, as the rest of the code does not
variable = true;
}
- if (MT->numProcesses() > g_maxMThreads) {
- if (!g_quiet)
- SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", over capacity" << endl,
+ if (g_multiTasker->numProcesses() > g_maxMThreads) {
+ if (!g_quiet) {
+ SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", over capacity" << endl,
g_slogudpin->info(Logr::Notice, "Dropped question, over capacity", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
-
+ }
t_Counters.at(rec::Counter::overCapacityDrops)++;
- return 0;
- }
-
- auto dc = std::make_unique<DNSComboWriter>(question, g_now, std::move(policyTags), t_pdl, std::move(data), std::move(records));
-
- dc->setSocket(fd);
- dc->d_tag = ctag;
- dc->d_qhash = qhash;
- dc->setRemote(fromaddr); // the address the query is coming from
- dc->setSource(source); // the address we assume the query is coming from, might be set by proxy protocol
- dc->setLocal(destaddr); // the address the query was received on
- dc->setDestination(destination); // the address we assume the query is sent to, might be set by proxy protocol
- dc->setMappedSource(mappedSource); // the address we assume the query is coming from. Differs from source if table-based mapping has been applied
- dc->d_tcp = false;
- dc->d_ecsFound = ecsFound;
- dc->d_ecsParsed = ecsParsed;
- dc->d_ednssubnet = ednssubnet;
- dc->d_ttlCap = ttlCap;
- dc->d_variable = variable;
- dc->d_followCNAMERecords = followCNAMEs;
- dc->d_rcode = rcode;
- dc->d_logResponse = logResponse;
+ return nullptr;
+ }
+
+ auto comboWriter = std::make_unique<DNSComboWriter>(question, g_now, std::move(policyTags), t_pdl, std::move(data), std::move(records));
+
+ comboWriter->setSocket(fileDesc);
+ comboWriter->d_tag = ctag;
+ comboWriter->d_qhash = qhash;
+ comboWriter->setRemote(fromaddr); // the address the query is coming from
+ comboWriter->setSource(source); // the address we assume the query is coming from, might be set by proxy protocol
+ comboWriter->setLocal(destaddr); // the address the query was received on
+ comboWriter->setDestination(destination); // the address we assume the query is sent to, might be set by proxy protocol
+ comboWriter->setMappedSource(mappedSource); // the address we assume the query is coming from. Differs from source if table-based mapping has been applied
+ comboWriter->d_tcp = false;
+ comboWriter->d_ecsFound = ecsFound;
+ comboWriter->d_ecsParsed = ecsParsed;
+ comboWriter->d_ednssubnet = ednssubnet;
+ comboWriter->d_ttlCap = ttlCap;
+ comboWriter->d_variable = variable;
+ comboWriter->d_followCNAMERecords = followCNAMEs;
+ comboWriter->d_rcode = rcode;
+ comboWriter->d_logResponse = logResponse;
if (t_protobufServers.servers || t_outgoingProtobufServers.servers) {
- dc->d_uuid = std::move(uniqueId);
- }
- dc->d_requestorId = requestorId;
- dc->d_deviceId = deviceId;
- dc->d_deviceName = deviceName;
- dc->d_kernelTimestamp = tv;
- dc->d_proxyProtocolValues = std::move(proxyProtocolValues);
- dc->d_routingTag = std::move(routingTag);
- dc->d_extendedErrorCode = extendedErrorCode;
- dc->d_extendedErrorExtra = std::move(extendedErrorExtra);
- dc->d_responsePaddingDisabled = responsePaddingDisabled;
- dc->d_meta = std::move(meta);
-
- dc->d_eventTrace = std::move(eventTrace);
- MT->makeThread(startDoResolve, (void*)dc.release()); // deletes dc
+ comboWriter->d_uuid = uniqueId;
+ }
+ comboWriter->d_requestorId = std::move(requestorId);
+ comboWriter->d_deviceId = std::move(deviceId);
+ comboWriter->d_deviceName = std::move(deviceName);
+ comboWriter->d_kernelTimestamp = tval;
+ comboWriter->d_proxyProtocolValues = std::move(proxyProtocolValues);
+ comboWriter->d_routingTag = std::move(routingTag);
+ comboWriter->d_extendedErrorCode = extendedErrorCode;
+ comboWriter->d_extendedErrorExtra = std::move(extendedErrorExtra);
+ comboWriter->d_responsePaddingDisabled = responsePaddingDisabled;
+ comboWriter->d_meta = std::move(meta);
+
+ comboWriter->d_eventTrace = std::move(eventTrace);
+ g_multiTasker->makeThread(startDoResolve, (void*)comboWriter.release()); // deletes dc
- return 0;
+ return nullptr;
}
-static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
+static void handleNewUDPQuestion(int fileDesc, FDMultiplexer::funcparam_t& /* var */) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
{
- ssize_t len;
static const size_t maxIncomingQuerySize = g_proxyProtocolACL.empty() ? 512 : (512 + g_proxyProtocolMaximumSize);
static thread_local std::string data;
ComboAddress fromaddr; // the address the query is coming from
ComboAddress source; // the address we assume the query is coming from, might be set by proxy protocol
ComboAddress destination; // the address we assume the query was sent to, might be set by proxy protocol
- struct msghdr msgh;
- struct iovec iov;
+ struct msghdr msgh
+ {
+ };
+ struct iovec iov
+ {
+ };
cmsgbuf_aligned cbuf;
bool firstQuery = true;
std::vector<ProxyProtocolValue> proxyProtocolValues;
proxyProtocolValues.clear();
data.resize(maxIncomingQuerySize);
fromaddr.sin6.sin6_family = AF_INET6; // this makes sure fromaddr is big enough
- fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), &data[0], data.size(), &fromaddr);
+ fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), data.data(), data.size(), &fromaddr);
- if ((len = recvmsg(fd, &msgh, 0)) >= 0) {
+ if (ssize_t len = recvmsg(fileDesc, &msgh, 0); len >= 0) {
eventTrace.clear();
- eventTrace.setEnabled(SyncRes::s_event_trace_enabled);
+ eventTrace.setEnabled(SyncRes::s_event_trace_enabled != 0);
eventTrace.add(RecEventTrace::ReqRecv);
firstQuery = false;
- if (msgh.msg_flags & MSG_TRUNC) {
+ if ((msgh.msg_flags & MSG_TRUNC) != 0) {
t_Counters.at(rec::Counter::truncatedDrops)++;
if (!g_quiet) {
SLOG(g_log << Logger::Error << "Ignoring truncated query from " << fromaddr.toString() << endl,
data.resize(static_cast<size_t>(len));
if (expectProxyProtocol(fromaddr)) {
- bool tcp;
+ bool tcp = false;
ssize_t used = parseProxyHeader(data, proxyProto, source, destination, tcp, proxyProtocolValues);
if (used <= 0) {
++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
}
return;
}
- else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+ if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
if (g_quiet) {
SLOG(g_log << Logger::Error << "Proxy protocol header in UDP packet from " << fromaddr.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping" << endl,
g_slogudpin->info(Logr::Error, "Proxy protocol header in UDP packet is larger than proxy-protocol-maximum-size",
}
ComboAddress mappedSource = source;
if (t_proxyMapping) {
- if (auto it = t_proxyMapping->lookup(source)) {
- mappedSource = it->second.address;
- ++it->second.stats.netmaskMatches;
+ if (const auto* iter = t_proxyMapping->lookup(source)) {
+ mappedSource = iter->second.address;
+ ++iter->second.stats.netmaskMatches;
}
}
if (t_remotes) {
if (t_allowFrom && !t_allowFrom->match(&mappedSource)) {
if (!g_quiet) {
- SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP query from " << mappedSource.toString() << ", address not matched by allow-from" << endl,
+ SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP query from " << mappedSource.toString() << ", address not matched by allow-from" << endl,
g_slogudpin->info(Logr::Error, "Dropping UDP query, address not matched by allow-from", "source", Logging::Loggable(mappedSource)));
}
}
BOOST_STATIC_ASSERT(offsetof(sockaddr_in, sin_port) == offsetof(sockaddr_in6, sin6_port));
- if (!fromaddr.sin4.sin_port) { // also works for IPv6
+ if (fromaddr.sin4.sin_port == 0) { // also works for IPv6
if (!g_quiet) {
- SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP query from " << fromaddr.toStringWithPort() << ", can't deal with port 0" << endl,
+ SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP query from " << fromaddr.toStringWithPort() << ", can't deal with port 0" << endl,
g_slogudpin->info(Logr::Error, "Dropping UDP query can't deal with port 0", "remote", Logging::Loggable(fromaddr)));
}
try {
const dnsheader_aligned headerdata(data.data());
- const dnsheader* dh = headerdata.get();
+ const dnsheader* dnsheader = headerdata.get();
- if (dh->qr) {
+ if (dnsheader->qr) {
t_Counters.at(rec::Counter::ignoredCount)++;
if (g_logCommonErrors) {
SLOG(g_log << Logger::Error << "Ignoring answer from " << fromaddr.toString() << " on server socket!" << endl,
g_slogudpin->info(Logr::Error, "Ignoring answer on server socket", "remote", Logging::Loggable(fromaddr)));
}
}
- else if (dh->opcode != Opcode::Query && dh->opcode != Opcode::Notify) {
+ else if (dnsheader->opcode != static_cast<unsigned>(Opcode::Query) && dnsheader->opcode != static_cast<unsigned>(Opcode::Notify)) {
t_Counters.at(rec::Counter::ignoredCount)++;
if (g_logCommonErrors) {
- SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(dh->opcode) << " from " << fromaddr.toString() << " on server socket!" << endl,
- g_slogudpin->info(Logr::Error, "Ignoring unsupported opcode server socket", "remote", Logging::Loggable(fromaddr), "opcode", Logging::Loggable(Opcode::to_s(dh->opcode))));
+ SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(dnsheader->opcode) << " from " << fromaddr.toString() << " on server socket!" << endl,
+ g_slogudpin->info(Logr::Error, "Ignoring unsupported opcode server socket", "remote", Logging::Loggable(fromaddr), "opcode", Logging::Loggable(Opcode::to_s(dnsheader->opcode))));
}
}
- else if (dh->qdcount == 0) {
+ else if (dnsheader->qdcount == 0U) {
t_Counters.at(rec::Counter::emptyQueriesCount)++;
if (g_logCommonErrors) {
SLOG(g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << fromaddr.toString() << " on server socket!" << endl,
}
}
else {
- if (dh->opcode == Opcode::Notify) {
+ if (dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(&mappedSource)) {
if (!g_quiet) {
- SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP NOTIFY from " << mappedSource.toString() << ", address not matched by allow-notify-from" << endl,
+ SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP NOTIFY from " << mappedSource.toString() << ", address not matched by allow-notify-from" << endl,
g_slogudpin->info(Logr::Error, "Dropping UDP NOTIFY from address not matched by allow-notify-from",
"source", Logging::Loggable(mappedSource)));
}
}
}
- struct timeval tv = {0, 0};
- HarvestTimestamp(&msgh, &tv);
- ComboAddress dest; // the address the query was sent to to
- dest.reset(); // this makes sure we ignore this address if not returned by recvmsg above
- auto loc = rplookup(g_listenSocketsAddresses, fd);
- if (HarvestDestinationAddress(&msgh, &dest)) {
+ struct timeval tval = {0, 0};
+ HarvestTimestamp(&msgh, &tval);
+ ComboAddress destaddr; // the address the query was sent to to
+ destaddr.reset(); // this makes sure we ignore this address if not returned by recvmsg above
+ const auto* loc = rplookup(g_listenSocketsAddresses, fileDesc);
+ if (HarvestDestinationAddress(&msgh, &destaddr)) {
// but.. need to get port too
- if (loc) {
- dest.sin4.sin_port = loc->sin4.sin_port;
+ if (loc != nullptr) {
+ destaddr.sin4.sin_port = loc->sin4.sin_port;
}
}
else {
- if (loc) {
- dest = *loc;
+ if (loc != nullptr) {
+ destaddr = *loc;
}
else {
- dest.sin4.sin_family = fromaddr.sin4.sin_family;
- socklen_t slen = dest.getSocklen();
- getsockname(fd, (sockaddr*)&dest, &slen); // if this fails, we're ok with it
+ destaddr.sin4.sin_family = fromaddr.sin4.sin_family;
+ socklen_t slen = destaddr.getSocklen();
+ getsockname(fileDesc, reinterpret_cast<sockaddr*>(&destaddr), &slen); // if this fails, we're ok with it // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
}
if (!proxyProto) {
- destination = dest;
+ destination = destaddr;
}
if (RecThreadInfo::weDistributeQueries()) {
std::string localdata = data;
- distributeAsyncFunction(data, [localdata, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace]() mutable {
- return doProcessUDPQuestion(localdata, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace);
+ distributeAsyncFunction(data, [localdata = std::move(localdata), fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace]() mutable {
+ return doProcessUDPQuestion(localdata, fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace);
});
}
else {
- doProcessUDPQuestion(data, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace);
+ doProcessUDPQuestion(data, fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace);
}
}
}
}
if (IsAnyAddress(address)) {
if (address.sin4.sin_family == AF_INET) {
- if (!setsockopt(socketFd, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one))) { // linux supports this, so why not - might fail on other systems
+ if (setsockopt(socketFd, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)) == 0) { // linux supports this, so why not - might fail on other systems
g_fromtosockets.insert(socketFd);
}
}
#ifdef IPV6_RECVPKTINFO
if (address.sin4.sin_family == AF_INET6) {
- if (!setsockopt(socketFd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one))) {
+ if (setsockopt(socketFd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) == 0) {
g_fromtosockets.insert(socketFd);
}
}
#endif
if (address.sin6.sin6_family == AF_INET6 && setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) < 0) {
int err = errno;
- SLOG(g_log << Logger::Error << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << strerror(err) << endl,
- log->error(Logr::Error, "Failed to set IPv6 socket to IPv6 only, continuing anyhow"));
+ SLOG(g_log << Logger::Warning << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << stringerror(err) << endl,
+ log->error(Logr::Warning, "Failed to set IPv6 socket to IPv6 only, continuing anyhow"));
}
}
if (::arg().mustDo("non-local-bind")) {
}
socklen_t socklen = address.getSocklen();
- if (::bind(socketFd, (struct sockaddr*)&address, socklen) < 0) {
+ if (::bind(socketFd, reinterpret_cast<struct sockaddr*>(&address), socklen) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
throw PDNSException("Resolver binding to server socket on " + address.toStringWithPort() + ": " + stringerror());
}
_exit(1);
}
- const auto& tps = targetInfo.pipes;
+ const auto& tps = targetInfo.getPipes();
- ssize_t written = write(tps.writeQueriesToThread, &tmsg, sizeof(tmsg));
+ ssize_t written = write(tps.writeQueriesToThread, &tmsg, sizeof(tmsg)); // NOLINT: correct sizeof
if (written > 0) {
- if (static_cast<size_t>(written) != sizeof(tmsg)) {
- delete tmsg;
+ if (static_cast<size_t>(written) != sizeof(tmsg)) { // NOLINT: correct sizeof
+ delete tmsg; // NOLINT: pointer ownership
unixDie("write to thread pipe returned wrong size or error");
}
}
if (error == EAGAIN || error == EWOULDBLOCK) {
return false;
}
- else {
- delete tmsg;
- unixDie("write to thread pipe returned wrong size or error:" + std::to_string(error));
- }
+ delete tmsg; // NOLINT: pointer ownership
+ unixDie("write to thread pipe returned wrong size or error:" + std::to_string(error));
}
return true;
static unsigned int getWorkerLoad(size_t workerIdx)
{
- const auto mt = RecThreadInfo::info(RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + workerIdx).mt;
- if (mt != nullptr) {
- return mt->numProcesses();
+ const auto* multiThreader = RecThreadInfo::info(RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + workerIdx).getMT();
+ if (multiThreader != nullptr) {
+ return multiThreader->numProcesses();
}
return 0;
}
static unsigned int selectWorker(unsigned int hash)
{
- assert(RecThreadInfo::numWorkers() != 0);
+ assert(RecThreadInfo::numUDPWorkers() != 0); // NOLINT: assert implementation
if (g_balancingFactor == 0) {
- return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + (hash % RecThreadInfo::numWorkers());
+ return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + (hash % RecThreadInfo::numUDPWorkers());
}
/* we start with one, representing the query we are currently handling */
double currentLoad = 1;
- std::vector<unsigned int> load(RecThreadInfo::numWorkers());
- for (size_t idx = 0; idx < RecThreadInfo::numWorkers(); idx++) {
+ std::vector<unsigned int> load(RecThreadInfo::numUDPWorkers());
+ for (size_t idx = 0; idx < RecThreadInfo::numUDPWorkers(); idx++) {
load[idx] = getWorkerLoad(idx);
currentLoad += load[idx];
}
- double targetLoad = (currentLoad / RecThreadInfo::numWorkers()) * g_balancingFactor;
+ double targetLoad = (currentLoad / RecThreadInfo::numUDPWorkers()) * g_balancingFactor;
- unsigned int worker = hash % RecThreadInfo::numWorkers();
+ unsigned int worker = hash % RecThreadInfo::numUDPWorkers();
/* at least one server has to be at or below the average load */
if (load[worker] > targetLoad) {
++t_Counters.at(rec::Counter::rebalancedQueries);
do {
- worker = (worker + 1) % RecThreadInfo::numWorkers();
+ worker = (worker + 1) % RecThreadInfo::numUDPWorkers();
} while (load[worker] > targetLoad);
}
{
if (!RecThreadInfo::self().isDistributor()) {
SLOG(g_log << Logger::Error << "distributeAsyncFunction() has been called by a worker (" << RecThreadInfo::id() << ")" << endl,
- g_slog->info(Logr::Error, "distributeAsyncFunction() has been called by a worker")); // tid will be added
+ g_slog->withName("runtime")->info(Logr::Error, "distributeAsyncFunction() has been called by a worker")); // tid will be added
_exit(1);
}
- bool ok;
- unsigned int hash = hashQuestion(reinterpret_cast<const uint8_t*>(packet.data()), packet.length(), g_disthashseed, ok);
- if (!ok) {
+ bool hashOK = false;
+ unsigned int hash = hashQuestion(reinterpret_cast<const uint8_t*>(packet.data()), packet.length(), g_disthashseed, hashOK); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (!hashOK) {
// hashQuestion does detect invalid names, so we might as well punt here instead of in the worker thread
t_Counters.at(rec::Counter::ignoredCount)++;
throw MOADNSException("too-short (" + std::to_string(packet.length()) + ") or invalid name");
}
unsigned int target = selectWorker(hash);
- ThreadMSG* tmsg = new ThreadMSG();
+ ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: pointer ownership
tmsg->func = func;
tmsg->wantAnswer = false;
was full, let's try another one */
unsigned int newTarget = 0;
do {
- newTarget = RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + dns_random(RecThreadInfo::numWorkers());
+ newTarget = RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + dns_random(RecThreadInfo::numUDPWorkers());
} while (newTarget == target);
if (!trySendingQueryToWorker(newTarget, tmsg)) {
t_Counters.at(rec::Counter::queryPipeFullDrops)++;
- delete tmsg;
+ delete tmsg; // NOLINT: pointer ownership
}
}
// coverity[leaked_storage]
// We close the chain for new entries, since they won't be processed anyway
iter->key->closed = true;
- if (iter->key->chain.empty())
+ if (iter->key->chain.empty()) {
return;
- for (PacketID::chain_t::iterator i = iter->key->chain.begin(); i != iter->key->chain.end(); ++i) {
- auto r = std::make_shared<PacketID>(*resend);
- r->fd = -1;
- r->id = *i;
- MT->sendEvent(r, &content);
+ }
+ for (auto i = iter->key->chain.begin(); i != iter->key->chain.end(); ++i) {
+ auto packetID = std::make_shared<PacketID>(*resend);
+ packetID->fd = -1;
+ packetID->id = *i;
+ g_multiTasker->sendEvent(packetID, &content);
t_Counters.at(rec::Counter::chainResends)++;
}
}
-static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t& var)
+static void handleUDPServerResponse(int fileDesc, FDMultiplexer::funcparam_t& var)
{
- std::shared_ptr<PacketID> pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
+ auto pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
PacketBuffer packet;
packet.resize(g_outgoingEDNSBufsize);
ComboAddress fromaddr;
socklen_t addrlen = sizeof(fromaddr);
- ssize_t len = recvfrom(fd, &packet.at(0), packet.size(), 0, reinterpret_cast<sockaddr*>(&fromaddr), &addrlen);
+ ssize_t len = recvfrom(fileDesc, &packet.at(0), packet.size(), 0, reinterpret_cast<sockaddr*>(&fromaddr), &addrlen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const ssize_t signed_sizeof_sdnsheader = sizeof(dnsheader);
if (len < 0) {
// len < 0: error on socket
- t_udpclientsocks->returnSocket(fd);
+ t_udpclientsocks->returnSocket(fileDesc);
PacketBuffer empty;
- MT_t::waiters_t::iterator iter = MT->d_waiters.find(pid);
- if (iter != MT->d_waiters.end()) {
+ auto iter = g_multiTasker->getWaiters().find(pid);
+ if (iter != g_multiTasker->getWaiters().end()) {
doResends(iter, pid, empty);
}
- MT->sendEvent(pid, &empty); // this denotes error (does retry lookup using other NS)
+ g_multiTasker->sendEvent(pid, &empty); // this denotes error (does retry lookup using other NS)
return;
}
// We have at least a full header
packet.resize(len);
- dnsheader dh;
- memcpy(&dh, &packet.at(0), sizeof(dh));
+ dnsheader dnsheader{};
+ memcpy(&dnsheader, &packet.at(0), sizeof(dnsheader));
auto pident = std::make_shared<PacketID>();
pident->remote = fromaddr;
- pident->id = dh.id;
- pident->fd = fd;
+ pident->id = dnsheader.id;
+ pident->fd = fileDesc;
- if (!dh.qr && g_logCommonErrors) {
+ if (!dnsheader.qr && g_logCommonErrors) {
SLOG(g_log << Logger::Notice << "Not taking data from question on outgoing socket from " << fromaddr.toStringWithPort() << endl,
g_slogout->info(Logr::Error, "Not taking data from question on outgoing socket", "from", Logging::Loggable(fromaddr)));
}
- if (!dh.qdcount || // UPC, Nominum, very old BIND on FormErr, NSD
- !dh.qr) { // one weird server
+ if (dnsheader.qdcount == 0U || // UPC, Nominum, very old BIND on FormErr, NSD
+ dnsheader.qr == 0U) { // one weird server
pident->domain.clear();
pident->type = 0;
}
else {
try {
if (len > signed_sizeof_sdnsheader) {
- pident->domain = DNSName(reinterpret_cast<const char*>(packet.data()), len, static_cast<int>(sizeof(dnsheader)), false, &pident->type); // don't copy this from above - we need to do the actual read
+ pident->domain = DNSName(reinterpret_cast<const char*>(packet.data()), static_cast<int>(len), static_cast<int>(sizeof(dnsheader)), false, &pident->type); // don't copy this from above - we need to do the actual read // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
else {
// len == sizeof(dnsheader), only header case
}
if (!pident->domain.empty()) {
- MT_t::waiters_t::iterator iter = MT->d_waiters.find(pident);
- if (iter != MT->d_waiters.end()) {
+ auto iter = g_multiTasker->getWaiters().find(pident);
+ if (iter != g_multiTasker->getWaiters().end()) {
doResends(iter, pident, packet);
}
}
retryWithName:
- if (pident->domain.empty() || MT->sendEvent(pident, &packet) == 0) {
+ if (pident->domain.empty() || g_multiTasker->sendEvent(pident, &packet) == 0) {
/* we did not find a match for this response, something is wrong */
// we do a full scan for outstanding queries on unexpected answers. not too bad since we only accept them on the right port number, which is hard enough to guess
- for (MT_t::waiters_t::iterator mthread = MT->d_waiters.begin(); mthread != MT->d_waiters.end(); ++mthread) {
- if (pident->fd == mthread->key->fd && mthread->key->remote == pident->remote && mthread->key->type == pident->type && pident->domain == mthread->key->domain) {
+ for (const auto& d_waiter : g_multiTasker->getWaiters()) {
+ if (pident->fd == d_waiter.key->fd && d_waiter.key->remote == pident->remote && d_waiter.key->type == pident->type && pident->domain == d_waiter.key->domain) {
/* we are expecting an answer from that exact source, on that exact port (since we are using connected sockets), for that qname/qtype,
but with a different message ID. That smells like a spoofing attempt. For now we will just increase the counter and will deal with
that later. */
- mthread->key->nearMisses++;
+ d_waiter.key->nearMisses++;
}
// be a bit paranoid here since we're weakening our matching
- if (pident->domain.empty() && !mthread->key->domain.empty() && !pident->type && mthread->key->type && pident->id == mthread->key->id && mthread->key->remote == pident->remote) {
+ if (pident->domain.empty() && !d_waiter.key->domain.empty() && pident->type == 0 && d_waiter.key->type != 0 && pident->id == d_waiter.key->id && d_waiter.key->remote == pident->remote) {
// cerr<<"Empty response, rest matches though, sending to a waiter"<<endl;
- pident->domain = mthread->key->domain;
- pident->type = mthread->key->type;
- goto retryWithName; // note that this only passes on an error, lwres will still reject the packet
+ pident->domain = d_waiter.key->domain;
+ pident->type = d_waiter.key->type;
+ goto retryWithName; // note that this only passes on an error, lwres will still reject the packet NOLINT(cppcoreguidelines-avoid-goto)
}
}
t_Counters.at(rec::Counter::unexpectedCount)++; // if we made it here, it really is an unexpected answer
if (g_logCommonErrors) {
- SLOG(g_log << Logger::Warning << "Discarding unexpected packet from " << fromaddr.toStringWithPort() << ": " << (pident->domain.empty() ? "<empty>" : pident->domain.toString()) << ", " << pident->type << ", " << MT->d_waiters.size() << " waiters" << endl,
+ SLOG(g_log << Logger::Warning << "Discarding unexpected packet from " << fromaddr.toStringWithPort() << ": " << (pident->domain.empty() ? "<empty>" : pident->domain.toString()) << ", " << pident->type << ", " << g_multiTasker->getWaiters().size() << " waiters" << endl,
g_slogudpin->info(Logr::Warning, "Discarding unexpected packet", "from", Logging::Loggable(fromaddr),
"qname", Logging::Loggable(pident->domain),
"qtype", Logging::Loggable(QType(pident->type)),
- "waiters", Logging::Loggable(MT->d_waiters.size())));
+ "waiters", Logging::Loggable(g_multiTasker->getWaiters().size())));
}
}
- else if (fd >= 0) {
+ else if (fileDesc >= 0) {
/* we either found a waiter (1) or encountered an issue (-1), it's up to us to clean the socket anyway */
- t_udpclientsocks->returnSocket(fd);
+ t_udpclientsocks->returnSocket(fileDesc);
}
}
}
catch (const std::exception& e) {
SLOG(g_log << Logger::Warning << "Error while loading the Public Suffix List from '" << file << "', falling back to the built-in list: " << e.what() << endl,
- g_slog->withName("runtime")->error(Logr::Error, e.what(), "Loaded the Public Suffix List", "file", Logging::Loggable(file)));
+ g_slog->withName("runtime")->error(Logr::Error, e.what(), "Error while loading the Public Suffix List", "file", Logging::Loggable(file)));
}
}
static void parseRPZParameters(rpzOptions_t& have, std::shared_ptr<DNSFilterEngine::Zone>& zone, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL)
{
- if (have.count("policyName")) {
+ if (have.count("policyName") != 0) {
polName = boost::get<std::string>(have["policyName"]);
}
- if (have.count("defpol")) {
+ if (have.count("defpol") != 0) {
defpol = DNSFilterEngine::Policy();
defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(have["defpol"]);
defpol->setName(polName);
if (defpol->d_kind == DNSFilterEngine::PolicyKind::Custom) {
- defpol->d_custom.push_back(DNSRecordContent::mastermake(QType::CNAME, QClass::IN,
- boost::get<string>(have["defcontent"])));
+ if (!defpol->d_custom) {
+ defpol->d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+ }
+ defpol->d_custom->push_back(DNSRecordContent::make(QType::CNAME, QClass::IN,
+ boost::get<string>(have["defcontent"])));
- if (have.count("defttl"))
+ if (have.count("defttl") != 0) {
defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(have["defttl"]));
- else
+ }
+ else {
defpol->d_ttl = -1; // get it from the zone
+ }
}
- if (have.count("defpolOverrideLocalData")) {
+ if (have.count("defpolOverrideLocalData") != 0) {
defpolOverrideLocal = boost::get<bool>(have["defpolOverrideLocalData"]);
}
}
- if (have.count("maxTTL")) {
+ if (have.count("maxTTL") != 0) {
maxTTL = boost::get<uint32_t>(have["maxTTL"]);
}
- if (have.count("zoneSizeHint")) {
+ if (have.count("zoneSizeHint") != 0) {
auto zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(have["zoneSizeHint"]));
if (zoneSizeHint > 0) {
zone->reserve(zoneSizeHint);
}
}
- if (have.count("tags")) {
- const auto tagsTable = boost::get<std::vector<std::pair<int, std::string>>>(have["tags"]);
+ if (have.count("tags") != 0) {
+ const auto& tagsTable = boost::get<std::vector<std::pair<int, std::string>>>(have["tags"]);
std::unordered_set<std::string> tags;
for (const auto& tag : tagsTable) {
tags.insert(tag.second);
}
zone->setTags(std::move(tags));
}
- if (have.count("overridesGettag")) {
+ if (have.count("overridesGettag") != 0) {
zone->setPolicyOverridesGettag(boost::get<bool>(have["overridesGettag"]));
}
- if (have.count("extendedErrorCode")) {
+ if (have.count("extendedErrorCode") != 0) {
auto code = boost::get<uint32_t>(have["extendedErrorCode"]);
if (code > std::numeric_limits<uint16_t>::max()) {
throw std::runtime_error("Invalid extendedErrorCode value " + std::to_string(code) + " in RPZ configuration");
}
zone->setExtendedErrorCode(static_cast<uint16_t>(code));
- if (have.count("extendedErrorExtra")) {
+ if (have.count("extendedErrorExtra") != 0) {
zone->setExtendedErrorExtra(boost::get<std::string>(have["extendedErrorExtra"]));
}
}
+ if (have.count("includeSOA") != 0) {
+ zone->setIncludeSOA(boost::get<bool>(have["includeSOA"]));
+ }
+ if (have.count("ignoreDuplicates") != 0) {
+ zone->setIgnoreDuplicates(boost::get<bool>(have["ignoreDuplicates"]));
+ }
}
typedef std::unordered_map<std::string, boost::variant<bool, uint64_t, std::string, std::vector<std::pair<int, std::string>>>> protobufOptions_t;
}
catch (const PDNSException& e) {
SLOG(g_log << Logger::Warning << "Unable to pre-load RPZ zone " << zoneName << " from seed file '" << seedFile << "': " << e.reason << endl,
- log->error(Logr::Warning, e.reason, "Exception while pre-loadin RPZ zone", "exception", Logging::Loggable("PDNSException")));
+ log->error(Logr::Warning, e.reason, "Exception while pre-loading RPZ zone", "exception", Logging::Loggable("PDNSException")));
zone->clear();
}
catch (const std::exception& e) {
SLOG(g_log << Logger::Warning << "Unable to pre-load RPZ zone " << zoneName << " from seed file '" << seedFile << "': " << e.what() << endl,
- log->error(Logr::Warning, e.what(), "Exception while pre-loadin RPZ zone", "exception", Logging::Loggable("std::exception")));
+ log->error(Logr::Warning, e.what(), "Exception while pre-loading RPZ zone", "exception", Logging::Loggable("std::exception")));
zone->clear();
}
}
}
catch (const std::exception& e) {
SLOG(g_log << Logger::Error << "Problem configuring 'rpzPrimary': " << e.what() << endl,
- lci.d_slog->error(Logr::Critical, e.what(), "Exception configuring 'rpzPrimary'", "exception", Logging::Loggable("std::exception")));
- exit(1); // FIXME proper exit code?
+ lci.d_slog->error(Logr::Error, e.what(), "Exception configuring 'rpzPrimary'", "exception", Logging::Loggable("std::exception")));
}
catch (const PDNSException& e) {
SLOG(g_log << Logger::Error << "Problem configuring 'rpzPrimary': " << e.reason << endl,
- lci.d_slog->error(Logr::Critical, e.reason, "Exception configuring 'rpzPrimary'", Logging::Loggable("PDNSException")));
- exit(1); // FIXME proper exit code?
+ lci.d_slog->error(Logr::Error, e.reason, "Exception configuring 'rpzPrimary'", Logging::Loggable("PDNSException")));
}
- delayedThreads.rpzPrimaryThreads.push_back(std::make_tuple(primaries, defpol, defpolOverrideLocal, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, refresh, sr, dumpFile));
+ delayedThreads.rpzPrimaryThreads.emplace_back(RPZTrackerParams{std::move(primaries), std::move(defpol), defpolOverrideLocal, maxTTL, zoneIdx, std::move(tt), maxReceivedXFRMBytes, localAddress, axfrTimeout, refresh, std::move(sr), std::move(dumpFile)});
}
// A wrapper class that loads the standard Lua defintions into the context, so that we can use things like pdns.A
log->info(Logr::Info, "Loading RPZ from file"));
zone->setName(polName);
loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL);
- lci.dfe.addZone(zone);
+ lci.dfe.addZone(std::move(zone));
SLOG(g_log << Logger::Warning << "Done loading RPZ from file '" << filename << "'" << endl,
log->info(Logr::Info, "Done loading RPZ from file"));
}
}
}
- lci.ztcConfigs[validZoneName] = conf;
+ lci.ztcConfigs[validZoneName] = std::move(conf);
}
catch (const std::exception& e) {
SLOG(g_log << Logger::Error << "Problem configuring zoneToCache for zone '" << zoneName << "': " << e.what() << endl,
}
catch (std::exception& e) {
SLOG(g_log << Logger::Error << "Error while adding protobuf logger: " << e.what() << endl,
- lci.d_slog->error(Logr::Error, e.what(), "Exception while adding protobuf logger", "exception", Logging::Loggable("std::exception")));
+ lci.d_slog->error(Logr::Error, e.what(), "Exception while adding protobuf logger", "exception", Logging::Loggable("std::exception")));
}
catch (PDNSException& e) {
SLOG(g_log << Logger::Error << "Error while adding protobuf logger: " << e.reason << endl,
- lci.d_slog->error(Logr::Error, e.reason, "Exception while adding protobuf logger", "exception", Logging::Loggable("PDNSException")));
+ lci.d_slog->error(Logr::Error, e.reason, "Exception while adding protobuf logger", "exception", Logging::Loggable("PDNSException")));
}
}
else {
catch (const PDNSException& exp) {
// exp is the exception that was thrown from inside the lambda
SLOG(g_log << exp.reason << std::endl,
- lci.d_slog->error(Logr::Error, exp.reason, "Exception loading Lua", "exception", Logging::Loggable("PDNSException"))) }
+ lci.d_slog->error(Logr::Error, exp.reason, "Exception loading Lua", "exception", Logging::Loggable("PDNSException")));
+ }
throw;
}
catch (std::exception& err) {
try {
// The get calls all return a value object here. That is essential, since we want copies so that RPZIXFRTracker gets values
// with the proper lifetime.
- std::thread t(RPZIXFRTracker, std::get<0>(rpzPrimary), std::get<1>(rpzPrimary), std::get<2>(rpzPrimary), std::get<3>(rpzPrimary), std::get<4>(rpzPrimary), std::get<5>(rpzPrimary), std::get<6>(rpzPrimary) * 1024 * 1024, std::get<7>(rpzPrimary), std::get<8>(rpzPrimary), std::get<9>(rpzPrimary), std::get<10>(rpzPrimary), std::get<11>(rpzPrimary), generation);
- t.detach();
+ std::thread theThread(RPZIXFRTracker, rpzPrimary, generation);
+ theThread.detach();
}
catch (const std::exception& e) {
SLOG(g_log << Logger::Error << "Problem starting RPZIXFRTracker thread: " << e.what() << endl,
- g_slog->withName("rpz")->error(Logr::Error, e.what(), "Exception startng RPZIXFRTracker thread", "exception", Logging::Loggable("std::exception")));
+ g_slog->withName("rpz")->error(Logr::Error, e.what(), "Exception starting RPZIXFRTracker thread", "exception", Logging::Loggable("std::exception")));
exit(1);
}
catch (const PDNSException& e) {
SLOG(g_log << Logger::Error << "Problem starting RPZIXFRTracker thread: " << e.reason << endl,
- g_slog->withName("rpz")->error(Logr::Error, e.reason, "Exception startng RPZIXFRTracker thread", "exception", Logging::Loggable("PDNSException")));
+ g_slog->withName("rpz")->error(Logr::Error, e.reason, "Exception starting RPZIXFRTracker thread", "exception", Logging::Loggable("PDNSException")));
exit(1);
}
}
#include "rec-zonetocache.hh"
#include "logging.hh"
#include "fstrm_logger.hh"
+#include "rpzloader.hh"
struct ProtobufExportConfig
{
struct luaConfigDelayedThreads
{
- // Please make sure that the tuple below only contains value types since they are used as parameters in a thread ct
- std::vector<std::tuple<std::vector<ComboAddress>, boost::optional<DNSFilterEngine::Policy>, bool, uint32_t, size_t, TSIGTriplet, size_t, ComboAddress, uint16_t, uint32_t, std::shared_ptr<const SOARecordContent>, std::string>> rpzPrimaryThreads;
+ std::vector<RPZTrackerParams> rpzPrimaryThreads;
};
void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads, ProxyMapping&);
#include "rec-taskqueue.hh"
#include "secpoll-recursor.hh"
#include "logging.hh"
+#include "dnsseckeeper.hh"
+#include "settings/cxxsettings.hh"
+#include "json.hh"
#ifdef NOD_ENABLED
#include "nod.hh"
#ifdef HAVE_LIBSODIUM
#include <sodium.h>
+
+#include <cstddef>
+#include <utility>
#endif
#ifdef HAVE_SYSTEMD
string g_pidfname;
RecursorControlChannel g_rcc; // only active in the handler thread
bool g_regressionTestMode;
+bool g_yamlSettings;
#ifdef NOD_ENABLED
bool g_nodEnabled;
std::shared_ptr<Logr::Logger> g_slogudpout;
/* without reuseport, all listeners share the same sockets */
-deferredAdd_t g_deferredAdds;
+static deferredAdd_t s_deferredUDPadds;
+static deferredAdd_t s_deferredTCPadds;
/* first we have the handler thread, t_id == 0 (some other
helper threads like SNMP might have t_id == 0 as well)
bool RecThreadInfo::s_weDistributeQueries; // if true, 1 or more threads listen on the incoming query sockets and distribute them to workers
unsigned int RecThreadInfo::s_numDistributorThreads;
-unsigned int RecThreadInfo::s_numWorkerThreads;
+unsigned int RecThreadInfo::s_numUDPWorkerThreads;
+unsigned int RecThreadInfo::s_numTCPWorkerThreads;
thread_local unsigned int RecThreadInfo::t_id;
static std::map<unsigned int, std::set<int>> parseCPUMap(Logr::log_t log)
stringtok(parts, value, " \t");
for (const auto& part : parts) {
- if (part.find('=') == string::npos)
+ if (part.find('=') == string::npos) {
continue;
+ }
try {
auto headers = splitField(part, '=');
if (cpuMapping == cpusMap.cend()) {
return;
}
- int rc = mapThreadToCPUList(tid, cpuMapping->second);
- if (rc == 0) {
+ int ret = mapThreadToCPUList(tid, cpuMapping->second);
+ if (ret == 0) {
if (!g_slogStructured) {
g_log << Logger::Info << "CPU affinity for thread " << n << " has been set to CPU map:";
for (const auto cpu : cpuMapping->second) {
for (const auto cpu : cpuMapping->second) {
g_log << Logger::Info << " " << cpu;
}
- g_log << Logger::Info << ' ' << strerror(rc) << endl;
+ g_log << Logger::Info << ' ' << stringerror(ret) << endl;
}
else {
- log->error(Logr::Warning, rc, "Error setting CPU affinity", "thread", Logging::Loggable(n), "cpumap", Logging::IterLoggable(cpuMapping->second.begin(), cpuMapping->second.end()));
+ log->error(Logr::Warning, ret, "Error setting CPU affinity", "thread", Logging::Loggable(n), "cpumap", Logging::IterLoggable(cpuMapping->second.begin(), cpuMapping->second.end()));
}
}
}
static void recursorThread();
-void RecThreadInfo::start(unsigned int id, const string& tname, const std::map<unsigned int, std::set<int>>& cpusMap, Logr::log_t log)
+void RecThreadInfo::start(unsigned int tid, const string& tname, const std::map<unsigned int, std::set<int>>& cpusMap, Logr::log_t log)
{
name = tname;
- thread = std::thread([id, tname] {
- t_id = id;
+ thread = std::thread([tid, tname] {
+ t_id = tid;
const string threadPrefix = "rec/";
setThreadName(threadPrefix + tname);
recursorThread();
});
- setCPUMap(cpusMap, id, thread.native_handle(), log);
+ setCPUMap(cpusMap, tid, thread.native_handle(), log);
}
int RecThreadInfo::runThreads(Logr::log_t log)
{
int ret = EXIT_SUCCESS;
- unsigned int currentThreadId = 1;
const auto cpusMap = parseCPUMap(log);
- if (RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers() == 1) {
- SLOG(g_log << Logger::Warning << "Operating with single distributor/worker thread" << endl,
- log->info(Logr::Notice, "Operating with single distributor/worker thread"));
+ if (RecThreadInfo::numDistributors() + RecThreadInfo::numUDPWorkers() == 1) {
+ SLOG(g_log << Logger::Warning << "Operating with single UDP distributor/worker thread" << endl,
+ log->info(Logr::Notice, "Operating with single UDP distributor/worker thread"));
/* This thread handles the web server, carbon, statistics and the control channel */
- auto& handlerInfo = RecThreadInfo::info(0);
+ unsigned int currentThreadId = 0;
+ auto& handlerInfo = RecThreadInfo::info(currentThreadId);
handlerInfo.setHandler();
- handlerInfo.start(0, "web+stat", cpusMap, log);
- auto& taskInfo = RecThreadInfo::info(2);
- taskInfo.setTaskThread();
- taskInfo.start(2, "task", cpusMap, log);
+ handlerInfo.start(currentThreadId, "web+stat", cpusMap, log);
+
+ // We skip the single UDP worker thread 1, it's handled after the loop and taskthreads
+ currentThreadId = 2;
+ for (unsigned int thread = 0; thread < RecThreadInfo::numTCPWorkers(); thread++, currentThreadId++) {
+ auto& info = RecThreadInfo::info(currentThreadId);
+ info.setTCPListener();
+ info.setWorker();
+ info.start(currentThreadId, "tcpworker", cpusMap, log);
+ }
+
+ for (unsigned int thread = 0; thread < RecThreadInfo::numTaskThreads(); thread++, currentThreadId++) {
+ auto& taskInfo = RecThreadInfo::info(currentThreadId);
+ taskInfo.setTaskThread();
+ taskInfo.start(currentThreadId, "task", cpusMap, log);
+ }
+ currentThreadId = 1;
auto& info = RecThreadInfo::info(currentThreadId);
info.setListener();
info.setWorker();
- info.setThreadId(currentThreadId++);
+ RecThreadInfo::setThreadId(currentThreadId);
recursorThread();
- handlerInfo.thread.join();
- if (handlerInfo.exitCode != 0) {
- ret = handlerInfo.exitCode;
- }
- taskInfo.thread.join();
- if (taskInfo.exitCode != 0) {
- ret = taskInfo.exitCode;
+ for (unsigned int thread = 0; thread < RecThreadInfo::numRecursorThreads(); thread++) {
+ if (thread == 1) {
+ continue;
+ }
+ auto& tInfo = RecThreadInfo::info(thread);
+ tInfo.thread.join();
+ if (tInfo.exitCode != 0) {
+ ret = tInfo.exitCode;
+ }
}
}
else {
// Setup RecThreadInfo objects
- unsigned int tmp = currentThreadId;
+ unsigned int currentThreadId = 1;
if (RecThreadInfo::weDistributeQueries()) {
- for (unsigned int n = 0; n < RecThreadInfo::numDistributors(); ++n) {
- RecThreadInfo::info(tmp++).setListener();
+ for (unsigned int thread = 0; thread < RecThreadInfo::numDistributors(); thread++, currentThreadId++) {
+ RecThreadInfo::info(currentThreadId).setListener();
}
}
- for (unsigned int n = 0; n < RecThreadInfo::numWorkers(); ++n) {
- auto& info = RecThreadInfo::info(tmp++);
+ for (unsigned int thread = 0; thread < RecThreadInfo::numUDPWorkers(); thread++, currentThreadId++) {
+ auto& info = RecThreadInfo::info(currentThreadId);
info.setListener(!RecThreadInfo::weDistributeQueries());
info.setWorker();
}
- for (unsigned int n = 0; n < RecThreadInfo::numTaskThreads(); ++n) {
- auto& info = RecThreadInfo::info(tmp++);
+ for (unsigned int thread = 0; thread < RecThreadInfo::numTCPWorkers(); thread++, currentThreadId++) {
+ auto& info = RecThreadInfo::info(currentThreadId);
+ info.setTCPListener();
+ info.setWorker();
+ }
+ for (unsigned int thread = 0; thread < RecThreadInfo::numTaskThreads(); thread++, currentThreadId++) {
+ auto& info = RecThreadInfo::info(currentThreadId);
info.setTaskThread();
}
// And now start the actual threads
+ currentThreadId = 1;
if (RecThreadInfo::weDistributeQueries()) {
SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numDistributors() << " distributor threads" << endl,
log->info(Logr::Notice, "Launching distributor threads", "count", Logging::Loggable(RecThreadInfo::numDistributors())));
- for (unsigned int n = 0; n < RecThreadInfo::numDistributors(); ++n) {
+ for (unsigned int thread = 0; thread < RecThreadInfo::numDistributors(); thread++, currentThreadId++) {
auto& info = RecThreadInfo::info(currentThreadId);
- info.start(currentThreadId++, "distr", cpusMap, log);
+ info.start(currentThreadId, "distr", cpusMap, log);
}
}
- SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numWorkers() << " worker threads" << endl,
- log->info(Logr::Notice, "Launching worker threads", "count", Logging::Loggable(RecThreadInfo::numWorkers())));
+ SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numUDPWorkers() << " worker threads" << endl,
+ log->info(Logr::Notice, "Launching worker threads", "count", Logging::Loggable(RecThreadInfo::numUDPWorkers())));
+
+ for (unsigned int thread = 0; thread < RecThreadInfo::numUDPWorkers(); thread++, currentThreadId++) {
+ auto& info = RecThreadInfo::info(currentThreadId);
+ info.start(currentThreadId, "worker", cpusMap, log);
+ }
- for (unsigned int n = 0; n < RecThreadInfo::numWorkers(); ++n) {
+ SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numTCPWorkers() << " tcpworker threads" << endl,
+ log->info(Logr::Notice, "Launching tcpworker threads", "count", Logging::Loggable(RecThreadInfo::numTCPWorkers())));
+
+ for (unsigned int thread = 0; thread < RecThreadInfo::numTCPWorkers(); thread++, currentThreadId++) {
auto& info = RecThreadInfo::info(currentThreadId);
- info.start(currentThreadId++, "worker", cpusMap, log);
+ info.start(currentThreadId, "tcpworker", cpusMap, log);
}
- for (unsigned int n = 0; n < RecThreadInfo::numTaskThreads(); ++n) {
+ for (unsigned int thread = 0; thread < RecThreadInfo::numTaskThreads(); thread++, currentThreadId++) {
auto& info = RecThreadInfo::info(currentThreadId);
- info.start(currentThreadId++, "task", cpusMap, log);
+ info.start(currentThreadId, "task", cpusMap, log);
}
/* This thread handles the web server, carbon, statistics and the control channel */
- auto& info = RecThreadInfo::info(0);
+ currentThreadId = 0;
+ auto& info = RecThreadInfo::info(currentThreadId);
info.setHandler();
- info.start(0, "web+stat", cpusMap, log);
+ info.start(currentThreadId, "web+stat", cpusMap, log);
- for (auto& ti : RecThreadInfo::infos()) {
- ti.thread.join();
- if (ti.exitCode != 0) {
- ret = ti.exitCode;
+ for (auto& tInfo : RecThreadInfo::infos()) {
+ tInfo.thread.join();
+ if (tInfo.exitCode != 0) {
+ ret = tInfo.exitCode;
}
}
}
}
/* thread 0 is the handler / SNMP, worker threads start at 1 */
- for (unsigned int n = 0; n < numRecursorThreads(); ++n) {
- auto& threadInfo = info(n);
+ for (unsigned int thread = 0; thread < numRecursorThreads(); ++thread) {
+ auto& threadInfo = info(thread);
- int fd[2];
- if (pipe(fd) < 0)
+ std::array<int, 2> fileDesc{};
+ if (pipe(fileDesc.data()) < 0) {
unixDie("Creating pipe for inter-thread communications");
+ }
- threadInfo.pipes.readToThread = fd[0];
- threadInfo.pipes.writeToThread = fd[1];
+ threadInfo.pipes.readToThread = fileDesc[0];
+ threadInfo.pipes.writeToThread = fileDesc[1];
// handler thread only gets first pipe, not the others
- if (n == 0) {
+ if (thread == 0) {
continue;
}
- if (pipe(fd) < 0)
+ if (pipe(fileDesc.data()) < 0) {
unixDie("Creating pipe for inter-thread communications");
+ }
- threadInfo.pipes.readFromThread = fd[0];
- threadInfo.pipes.writeFromThread = fd[1];
+ threadInfo.pipes.readFromThread = fileDesc[0];
+ threadInfo.pipes.writeFromThread = fileDesc[1];
- if (pipe(fd) < 0)
+ if (pipe(fileDesc.data()) < 0) {
unixDie("Creating pipe for inter-thread communications");
+ }
- threadInfo.pipes.readQueriesToThread = fd[0];
- threadInfo.pipes.writeQueriesToThread = fd[1];
+ threadInfo.pipes.readQueriesToThread = fileDesc[0];
+ threadInfo.pipes.writeQueriesToThread = fileDesc[1];
if (pipeBufferSize > 0) {
if (!setPipeBufferSize(threadInfo.pipes.writeQueriesToThread, pipeBufferSize)) {
int err = errno;
- SLOG(g_log << Logger::Warning << "Error resizing the buffer of the distribution pipe for thread " << n << " to " << pipeBufferSize << ": " << strerror(err) << endl,
- log->error(Logr::Warning, err, "Error resizing the buffer of the distribution pipe for thread", "thread", Logging::Loggable(n), "size", Logging::Loggable(pipeBufferSize)));
+ SLOG(g_log << Logger::Warning << "Error resizing the buffer of the distribution pipe for thread " << thread << " to " << pipeBufferSize << ": " << stringerror(err) << endl,
+ log->error(Logr::Warning, err, "Error resizing the buffer of the distribution pipe for thread", "thread", Logging::Loggable(thread), "size", Logging::Loggable(pipeBufferSize)));
auto existingSize = getPipeBufferSize(threadInfo.pipes.writeQueriesToThread);
if (existingSize > 0) {
- SLOG(g_log << Logger::Warning << "The current size of the distribution pipe's buffer for thread " << n << " is " << existingSize << endl,
- log->info(Logr::Warning, "The current size of the distribution pipe's buffer for thread", "thread", Logging::Loggable(n), "size", Logging::Loggable(existingSize)));
+ SLOG(g_log << Logger::Warning << "The current size of the distribution pipe's buffer for thread " << thread << " is " << existingSize << endl,
+ log->info(Logr::Warning, "The current size of the distribution pipe's buffer for thread", "thread", Logging::Loggable(thread), "size", Logging::Loggable(existingSize)));
}
}
}
static FDMultiplexer* getMultiplexer(Logr::log_t log)
{
- FDMultiplexer* ret;
- for (const auto& i : FDMultiplexer::getMultiplexerMap()) {
+ FDMultiplexer* ret = nullptr;
+ for (const auto& mplexer : FDMultiplexer::getMultiplexerMap()) {
try {
- ret = i.second(FDMultiplexer::s_maxevents);
+ ret = mplexer.second(FDMultiplexer::s_maxevents);
return ret;
}
catch (FDMultiplexerException& fe) {
return true;
}
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedRemote, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta)
+void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedSource, const Netmask& ednssubnet, bool tcp, uint16_t queryID, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta)
{
auto log = g_slog->withName("pblq");
requestor.setPort(remote.getPort());
}
else {
- Netmask requestorNM(mappedRemote, mappedRemote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ Netmask requestorNM(mappedSource, mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
requestor = requestorNM.getMaskedNetwork();
- requestor.setPort(mappedRemote.getPort());
+ requestor.setPort(mappedSource.getPort());
}
- pdns::ProtoZero::RecMessage m{128, std::string::size_type(policyTags.empty() ? 0 : 64)}; // It's a guess
- m.setType(pdns::ProtoZero::Message::MessageType::DNSQueryType);
- m.setRequest(uniqueId, requestor, local, qname, qtype, qclass, id, tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP, len);
- m.setServerIdentity(SyncRes::s_serverID);
- m.setEDNSSubnet(ednssubnet, ednssubnet.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- m.setRequestorId(requestorId);
- m.setDeviceId(deviceId);
- m.setDeviceName(deviceName);
+ pdns::ProtoZero::RecMessage msg{128, std::string::size_type(policyTags.empty() ? 0 : 64)}; // It's a guess
+ msg.setType(pdns::ProtoZero::Message::MessageType::DNSQueryType);
+ msg.setRequest(uniqueId, requestor, local, qname, qtype, qclass, queryID, tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP, len);
+ msg.setServerIdentity(SyncRes::s_serverID);
+ msg.setEDNSSubnet(ednssubnet, ednssubnet.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ msg.setRequestorId(requestorId);
+ msg.setDeviceId(deviceId);
+ msg.setDeviceName(deviceName);
if (!policyTags.empty()) {
- m.addPolicyTags(policyTags);
+ msg.addPolicyTags(policyTags);
}
for (const auto& mit : meta) {
- m.setMeta(mit.first, mit.second.stringVal, mit.second.intVal);
+ msg.setMeta(mit.first, mit.second.stringVal, mit.second.intVal);
}
- std::string msg(m.finishAndMoveBuf());
+ std::string strMsg(msg.finishAndMoveBuf());
for (auto& server : *t_protobufServers.servers) {
- remoteLoggerQueueData(*server, msg);
+ remoteLoggerQueueData(*server, strMsg);
}
}
}
}
-void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
- const RecursorPacketCache::OptPBData& pbData, const struct timeval& tv,
+void protobufLogResponse(const struct dnsheader* header, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
+ const RecursorPacketCache::OptPBData& pbData, const struct timeval& tval,
bool tcp, const ComboAddress& source, const ComboAddress& destination,
const ComboAddress& mappedSource,
const EDNSSubnetOpts& ednssubnet,
const boost::uuids::uuid& uniqueId, const string& requestorId, const string& deviceId,
const string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta,
- const RecEventTrace& eventTrace)
+ const RecEventTrace& eventTrace,
+ const std::unordered_set<std::string>& policyTags)
{
pdns::ProtoZero::RecMessage pbMessage(pbData ? pbData->d_message : "", pbData ? pbData->d_response : "", 64, 10); // The extra bytes we are going to add
// Normally we take the immutable string from the cache and append a few values, but if it's not there (can this happen?)
}
// In response part
- if (g_useKernelTimestamp && tv.tv_sec) {
- pbMessage.setQueryTime(tv.tv_sec, tv.tv_usec);
+ if (g_useKernelTimestamp && tval.tv_sec != 0) {
+ pbMessage.setQueryTime(tval.tv_sec, tval.tv_usec);
}
else {
pbMessage.setQueryTime(g_now.tv_sec, g_now.tv_usec);
// In message part
if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
+ pbMessage.setSocketFamily(source.sin4.sin_family);
Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- auto requestor = requestorNM.getMaskedNetwork();
+ const auto& requestor = requestorNM.getMaskedNetwork();
pbMessage.setFrom(requestor);
pbMessage.setFromPort(source.getPort());
}
else {
+ pbMessage.setSocketFamily(mappedSource.sin4.sin_family);
Netmask requestorNM(mappedSource, mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- auto requestor = requestorNM.getMaskedNetwork();
+ const auto& requestor = requestorNM.getMaskedNetwork();
pbMessage.setFrom(requestor);
pbMessage.setFromPort(mappedSource.getPort());
}
pbMessage.setMessageIdentity(uniqueId);
pbMessage.setTo(destination);
pbMessage.setSocketProtocol(tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
- pbMessage.setId(dh->id);
+ pbMessage.setId(header->id);
pbMessage.setTime();
pbMessage.setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
pbMessage.setDeviceId(deviceId);
pbMessage.setDeviceName(deviceName);
pbMessage.setToPort(destination.getPort());
- for (const auto& m : meta) {
- pbMessage.setMeta(m.first, m.second.stringVal, m.second.intVal);
+ for (const auto& metaItem : meta) {
+ pbMessage.setMeta(metaItem.first, metaItem.second.stringVal, metaItem.second.intVal);
}
#ifdef NOD_ENABLED
if (g_nodEnabled) {
pbMessage.setNewlyObservedDomain(false);
}
#endif
- if (eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) {
+ if (eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) != 0) {
pbMessage.addEvents(eventTrace);
}
+ pbMessage.addPolicyTags(policyTags);
+
protobufLogResponse(pbMessage);
}
options["outputQueueSize"] = config.outputQueueSize;
options["queueNotifyThreshold"] = config.queueNotifyThreshold;
options["reopenInterval"] = config.reopenInterval;
- FrameStreamLogger* fsl = nullptr;
+ unique_ptr<FrameStreamLogger> fsl = nullptr;
try {
ComboAddress address(server);
- fsl = new FrameStreamLogger(address.sin4.sin_family, address.toStringWithPort(), true, options);
+ fsl = make_unique<FrameStreamLogger>(address.sin4.sin_family, address.toStringWithPort(), true, options);
}
catch (const PDNSException& e) {
- fsl = new FrameStreamLogger(AF_UNIX, server, true, options);
+ fsl = make_unique<FrameStreamLogger>(AF_UNIX, server, true, options);
}
fsl->setLogQueries(config.logQueries);
fsl->setLogResponses(config.logResponses);
fsl->setLogNODs(config.logNODs);
fsl->setLogUDRs(config.logUDRs);
- result->emplace_back(fsl);
+ result->emplace_back(std::move(fsl));
}
catch (const std::exception& e) {
SLOG(g_log << Logger::Error << "Error while starting dnstap framestream logger to '" << server << ": " << e.what() << endl,
static void makeControlChannelSocket(int processNum = -1)
{
string sockname = ::arg()["socket-dir"] + "/" + g_programname;
- if (processNum >= 0)
+ if (processNum >= 0) {
sockname += "." + std::to_string(processNum);
+ }
sockname += ".controlsocket";
g_rcc.listen(sockname);
- int sockowner = -1;
- int sockgroup = -1;
+ uid_t sockowner = -1;
+ gid_t sockgroup = -1;
- if (!::arg().isEmpty("socket-group"))
+ if (!::arg().isEmpty("socket-group")) {
sockgroup = ::arg().asGid("socket-group");
- if (!::arg().isEmpty("socket-owner"))
+ }
+ if (!::arg().isEmpty("socket-owner")) {
sockowner = ::arg().asUid("socket-owner");
+ }
- if (sockgroup > -1 || sockowner > -1) {
+ if (sockgroup != static_cast<gid_t>(-1) || sockowner != static_cast<uid_t>(-1)) {
if (chown(sockname.c_str(), sockowner, sockgroup) < 0) {
unixDie("Failed to chown control socket");
}
static void writePid(Logr::log_t log)
{
- if (!::arg().mustDo("write-pid"))
+ if (!::arg().mustDo("write-pid")) {
return;
- ofstream of(g_pidfname.c_str(), std::ios_base::app);
- if (of)
- of << Utility::getpid() << endl;
+ }
+ ofstream ostr(g_pidfname.c_str(), std::ios_base::app);
+ if (ostr) {
+ ostr << Utility::getpid() << endl;
+ }
else {
int err = errno;
SLOG(g_log << Logger::Error << "Writing pid for " << Utility::getpid() << " to " << g_pidfname << " failed: " << stringerror(err) << endl,
}
catch (const PDNSException& e) {
SLOG(g_log << Logger::Error << "new-domain-history-dir (" << ::arg()["new-domain-history-dir"] << ") is not readable or does not exist" << endl,
- log->error(Logr::Error, e.reason, "new-domain-history-dir is not readbale or does not exists", "dir", Logging::Loggable(::arg()["new-domain-history-dir"])));
+ log->error(Logr::Error, e.reason, "new-domain-history-dir is not readable or does not exists", "dir", Logging::Loggable(::arg()["new-domain-history-dir"])));
_exit(1);
}
if (!t_nodDBp->init()) {
log->info(Logr::Error, "Could not initialize domain tracking"));
_exit(1);
}
- std::thread t(nod::NODDB::startHousekeepingThread, t_nodDBp, std::this_thread::get_id());
- t.detach();
+ std::thread thread(nod::NODDB::startHousekeepingThread, t_nodDBp, std::this_thread::get_id());
+ thread.detach();
}
if (g_udrEnabled) {
uint32_t num_cells = ::arg().asNum("unique-response-db-size");
log->info(Logr::Error, "Could not initialize unique response tracking"));
_exit(1);
}
- std::thread t(nod::UniqueResponseDB::startHousekeepingThread, t_udrDBp, std::this_thread::get_id());
- t.detach();
+ std::thread thread(nod::UniqueResponseDB::startHousekeepingThread, t_udrDBp, std::this_thread::get_id());
+ thread.detach();
}
}
{
vector<string> parts;
stringtok(parts, wlist, ",; ");
- for (const auto& a : parts) {
- g_nodDomainWL.add(DNSName(a));
+ for (const auto& part : parts) {
+ g_nodDomainWL.add(DNSName(part));
}
}
static void daemonize(Logr::log_t log)
{
- if (fork())
- exit(0); // bye bye
+ if (auto pid = fork(); pid != 0) {
+ if (pid < 0) {
+ int err = errno;
+ SLOG(g_log << Logger::Critical << "Fork failed: " << stringerror(err) << endl,
+ log->error(Logr::Critical, err, "Fork failed"));
+ exit(1); // NOLINT(concurrency-mt-unsafe)
+ }
+ exit(0); // NOLINT(concurrency-mt-unsafe)
+ }
setsid();
- int i = open("/dev/null", O_RDWR); /* open stdin */
- if (i < 0) {
+ int devNull = open("/dev/null", O_RDWR); /* open stdin */
+ if (devNull < 0) {
int err = errno;
SLOG(g_log << Logger::Critical << "Unable to open /dev/null: " << stringerror(err) << endl,
log->error(Logr::Critical, err, "Unable to open /dev/null"));
}
else {
- dup2(i, 0); /* stdin */
- dup2(i, 1); /* stderr */
- dup2(i, 2); /* stderr */
- close(i);
+ dup2(devNull, 0); /* stdin */
+ dup2(devNull, 1); /* stderr */
+ dup2(devNull, 2); /* stderr */
+ close(devNull);
}
}
-static void termIntHandler(int)
+static void termIntHandler([[maybe_unused]] int arg)
{
doExit();
}
-static void usr1Handler(int)
+static void usr1Handler([[maybe_unused]] int arg)
{
statsWanted = true;
}
-static void usr2Handler(int)
+static void usr2Handler([[maybe_unused]] int arg)
{
g_quiet = !g_quiet;
SyncRes::setDefaultLogMode(g_quiet ? SyncRes::LogNone : SyncRes::Log);
- ::arg().set("quiet") = g_quiet ? "" : "no";
+ ::arg().set("quiet") = g_quiet ? "yes" : "no";
}
static void checkLinuxIPv6Limits([[maybe_unused]] Logr::log_t log)
static void checkOrFixFDS(Logr::log_t log)
{
unsigned int availFDs = getFilenumLimit();
- unsigned int wantFDs = g_maxMThreads * RecThreadInfo::numWorkers() + 25; // even healthier margin then before
- wantFDs += RecThreadInfo::numWorkers() * TCPOutConnectionManager::s_maxIdlePerThread;
+ unsigned int wantFDs = g_maxMThreads * (RecThreadInfo::numUDPWorkers() + RecThreadInfo::numTCPWorkers()) + 25; // even healthier margin than before
+ wantFDs += (RecThreadInfo::numUDPWorkers() + RecThreadInfo::numTCPWorkers()) * TCPOutConnectionManager::s_maxIdlePerThread;
if (wantFDs > availFDs) {
unsigned int hardlimit = getFilenumLimit(true);
log->info(Logr::Warning, "Raised soft limit on number of filedescriptors to match max-mthreads and threads settings", "limit", Logging::Loggable(wantFDs)));
}
else {
- int newval = (hardlimit - 25 - TCPOutConnectionManager::s_maxIdlePerThread) / RecThreadInfo::numWorkers();
+ auto newval = (hardlimit - 25 - TCPOutConnectionManager::s_maxIdlePerThread) / (RecThreadInfo::numUDPWorkers() + RecThreadInfo::numTCPWorkers());
SLOG(g_log << Logger::Warning << "Insufficient number of filedescriptors available for max-mthreads*threads setting! (" << hardlimit << " < " << wantFDs << "), reducing max-mthreads to " << newval << endl,
log->info(Logr::Warning, "Insufficient number of filedescriptors available for max-mthreads*threads setting! Reducing max-mthreads", "hardlimit", Logging::Loggable(hardlimit), "want", Logging::Loggable(wantFDs), "max-mthreads", Logging::Loggable(newval)));
g_maxMThreads = newval;
// static std::string s_timestampFormat = "%m-%dT%H:%M:%S";
static std::string s_timestampFormat = "%s";
-static const char* toTimestampStringMilli(const struct timeval& tv, char* buf, size_t sz)
+static const char* toTimestampStringMilli(const struct timeval& tval, std::array<char, 64>& buf)
{
size_t len = 0;
if (s_timestampFormat != "%s") {
// strftime is not thread safe, it can access locale information
- static std::mutex m;
- auto lock = std::lock_guard(m);
- struct tm tm;
- len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&tv.tv_sec, &tm));
+ static std::mutex mutex;
+ auto lock = std::lock_guard(mutex);
+ struct tm theTime // clang-format insists on formatting it like this
+ {
+ };
+ len = strftime(buf.data(), buf.size(), s_timestampFormat.c_str(), localtime_r(&tval.tv_sec, &theTime));
}
if (len == 0) {
- len = snprintf(buf, sz, "%lld", static_cast<long long>(tv.tv_sec));
+ len = snprintf(buf.data(), buf.size(), "%lld", static_cast<long long>(tval.tv_sec));
}
- snprintf(buf + len, sz - len, ".%03ld", static_cast<long>(tv.tv_usec) / 1000);
- return buf;
+ snprintf(&buf.at(len), buf.size() - len, ".%03ld", static_cast<long>(tval.tv_usec) / 1000);
+ return buf.data();
}
#ifdef HAVE_SYSTEMD
static void loggerSDBackend(const Logging::Entry& entry)
{
+ static const set<std::string, CIStringComparePOSIX> special = {
+ "message",
+ "message_id",
+ "priority",
+ "code_file",
+ "code_line",
+ "code_func",
+ "errno",
+ "invocation_id",
+ "user_invocation_id",
+ "syslog_facility",
+ "syslog_identifier",
+ "syslog_pid",
+ "syslog_timestamp",
+ "syslog_raw",
+ "documentation",
+ "tid",
+ "unit",
+ "user_unit",
+ "object_pid"};
+
// First map SL priority to syslog's Urgency
- Logger::Urgency u = entry.d_priority ? Logger::Urgency(entry.d_priority) : Logger::Info;
- if (u > s_logUrgency) {
+ Logger::Urgency urgency = entry.d_priority != 0 ? Logger::Urgency(entry.d_priority) : Logger::Info;
+ if (urgency > s_logUrgency) {
// We do not log anything if the Urgency of the message is lower than the requested loglevel.
// Not that lower Urgency means higher number.
return;
}
// We need to keep the string in mem until sd_journal_sendv has ben called
vector<string> strings;
- auto appendKeyAndVal = [&strings](const string& k, const string& v) {
- strings.emplace_back(k + "=" + v);
+ auto appendKeyAndVal = [&strings](const string& key, const string& value) {
+ strings.emplace_back(key + "=" + value);
};
appendKeyAndVal("MESSAGE", entry.message);
if (entry.error) {
if (entry.name) {
appendKeyAndVal("SUBSYSTEM", entry.name.get());
}
- char timebuf[64];
- appendKeyAndVal("TIMESTAMP", toTimestampStringMilli(entry.d_timestamp, timebuf, sizeof(timebuf)));
- for (auto const& v : entry.values) {
- appendKeyAndVal(toUpper(v.first), v.second);
+ std::array<char, 64> timebuf{};
+ appendKeyAndVal("TIMESTAMP", toTimestampStringMilli(entry.d_timestamp, timebuf));
+ for (const auto& value : entry.values) {
+ if (value.first.at(0) == '_' || special.count(value.first) != 0) {
+ string key{"PDNS"};
+ key.append(value.first);
+ appendKeyAndVal(toUpper(key), value.second);
+ }
+ else {
+ appendKeyAndVal(toUpper(value.first), value.second);
+ }
}
// Thread id filled in by backend, since the SL code does not know about RecursorThreads
// We use the Recursor thread, other threads get id 0. May need to revisit.
vector<iovec> iov;
iov.reserve(strings.size());
- for (const auto& s : strings) {
+ for (const auto& str : strings) {
// iovec has no 2 arg constructor, so make it explicit
- iov.emplace_back(iovec{const_cast<void*>(reinterpret_cast<const void*>(s.data())), s.size()});
+ iov.emplace_back(iovec{const_cast<void*>(reinterpret_cast<const void*>(str.data())), str.size()}); // NOLINT: it's the API
}
sd_journal_sendv(iov.data(), static_cast<int>(iov.size()));
}
#endif
+static void loggerJSONBackend(const Logging::Entry& entry)
+{
+ // First map SL priority to syslog's Urgency
+ Logger::Urgency urg = entry.d_priority != 0 ? Logger::Urgency(entry.d_priority) : Logger::Info;
+ if (urg > s_logUrgency) {
+ // We do not log anything if the Urgency of the message is lower than the requested loglevel.
+ // Not that lower Urgency means higher number.
+ return;
+ }
+
+ std::array<char, 64> timebuf{};
+ json11::Json::object json = {
+ {"msg", entry.message},
+ {"level", std::to_string(entry.level)},
+ // Thread id filled in by backend, since the SL code does not know about RecursorThreads
+ // We use the Recursor thread, other threads get id 0. May need to revisit.
+ {"tid", std::to_string(RecThreadInfo::id())},
+ {"ts", toTimestampStringMilli(entry.d_timestamp, timebuf)},
+ };
+
+ if (entry.error) {
+ json.emplace("error", entry.error.get());
+ }
+
+ if (entry.name) {
+ json.emplace("subsystem", entry.name.get());
+ }
+
+ if (entry.d_priority != 0) {
+ json.emplace("priority", std::to_string(entry.d_priority));
+ }
+
+ for (auto const& value : entry.values) {
+ json.emplace(value.first, value.second);
+ }
+
+ static thread_local std::string out;
+ out.clear();
+ json11::Json doc(std::move(json));
+ doc.dump(out);
+ cerr << out << endl;
+}
+
static void loggerBackend(const Logging::Entry& entry)
{
static thread_local std::stringstream buf;
// First map SL priority to syslog's Urgency
- Logger::Urgency u = entry.d_priority ? Logger::Urgency(entry.d_priority) : Logger::Info;
- if (u > s_logUrgency) {
+ Logger::Urgency urg = entry.d_priority != 0 ? Logger::Urgency(entry.d_priority) : Logger::Info;
+ if (urg > s_logUrgency) {
// We do not log anything if the Urgency of the message is lower than the requested loglevel.
// Not that lower Urgency means higher number.
return;
buf << " subsystem=" << std::quoted(entry.name.get());
}
buf << " level=" << std::quoted(std::to_string(entry.level));
- if (entry.d_priority) {
+ if (entry.d_priority != 0) {
buf << " prio=" << std::quoted(Logr::Logger::toString(entry.d_priority));
}
// Thread id filled in by backend, since the SL code does not know about RecursorThreads
// We use the Recursor thread, other threads get id 0. May need to revisit.
buf << " tid=" << std::quoted(std::to_string(RecThreadInfo::id()));
- char timebuf[64];
- buf << " ts=" << std::quoted(toTimestampStringMilli(entry.d_timestamp, timebuf, sizeof(timebuf)));
- for (auto const& v : entry.values) {
+ std::array<char, 64> timebuf{};
+ buf << " ts=" << std::quoted(toTimestampStringMilli(entry.d_timestamp, timebuf));
+ for (auto const& value : entry.values) {
buf << " ";
- buf << v.first << "=" << std::quoted(v.second);
+ buf << value.first << "=" << std::quoted(value.second);
}
- g_log << u << buf.str() << endl;
+ g_log << urg << buf.str() << endl;
}
static int ratePercentage(uint64_t nom, uint64_t denom)
if (denom == 0) {
return 0;
}
- return round(100.0 * nom / denom);
+ return static_cast<int>(round(100.0 * static_cast<double>(nom) / static_cast<double>(denom)));
}
-static void doStats(void)
+static void doStats()
{
static time_t lastOutputTime;
static uint64_t lastQueryCount;
- uint64_t cacheHits = g_recCache->cacheHits;
- uint64_t cacheMisses = g_recCache->cacheMisses;
+ uint64_t cacheHits = g_recCache->getCacheHits();
+ uint64_t cacheMisses = g_recCache->getCacheMisses();
uint64_t cacheSize = g_recCache->size();
auto rc_stats = g_recCache->stats();
auto pc_stats = g_packetCache ? g_packetCache->stats() : std::pair<uint64_t, uint64_t>{0, 0};
- double rrc = rc_stats.second == 0 ? 0.0 : (100.0 * rc_stats.first / rc_stats.second);
- double rpc = pc_stats.second == 0 ? 0.0 : (100.0 * pc_stats.first / pc_stats.second);
+ double rrc = ratePercentage(rc_stats.first, rc_stats.second);
+ double rpc = ratePercentage(pc_stats.first, pc_stats.second);
uint64_t negCacheSize = g_negCache->size();
auto taskPushes = getTaskPushes();
auto taskExpired = getTaskExpired();
g_log << Logger::Notice << "stats: tasks pushed/expired/queuesize: " << taskPushes << '/' << taskExpired << '/' << taskSize << endl;
}
else {
- const string m = "Periodic statistics report";
- log->info(Logr::Info, m,
+ const string report = "Periodic statistics report";
+ log->info(Logr::Info, report,
"questions", Logging::Loggable(qcounter),
"cache-entries", Logging::Loggable(cacheSize),
"negcache-entries", Logging::Loggable(negCacheSize),
"packetcache-contended", Logging::Loggable(pc_stats.first),
"packetcache-acquired", Logging::Loggable(pc_stats.second),
"packetcache-contended-perc", Logging::Loggable(rpc));
- log->info(Logr::Info, m,
+ log->info(Logr::Info, report,
"throttle-entries", Logging::Loggable(SyncRes::getThrottledServersSize()),
"nsspeed-entries", Logging::Loggable(SyncRes::getNSSpeedsSize()),
"failed-host-entries", Logging::Loggable(SyncRes::getFailedServersSize()),
"non-resolving-nameserver-entries", Logging::Loggable(SyncRes::getNonResolvingNSSize()),
"saved-parent-ns-sets-entries", Logging::Loggable(SyncRes::getSaveParentsNSSetsSize()),
"outqueries-per-query", Logging::Loggable(ratePercentage(outqueries, syncresqueries)));
- log->info(Logr::Info, m,
+ log->info(Logr::Info, report,
"throttled-queries-perc", Logging::Loggable(ratePercentage(throttledqueries, outqueries + throttledqueries)),
"tcp-outqueries", Logging::Loggable(tcpoutqueries),
"dot-outqueries", Logging::Loggable(dotoutqueries),
"idle-tcpout-connections", Logging::Loggable(getCurrentIdleTCPConnections()),
"concurrent-queries", Logging::Loggable(broadcastAccFunction<uint64_t>(pleaseGetConcurrentQueries)),
"outgoing-timeouts", Logging::Loggable(outgoingtimeouts));
- log->info(Logr::Info, m,
+ log->info(Logr::Info, report,
"packetcache-entries", Logging::Loggable(pcSize),
"packetcache-hitratio-perc", Logging::Loggable(ratePercentage(pcHits, qcounter)),
"taskqueue-pushed", Logging::Loggable(taskPushes),
size_t idx = 0;
for (const auto& threadInfo : RecThreadInfo::infos()) {
if (threadInfo.isWorker()) {
- SLOG(g_log << Logger::Notice << "stats: thread " << idx << " has been distributed " << threadInfo.numberOfDistributedQueries << " queries" << endl,
- log->info(Logr::Info, "Queries handled by thread", "thread", Logging::Loggable(idx), "count", Logging::Loggable(threadInfo.numberOfDistributedQueries)));
+ SLOG(g_log << Logger::Notice << "stats: thread " << idx << " has been distributed " << threadInfo.getNumberOfDistributedQueries() << " queries" << endl,
+ log->info(Logr::Info, "Queries handled by thread", "thread", Logging::Loggable(idx), "tname", Logging::Loggable(threadInfo.getName()), "count", Logging::Loggable(threadInfo.getNumberOfDistributedQueries())));
++idx;
}
}
- time_t now = time(0);
- if (lastOutputTime && lastQueryCount && now != lastOutputTime) {
+ time_t now = time(nullptr);
+ if (lastOutputTime != 0 && lastQueryCount != 0 && now != lastOutputTime) {
SLOG(g_log << Logger::Notice << "stats: " << (qcounter - lastQueryCount) / (now - lastOutputTime) << " qps (average over " << (now - lastOutputTime) << " seconds)" << endl,
log->info(Logr::Info, "Periodic QPS report", "qps", Logging::Loggable((qcounter - lastQueryCount) / (now - lastOutputTime)),
"averagedOver", Logging::Loggable(now - lastOutputTime)));
{
auto result = std::make_shared<NetmaskGroup>();
- if (!::arg()[aclFile].empty()) {
- string line;
- ifstream ifs(::arg()[aclFile].c_str());
- if (!ifs) {
- throw runtime_error("Could not open '" + ::arg()[aclFile] + "': " + stringerror());
+ const string file = ::arg()[aclFile];
+
+ if (!file.empty()) {
+ if (boost::ends_with(file, ".yml")) {
+ ::rust::vec<::rust::string> vec;
+ pdns::settings::rec::readYamlAllowFromFile(file, vec, log);
+ for (const auto& subnet : vec) {
+ result->addMask(string(subnet));
+ }
}
+ else {
+ string line;
+ ifstream ifs(file);
+ if (!ifs) {
+ int err = errno;
+ throw runtime_error("Could not open '" + file + "': " + stringerror(err));
+ }
- string::size_type pos;
- while (getline(ifs, line)) {
- pos = line.find('#');
- if (pos != string::npos)
- line.resize(pos);
- boost::trim(line);
- if (line.empty())
- continue;
+ while (getline(ifs, line)) {
+ auto pos = line.find('#');
+ if (pos != string::npos) {
+ line.resize(pos);
+ }
+ boost::trim(line);
+ if (line.empty()) {
+ continue;
+ }
- result->addMask(line);
+ result->addMask(line);
+ }
}
- SLOG(g_log << Logger::Info << "Done parsing " << result->size() << " " << aclSetting << " ranges from file '" << ::arg()[aclFile] << "' - overriding '" << aclSetting << "' setting" << endl,
+ SLOG(g_log << Logger::Info << "Done parsing " << result->size() << " " << aclSetting << " ranges from file '" << file << "' - overriding '" << aclSetting << "' setting" << endl,
log->info(Logr::Info, "Done parsing ranges from file, will override setting", "setting", Logging::Loggable(aclSetting),
- "number", Logging::Loggable(result->size()), "file", Logging::Loggable(::arg()[aclFile])));
+ "number", Logging::Loggable(result->size()), "file", Logging::Loggable(file)));
}
else if (!::arg()[aclSetting].empty()) {
vector<string> ips;
stringtok(ips, ::arg()[aclSetting], ", ");
- for (const auto& i : ips) {
- result->addMask(i);
+ for (const auto& address : ips) {
+ result->addMask(address);
}
if (!g_slogStructured) {
g_log << Logger::Info << aclSetting << ": ";
- 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::Info << ", ";
+ }
g_log << Logger::Info << *i;
}
g_log << Logger::Info << endl;
return result;
}
-static void* pleaseSupplantAllowFrom(std::shared_ptr<NetmaskGroup> ng)
+static void* pleaseSupplantAllowFrom(std::shared_ptr<NetmaskGroup> nmgroup)
{
- t_allowFrom = ng;
+ t_allowFrom = std::move(nmgroup);
return nullptr;
}
-static void* pleaseSupplantAllowNotifyFrom(std::shared_ptr<NetmaskGroup> ng)
+static void* pleaseSupplantAllowNotifyFrom(std::shared_ptr<NetmaskGroup> nmgroup)
{
- t_allowNotifyFrom = ng;
+ t_allowNotifyFrom = std::move(nmgroup);
return nullptr;
}
-void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> ns)
+void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> allowNotifyFor)
{
- t_allowNotifyFor = ns;
+ t_allowNotifyFor = std::move(allowNotifyFor);
return nullptr;
}
static bool l_initialized;
if (l_initialized) { // only reload configuration file on second call
- string configName = ::arg()["config-dir"] + "/recursor.conf";
+
+ string configName = ::arg()["config-dir"] + "/recursor";
if (!::arg()["config-name"].empty()) {
- configName = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+ configName = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
}
cleanSlashes(configName);
- if (!::arg().preParseFile(configName.c_str(), "allow-from-file")) {
- throw runtime_error("Unable to re-parse configuration file '" + configName + "'");
+ if (g_yamlSettings) {
+ configName += ".yml";
+ string msg;
+ pdns::rust::settings::rec::Recursorsettings settings;
+ // XXX Does ::arg()["include-dir"] have the right value, i.e. potentially overriden by command line?
+ auto yamlstatus = pdns::settings::rec::readYamlSettings(configName, ::arg()["include-dir"], settings, msg, log);
+
+ switch (yamlstatus) {
+ case pdns::settings::rec::YamlSettingsStatus::CannotOpen:
+ throw runtime_error("Unable to open '" + configName + "': " + msg);
+ break;
+ case pdns::settings::rec::YamlSettingsStatus::PresentButFailed:
+ throw runtime_error("Error processing '" + configName + "': " + msg);
+ break;
+ case pdns::settings::rec::YamlSettingsStatus::OK:
+ pdns::settings::rec::processAPIDir(arg()["include-dir"], settings, log);
+ // Does *not* set include-dir
+ pdns::settings::rec::setArgsForACLRelatedSettings(settings);
+ break;
+ }
}
- ::arg().preParseFile(configName.c_str(), "allow-from", LOCAL_NETS);
+ else {
+ configName += ".conf";
+ if (!::arg().preParseFile(configName, "allow-from-file")) {
+ throw runtime_error("Unable to re-parse configuration file '" + configName + "'");
+ }
+ ::arg().preParseFile(configName, "allow-from", LOCAL_NETS);
- if (!::arg().preParseFile(configName.c_str(), "allow-notify-from-file")) {
- throw runtime_error("Unable to re-parse configuration file '" + configName + "'");
- }
- ::arg().preParseFile(configName.c_str(), "allow-notify-from");
+ if (!::arg().preParseFile(configName, "allow-notify-from-file")) {
+ throw runtime_error("Unable to re-parse configuration file '" + configName + "'");
+ }
+ ::arg().preParseFile(configName, "allow-notify-from");
- ::arg().preParseFile(configName.c_str(), "include-dir");
- ::arg().preParse(g_argc, g_argv, "include-dir");
+ ::arg().preParseFile(configName, "include-dir");
+ ::arg().preParse(g_argc, g_argv, "include-dir");
- // then process includes
- std::vector<std::string> extraConfigs;
- ::arg().gatherIncludes(extraConfigs);
+ // then process includes
+ std::vector<std::string> extraConfigs;
+ ::arg().gatherIncludes(::arg()["include-dir"], ".conf", extraConfigs);
- for (const std::string& fileName : extraConfigs) {
- if (!::arg().preParseFile(fileName.c_str(), "allow-from-file", ::arg()["allow-from-file"])) {
- throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
- }
- if (!::arg().preParseFile(fileName.c_str(), "allow-from", ::arg()["allow-from"])) {
- throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
- }
+ for (const std::string& fileName : extraConfigs) {
+ if (!::arg().preParseFile(fileName, "allow-from-file", ::arg()["allow-from-file"])) {
+ throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+ }
+ if (!::arg().preParseFile(fileName, "allow-from", ::arg()["allow-from"])) {
+ throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+ }
- if (!::arg().preParseFile(fileName.c_str(), "allow-notify-from-file", ::arg()["allow-notify-from-file"])) {
- throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
- }
- if (!::arg().preParseFile(fileName.c_str(), "allow-notify-from", ::arg()["allow-notify-from"])) {
- throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+ if (!::arg().preParseFile(fileName, "allow-notify-from-file", ::arg()["allow-notify-from-file"])) {
+ throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+ }
+ if (!::arg().preParseFile(fileName, "allow-notify-from", ::arg()["allow-notify-from"])) {
+ throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+ }
}
}
-
- ::arg().preParse(g_argc, g_argv, "allow-from-file");
- ::arg().preParse(g_argc, g_argv, "allow-from");
-
- ::arg().preParse(g_argc, g_argv, "allow-notify-from-file");
- ::arg().preParse(g_argc, g_argv, "allow-notify-from");
}
+ // Process command line args potentially overriding settings read from file
+ ::arg().preParse(g_argc, g_argv, "allow-from-file");
+ ::arg().preParse(g_argc, g_argv, "allow-from");
+
+ ::arg().preParse(g_argc, g_argv, "allow-notify-from-file");
+ ::arg().preParse(g_argc, g_argv, "allow-notify-from");
auto allowFrom = parseACL("allow-from-file", "allow-from", log);
}
g_initialAllowFrom = allowFrom;
+ // coverity[copy_constructor_call] maybe this can be avoided, but be careful as pointers get passed to other threads
broadcastFunction([=] { return pleaseSupplantAllowFrom(allowFrom); });
auto allowNotifyFrom = parseACL("allow-notify-from-file", "allow-notify-from", log);
g_initialAllowNotifyFrom = allowNotifyFrom;
+ // coverity[copy_constructor_call] maybe this can be avoided, but be careful as pointers get passed to other threads
broadcastFunction([=] { return pleaseSupplantAllowNotifyFrom(allowNotifyFrom); });
l_initialized = true;
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.getPipes().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.getPipes().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]
return func();
}
-static vector<ComboAddress>& operator+=(vector<ComboAddress>& a, const vector<ComboAddress>& b)
+static vector<ComboAddress>& operator+=(vector<ComboAddress>& lhs, const vector<ComboAddress>& rhs)
{
- a.insert(a.end(), b.begin(), b.end());
- return a;
+ lhs.insert(lhs.end(), rhs.begin(), rhs.end());
+ return lhs;
}
-static vector<pair<DNSName, uint16_t>>& operator+=(vector<pair<DNSName, uint16_t>>& a, const vector<pair<DNSName, uint16_t>>& b)
+static vector<pair<DNSName, uint16_t>>& operator+=(vector<pair<DNSName, uint16_t>>& lhs, const vector<pair<DNSName, uint16_t>>& rhs)
{
- a.insert(a.end(), b.begin(), b.end());
- return a;
+ lhs.insert(lhs.end(), rhs.begin(), rhs.end());
+ return lhs;
}
-static ProxyMappingStats_t& operator+=(ProxyMappingStats_t& a, const ProxyMappingStats_t& b)
+static ProxyMappingStats_t& operator+=(ProxyMappingStats_t& lhs, const ProxyMappingStats_t& rhs)
{
- for (const auto& [key, entry] : b) {
- a[key].netmaskMatches += entry.netmaskMatches;
- a[key].suffixMatches += entry.suffixMatches;
+ for (const auto& [key, entry] : rhs) {
+ lhs[key].netmaskMatches += entry.netmaskMatches;
+ lhs[key].suffixMatches += entry.suffixMatches;
}
- return a;
+ return lhs;
}
-static RemoteLoggerStats_t& operator+=(RemoteLoggerStats_t& a, const RemoteLoggerStats_t& b)
+static RemoteLoggerStats_t& operator+=(RemoteLoggerStats_t& lhs, const RemoteLoggerStats_t& rhs)
{
- for (const auto& [key, entry] : b) {
- a[key] += entry;
+ for (const auto& [key, entry] : rhs) {
+ lhs[key] += entry;
}
- return a;
+ return lhs;
}
// This function should only be called by the handler to gather
_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();
+ const auto& tps = threadInfo.getPipes();
+ 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]
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"]);
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)) {
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");
+ g_maxRRSIGsPerRecordToConsider = ::arg().asNum("max-rrsigs-per-record");
+ g_maxNSEC3sPerRecordToConsider = ::arg().asNum("max-nsec3s-per-record");
+ g_maxDNSKEYsToConsider = ::arg().asNum("max-dnskeys");
+ g_maxDSsToConsider = ::arg().asNum("max-ds-per-zone");
- 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);
+ vector<string> nums;
+ bool automatic = true;
+ if (!::arg()["dnssec-disabled-algorithms"].empty()) {
+ automatic = false;
+ stringtok(nums, ::arg()["dnssec-disabled-algorithms"], ", ");
+ for (const auto& num : nums) {
+ DNSCryptoKeyEngine::switchOffAlgorithm(pdns::checked_stoi<unsigned int>(num));
+ }
}
- 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);
+ else {
+ for (auto algo : {DNSSECKeeper::RSASHA1, DNSSECKeeper::RSASHA1NSEC3SHA1}) {
+ if (!DNSCryptoKeyEngine::verifyOne(algo)) {
+ DNSCryptoKeyEngine::switchOffAlgorithm(algo);
+ nums.push_back(std::to_string(algo));
+ }
+ }
+ }
+ if (!nums.empty()) {
+ if (!g_slogStructured) {
+ g_log << Logger::Warning << (automatic ? "Automatically" : "Manually") << " disabled DNSSEC algorithms: ";
+ for (auto i = nums.begin(); i != nums.end(); ++i) {
+ if (i != nums.begin()) {
+ g_log << Logger::Warning << ", ";
+ }
+ g_log << Logger::Warning << *i;
+ }
+ g_log << Logger::Warning << endl;
+ }
+ else {
+ log->info(Logr::Notice, "Disabled DNSSEC algorithms", "automatically", Logging::Loggable(automatic), "algorithms", Logging::IterLoggable(nums.begin(), nums.end()));
+ }
}
- parseACLs();
- initPublicSuffixList(::arg()["public-suffix-list-file"]);
+ return 0;
+}
+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;
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)
+{
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");
SyncRes::s_serverdownmaxfails = ::arg().asNum("server-down-max-fails");
SyncRes::s_serverdownthrottletime = ::arg().asNum("server-down-throttle-time");
+ SyncRes::s_unthrottle_n = ::arg().asNum("bypass-server-throttling-probability");
SyncRes::s_nonresolvingnsmaxfails = ::arg().asNum("non-resolving-ns-max-fails");
SyncRes::s_nonresolvingnsthrottletime = ::arg().asNum("non-resolving-ns-throttle-time");
SyncRes::s_serverID = ::arg()["server-id"];
+ // This bound is dynamically adjusted in SyncRes, depending on qname minimization being active
SyncRes::s_maxqperq = ::arg().asNum("max-qperq");
SyncRes::s_maxnsperresolve = ::arg().asNum("max-ns-per-resolve");
SyncRes::s_maxnsaddressqperq = ::arg().asNum("max-ns-address-qperq");
SyncRes::s_maxtotusec = 1000 * ::arg().asNum("max-total-msec");
SyncRes::s_maxdepth = ::arg().asNum("max-recursion-depth");
+ SyncRes::s_maxvalidationsperq = ::arg().asNum("max-signature-validations-per-query");
+ SyncRes::s_maxnsec3iterationsperq = ::arg().asNum("max-nsec3-hash-computations-per-query");
SyncRes::s_rootNXTrust = ::arg().mustDo("root-nx-trust");
SyncRes::s_refresh_ttlperc = ::arg().asNum("refresh-on-ttl-perc");
SyncRes::s_locked_ttlperc = ::arg().asNum("record-cache-locked-ttl-perc");
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;
checkFastOpenSysctl(true, log);
checkTFOconnect(log);
}
-
- if (SyncRes::s_serverID.empty()) {
- SyncRes::s_serverID = myHostname.has_value() ? *myHostname : "";
- }
-
SyncRes::s_ecsipv4limit = ::arg().asNum("ecs-ipv4-bits");
SyncRes::s_ecsipv6limit = ::arg().asNum("ecs-ipv6-bits");
SyncRes::clearECSStats();
SyncRes::s_ecscachelimitttl = ::arg().asNum("ecs-cache-limit-ttl");
SyncRes::s_qnameminimization = ::arg().mustDo("qname-minimization");
-
- if (SyncRes::s_qnameminimization) {
- // With an empty cache, a rev ipv6 query with dnssec enabled takes
- // almost 100 queries. Default maxqperq is 60.
- SyncRes::s_maxqperq = std::max(SyncRes::s_maxqperq, static_cast<unsigned int>(100));
- }
+ SyncRes::s_minimize_one_label = ::arg().asNum("qname-minimize-one-label");
+ SyncRes::s_max_minimize_count = ::arg().asNum("qname-max-minimize-count");
SyncRes::s_hardenNXD = SyncRes::HardenNXD::DNSSEC;
string value = ::arg()["nothing-below-nxdomain"];
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")) {
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);
+ netmask = Netmask(addr, 32);
done = true;
}
if (!done) {
addr = pdns::getNonAnyQueryLocalAddress(AF_INET6);
if (addr.sin4.sin_family != 0) {
- nm = Netmask(addr, 128);
+ 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");
+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;
+ SLOG(g_log << Logger::Warning << "Asked to run with a distribution-load-factor below 1.0, disabling it instead" << endl,
+ log->info(Logr::Warning, "Asked to run with a distribution-load-factor below 1.0, disabling it instead"));
+ }
- 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);
+#ifdef SO_REUSEPORT
+ g_reusePort = ::arg().mustDo("reuseport");
+#endif
+
+ RecThreadInfo::infos().resize(RecThreadInfo::numRecursorThreads());
+
+ if (g_reusePort) {
+ unsigned int threadNum = 1;
+ if (RecThreadInfo::weDistributeQueries()) {
+ /* first thread is the handler, then distributors */
+ for (unsigned int i = 0; i < RecThreadInfo::numDistributors(); i++, threadNum++) {
+ auto& info = RecThreadInfo::info(threadNum);
+ auto& deferredAdds = info.getDeferredAdds();
+ makeUDPServerSockets(deferredAdds, log);
}
- 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();
+ }
+ else {
+ /* first thread is the handler, there is no distributor here and workers are accepting queries */
+ for (unsigned int i = 0; i < RecThreadInfo::numUDPWorkers(); i++, threadNum++) {
+ auto& info = RecThreadInfo::info(threadNum);
+ auto& deferredAdds = info.getDeferredAdds();
+ makeUDPServerSockets(deferredAdds, log);
}
}
- 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);
+ threadNum = 1 + RecThreadInfo::numDistributors() + RecThreadInfo::numUDPWorkers();
+ for (unsigned int i = 0; i < RecThreadInfo::numTCPWorkers(); i++, threadNum++) {
+ auto& info = RecThreadInfo::info(threadNum);
+ auto& deferredAdds = info.getDeferredAdds();
+ auto& tcpSockets = info.getTCPSockets();
+ makeTCPServerSockets(deferredAdds, tcpSockets, log);
}
}
+ else {
+ std::set<int> tcpSockets;
+ /* we don't have reuseport so we can only open one socket per
+ listening addr:port and everyone will listen on it */
+ makeUDPServerSockets(s_deferredUDPadds, log);
+ makeTCPServerSockets(s_deferredTCPadds, tcpSockets, log);
- g_networkTimeoutMsec = ::arg().asNum("network-timeout");
+ // TCP queries are handled by TCP workers
+ for (unsigned int i = 0; i < RecThreadInfo::numTCPWorkers(); i++) {
+ auto& info = RecThreadInfo::info(i + 1 + RecThreadInfo::numDistributors() + RecThreadInfo::numUDPWorkers());
+ info.setTCPSockets(tcpSockets);
+ }
+ }
+}
- std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration();
+static int initForks(Logr::log_t log)
+{
+ int forks = 0;
+ for (; forks < ::arg().asNum("processes") - 1; ++forks) {
+ if (fork() == 0) { // we are child
+ break;
+ }
+ }
- g_latencyStatSize = ::arg().asNum("latency-statistic-size");
+ if (::arg().mustDo("daemon")) {
+ SLOG(g_log << Logger::Warning << "Calling daemonize, going to background" << endl,
+ log->info(Logr::Warning, "Calling daemonize, going to background"));
+ g_log.toConsole(Logger::Critical);
+ daemonize(log);
+ }
- g_logCommonErrors = ::arg().mustDo("log-common-errors");
- g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
+ if (Utility::getpid() == 1) {
+ /* We are running as pid 1, register sigterm and sigint handler
- g_anyToTcp = ::arg().mustDo("any-to-tcp");
- g_udpTruncationThreshold = ::arg().asNum("udp-truncation-threshold");
+ The Linux kernel will handle SIGTERM and SIGINT for all processes, except PID 1.
+ It assumes that the processes running as pid 1 is an "init" like system.
+ For years, this was a safe assumption, but containers change that: in
+ most (all?) container implementations, the application itself is running
+ as pid 1. This means that sending signals to those applications, will not
+ be handled by default. Results might be "your container not responding
+ when asking it to stop", or "ctrl-c not working even when the app is
+ running in the foreground inside a container".
- g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
+ So TL;DR: If we're running pid 1 (container), we should handle SIGTERM and SIGINT ourselves */
- 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);
+ signal(SIGTERM, termIntHandler);
+ signal(SIGINT, termIntHandler);
}
- 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");
+ signal(SIGUSR1, usr1Handler);
+ signal(SIGUSR2, usr2Handler);
+ signal(SIGPIPE, SIG_IGN); // NOLINT: Posix API
+ return forks;
+}
- 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;
+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
}
- else {
- TCPConnection::s_maxInFlight = maxInFlight;
+ 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
}
+ 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;
+}
- 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");
+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
+ }
+}
- 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"));
+static int initControl(Logr::log_t log, uid_t newuid, int forks)
+{
+ 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'"));
+ return 1;
}
- 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"));
+#endif
+ if (chroot(::arg()["chroot"].c_str()) < 0 || chdir("/") < 0) {
+ int err = errno;
+ 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"])));
+ return 1;
}
+ SLOG(g_log << Logger::Info << "Chrooted to '" << ::arg()["chroot"] << "'" << endl,
+ log->info(Logr::Info, "Chrooted", "chroot", Logging::Loggable(::arg()["chroot"])));
}
- 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)));
+ checkSocketDir(log);
+
+ g_pidfname = ::arg()["socket-dir"] + "/" + g_programname + ".pid";
+ if (!g_pidfname.empty()) {
+ unlink(g_pidfname.c_str()); // remove possible old pid file
+ }
+ writePid(log);
+
+ makeControlChannelSocket(::arg().asNum("processes") > 1 ? forks : -1);
+ Utility::dropUserPrivs(newuid);
+ try {
+ /* we might still have capabilities remaining, for example if we have been started as root
+ without --setuid (please don't do that) or as an unprivileged user with ambient capabilities
+ like CAP_NET_BIND_SERVICE.
+ */
+ dropCapabilities();
+ }
+ catch (const std::exception& e) {
+ 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& p : parts) {
- dontThrottleNames.add(DNSName(p));
+ 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& p : parts) {
- dontThrottleNetmasks.addMask(Netmask(p));
- }
+ dontThrottleNetmasks.toMasks(::arg()["dont-throttle-netmasks"]);
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));
+ for (const auto& part : parts) {
+ xdnssecNames.add(DNSName(part));
}
g_xdnssec.setState(std::move(xdnssecNames));
}
vector<string> parts;
stringtok(parts, ::arg()["dot-to-auth-names"], " ,");
#ifndef HAVE_DNS_OVER_TLS
- if (parts.size()) {
+ if (!parts.empty()) {
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));
+ for (const auto& part : parts) {
+ dotauthNames.add(DNSName(part));
}
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));
- }
-
- g_balancingFactor = ::arg().asDouble("distribution-load-factor");
- if (g_balancingFactor != 0.0 && g_balancingFactor < 1.0) {
- g_balancingFactor = 0.0;
- SLOG(g_log << Logger::Warning << "Asked to run with a distribution-load-factor below 1.0, disabling it instead" << endl,
- log->info(Logr::Warning, "Asked to run with a distribution-load-factor below 1.0, disabling it instead"));
- }
-
-#ifdef SO_REUSEPORT
- g_reusePort = ::arg().mustDo("reuseport");
-#endif
-
- RecThreadInfo::infos().resize(RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers() + RecThreadInfo::numTaskThreads());
+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));
+}
- if (g_reusePort) {
- if (RecThreadInfo::weDistributeQueries()) {
- /* first thread is the handler, then distributors */
- for (unsigned int threadId = 1; threadId <= RecThreadInfo::numDistributors(); threadId++) {
- auto& info = RecThreadInfo::info(threadId);
- auto& deferredAdds = info.deferredAdds;
- auto& tcpSockets = info.tcpSockets;
- makeUDPServerSockets(deferredAdds, log);
- makeTCPServerSockets(deferredAdds, tcpSockets, log);
+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;
}
- }
- else {
- /* first thread is the handler, there is no distributor here and workers are accepting queries */
- for (unsigned int threadId = 1; threadId <= RecThreadInfo::numWorkers(); threadId++) {
- auto& info = RecThreadInfo::info(threadId);
- auto& deferredAdds = info.deferredAdds;
- auto& tcpSockets = info.tcpSockets;
- makeUDPServerSockets(deferredAdds, log);
- makeTCPServerSockets(deferredAdds, tcpSockets, log);
+ 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;
+ }
}
- else {
- std::set<int> tcpSockets;
- /* we don't have reuseport so we can only open one socket per
- listening addr:port and everyone will listen on it */
- makeUDPServerSockets(g_deferredAdds, log);
- makeTCPServerSockets(g_deferredAdds, tcpSockets, log);
+ return 0;
+}
- /* every listener (so distributor if g_weDistributeQueries, workers otherwise)
- needs to listen to the shared sockets */
- if (RecThreadInfo::weDistributeQueries()) {
- /* first thread is the handler, then distributors */
- for (unsigned int threadId = 1; threadId <= RecThreadInfo::numDistributors(); threadId++) {
- RecThreadInfo::info(threadId).tcpSockets = tcpSockets;
- }
+static int serviceMain(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 {
- /* first thread is the handler, there is no distributor here and workers are accepting queries */
- for (unsigned int threadId = 1; threadId <= RecThreadInfo::numWorkers(); threadId++) {
- RecThreadInfo::info(threadId).tcpSockets = tcpSockets;
- }
+ 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"))));
}
}
-#ifdef NOD_ENABLED
- // Setup newly observed domain globals
- setupNODGlobal();
-#endif /* NOD_ENABLED */
+ g_disthashseed = dns_random_uint32();
- int forks;
- for (forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) {
- if (!fork()) // we are child
- break;
+ 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");
- if (::arg().mustDo("daemon")) {
- SLOG(g_log << Logger::Warning << "Calling daemonize, going to background" << endl,
- log->info(Logr::Warning, "Calling daemonize, going to background"));
- g_log.toConsole(Logger::Critical);
- daemonize(log);
+ 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;
}
- if (Utility::getpid() == 1) {
- /* We are running as pid 1, register sigterm and sigint handler
- The Linux kernel will handle SIGTERM and SIGINT for all processes, except PID 1.
- It assumes that the processes running as pid 1 is an "init" like system.
- For years, this was a safe assumption, but containers change that: in
- most (all?) container implementations, the application itself is running
- as pid 1. This means that sending signals to those applications, will not
- be handled by default. Results might be "your container not responding
- when asking it to stop", or "ctrl-c not working even when the app is
- running in the foreground inside a container".
+ parseACLs();
+ initPublicSuffixList(::arg()["public-suffix-list-file"]);
- So TL;DR: If we're running pid 1 (container), we should handle SIGTERM and SIGINT ourselves */
+ initDontQuery(log);
- signal(SIGTERM, termIntHandler);
- signal(SIGINT, termIntHandler);
+ 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"));
}
- signal(SIGUSR1, usr1Handler);
- signal(SIGUSR2, usr2Handler);
- signal(SIGPIPE, SIG_IGN);
+ g_outgoingEDNSBufsize = ::arg().asNum("edns-outgoing-bufsize");
- checkOrFixFDS(log);
+ if (::arg()["trace"] == "fail") {
+ SyncRes::setDefaultLogMode(SyncRes::Store);
+ }
+ else if (::arg().mustDo("trace")) {
+ SyncRes::setDefaultLogMode(SyncRes::Log);
+ ::arg().set("quiet") = "no";
+ g_quiet = false;
+ }
-#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);
+ ret = initSyncRes(log);
+ if (ret != 0) {
+ return ret;
}
-#endif
- openssl_thread_setup();
- openssl_seed();
- /* setup rng before chroot */
- dns_random_init();
+ g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+ g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
- if (::arg()["server-id"].empty()) {
- ::arg().set("server-id") = myHostname.has_value() ? *myHostname : "";
+ ret = initDNS64(log);
+ if (ret != 0) {
+ return ret;
}
+ g_networkTimeoutMsec = ::arg().asNum("network-timeout");
- int newgid = 0;
- if (!::arg()["setgid"].empty())
- newgid = strToGID(::arg()["setgid"]);
- int newuid = 0;
- if (!::arg()["setuid"].empty())
- newuid = strToUID(::arg()["setuid"]);
+ std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration(g_yamlSettings);
- Utility::dropGroupPrivs(newuid, newgid);
+ g_latencyStatSize = ::arg().asNum("latency-statistic-size");
- 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);
- }
-#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,
- log->error(Logr::Error, err, "Unable to chroot", "chroot", Logging::Loggable(::arg()["chroot"])));
- exit(1);
+ g_logCommonErrors = ::arg().mustDo("log-common-errors");
+ g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
+
+ g_anyToTcp = ::arg().mustDo("any-to-tcp");
+ g_allowNoRD = ::arg().mustDo("allow-no-rd");
+ 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::setNumUDPWorkerThreads(::arg().asNum("threads"));
+ if (RecThreadInfo::numUDPWorkers() < 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::setNumUDPWorkerThreads(1);
+ }
+ RecThreadInfo::setNumTCPWorkerThreads(::arg().asNum("tcp-threads"));
+ if (RecThreadInfo::numTCPWorkers() < 1) {
+ SLOG(g_log << Logger::Warning << "Asked to run with 0 TCP threads, raising to 1 instead" << endl,
+ log->info(Logr::Warning, "Asked to run with 0 TCP threads, raising to 1 instead"));
+ RecThreadInfo::setNumTCPWorkerThreads(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::Info << "Chrooted to '" << ::arg()["chroot"] << "'" << endl,
- log->info(Logr::Info, "Chrooted", "chroot", Logging::Loggable(::arg()["chroot"])));
+ 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"));
}
}
- checkSocketDir(log);
+ AggressiveNSECCache::s_nsec3DenialProofMaxCost = ::arg().asNum("aggressive-cache-max-nsec3-hash-cost");
+ 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)));
- g_pidfname = ::arg()["socket-dir"] + "/" + g_programname + ".pid";
- if (!g_pidfname.empty())
- unlink(g_pidfname.c_str()); // remove possible old pid file
- writePid(log);
+ initSuffixMatchNodes(log);
+ initCarbon();
+ initDistribution(log);
- makeControlChannelSocket(::arg().asNum("processes") > 1 ? forks : -1);
+#ifdef NOD_ENABLED
+ // Setup newly observed domain globals
+ setupNODGlobal();
+#endif /* NOD_ENABLED */
- Utility::dropUserPrivs(newuid);
- try {
- /* we might still have capabilities remaining, for example if we have been started as root
- without --setuid (please don't do that) or as an unprivileged user with ambient capabilities
- like CAP_NET_BIND_SERVICE.
- */
- dropCapabilities();
+ 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;
}
- catch (const std::exception& e) {
- SLOG(g_log << Logger::Warning << e.what() << endl,
- log->error(Logr::Warning, e.what(), "Could not drop capabilities"));
+#endif
+
+ openssl_thread_setup();
+ openssl_seed();
+
+ 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);
// 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
- }
+ initSNMP(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"));
- 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);
+ 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();
}
}
}
if (tmsg->wantAnswer) {
- if (write(RecThreadInfo::self().pipes.writeFromThread, &resp, sizeof(resp)) != sizeof(resp)) {
- delete tmsg;
+ if (write(RecThreadInfo::self().getPipes().writeFromThread, &resp, sizeof(resp)) != sizeof(resp)) {
+ 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 */)
+static void handleRCC(int fileDesc, FDMultiplexer::funcparam_t& /* var */)
{
auto log = g_slog->withName("control");
try {
- FDWrapper clientfd = accept(fd, nullptr, nullptr);
+ FDWrapper clientfd = accept(fileDesc, nullptr, nullptr);
if (clientfd == -1) {
throw PDNSException("accept failed");
}
SLOG(g_log << Logger::Info << "Received rec_control command '" << msg << "' via controlsocket" << endl,
log->info(Logr::Info, "Received rec_control command via control socket", "command", Logging::Loggable(msg)));
- RecursorControlParser rcp;
- RecursorControlParser::func_t* command;
- auto answer = rcp.getAnswer(clientfd, msg, &command);
+ RecursorControlParser::func_t* command = nullptr;
+ auto answer = RecursorControlParser::getAnswer(clientfd, msg, &command);
g_rcc.send(clientfd, answer);
command();
class PeriodicTask
{
public:
- PeriodicTask(const string& n, time_t p) :
- period{p, 0}, name(n)
+ PeriodicTask(const string& aName, time_t aTime) :
+ period{aTime, 0}, name(aName)
{
- if (p <= 0) {
- throw PDNSException("Invalid period of periodic task " + n);
+ if (aTime <= 0) {
+ throw PDNSException("Invalid period of periodic task " + aName);
}
}
- void runIfDue(struct timeval& now, const std::function<void()>& f)
+ void runIfDue(struct timeval& now, const std::function<void()>& function)
{
if (last_run < now - period) {
- // cerr << RecThreadInfo::id() << ' ' << name << ' ' << now.tv_sec << '.' << now.tv_usec << " running" << endl;
- f();
+ function();
Utility::gettimeofday(&last_run);
now = last_run;
}
}
- time_t getPeriod() const
+ [[nodiscard]] time_t getPeriod() const
{
return period.tv_sec;
}
- void setPeriod(time_t p)
+ void setPeriod(time_t newperiod)
{
- period.tv_sec = p;
+ period.tv_sec = newperiod;
}
void updateLastRun()
Utility::gettimeofday(&last_run);
}
- bool hasRun() const
+ [[nodiscard]] bool hasRun() const
{
return last_run.tv_sec != 0 || last_run.tv_usec != 0;
}
0, 0
};
struct timeval period;
- const string name;
+ string name;
};
-static void houseKeeping(void*)
+static void houseKeepingWork(Logr::log_t log)
{
- 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, [now]() {
+ g_packetCache->doPruneTo(now.tv_sec, g_maxPacketCacheEntries);
});
+ }
+ static PeriodicTask recordCachePruneTask{"RecordCachePruneTask", 5};
+ recordCachePruneTask.runIfDue(now, [now]() {
+ g_recCache->doPrune(now.tv_sec, g_maxCacheEntries);
+ });
- static PeriodicTask negCachePruneTask{"NegCachePrunteTask", 5};
- negCachePruneTask.runIfDue(now, []() {
- g_negCache->prune(g_maxCacheEntries / 8);
- });
+ static PeriodicTask negCachePruneTask{"NegCachePrunteTask", 5};
+ negCachePruneTask.runIfDue(now, [now]() {
+ g_negCache->prune(now.tv_sec, 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"));
}
- });
+ }
+ });
+ }
+ t_Counters.updateSnap(g_regressionTestMode);
+}
- 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"));
- }
- }
- });
+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) {
}
}
+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
+ if (threadInfo.isWorker()) { // either UDP of TCP worker
+ // 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.isTCPListener()) {
+ if (listenOnTCP) {
+ if (TCPConnection::getCurrentConnections() > maxTcpClients) { // shutdown, too many connections
+ for (const auto fileDesc : threadInfo.getTCPSockets()) {
+ t_fdm->removeReadFD(fileDesc);
+ }
+ listenOnTCP = false;
+ }
+ }
+ else {
+ if (TCPConnection::getCurrentConnections() <= maxTcpClients) { // reenable
+ for (const auto fileDesc : threadInfo.getTCPSockets()) {
+ 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 (g_multiTasker->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);
+ g_multiTasker->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,
+ 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) {
+ g_multiTasker->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()
{
auto log = g_slog->withName("runtime");
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"));
}
}
#ifdef NOD_ENABLED
- if (threadInfo.isWorker())
+ if (threadInfo.isWorker()) {
setupNODThread(log);
+ }
#endif /* NOD_ENABLED */
/* the listener threads handle TCP queries */
}
}
- unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numWorkers();
- if (ringsize) {
+ if (unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numUDPWorkers(); 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>();
t_bogusqueryring = std::make_unique<boost::circular_buffer<pair<DNSName, uint16_t>>>();
t_bogusqueryring->set_capacity(ringsize);
}
- MT = std::make_unique<MT_t>(::arg().asNum("stack-size"), ::arg().asNum("stack-cache-size"));
- threadInfo.mt = MT.get();
+ g_multiTasker = std::make_unique<MT_t>(::arg().asNum("stack-size"), ::arg().asNum("stack-cache-size"));
+ threadInfo.setMT(g_multiTasker.get());
- /* start protobuf export threads if needed */
- auto luaconfsLocal = g_luaconfs.getLocal();
- checkProtobufExport(luaconfsLocal);
- checkOutgoingProtobufExport(luaconfsLocal);
+ {
+ /* start protobuf export threads if needed, don;'t keep a ref to lua config around */
+ auto luaconfsLocal = g_luaconfs.getLocal();
+ 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
+ }
t_fdm = unique_ptr<FDMultiplexer>(getMultiplexer(log));
std::unique_ptr<RecursorWebServer> rws;
- t_fdm->addReadFD(threadInfo.pipes.readToThread, handlePipeRequest);
+ t_fdm->addReadFD(threadInfo.getPipes().readToThread, handlePipeRequest);
if (threadInfo.isHandler()) {
if (::arg().mustDo("webserver")) {
SLOG(g_log << Logger::Warning << "Enabling web server" << endl,
- log->info(Logr::Info, "Enabling web server"))
+ log->info(Logr::Info, "Enabling web server"));
try {
rws = make_unique<RecursorWebServer>(t_fdm.get());
}
log->info(Logr::Info, "Enabled multiplexer", "name", Logging::Loggable(t_fdm->getName())));
}
else {
- t_fdm->addReadFD(threadInfo.pipes.readQueriesToThread, handlePipeRequest);
+ t_fdm->addReadFD(threadInfo.getPipes().readQueriesToThread, handlePipeRequest);
if (threadInfo.isListener()) {
if (g_reusePort) {
/* then every listener has its own FDs */
- for (const auto& deferred : threadInfo.deferredAdds) {
+ for (const auto& deferred : threadInfo.getDeferredAdds()) {
t_fdm->addReadFD(deferred.first, deferred.second);
}
}
else {
/* otherwise all listeners are listening on the same ones */
- for (const auto& deferred : g_deferredAdds) {
+ for (const auto& deferred : threadInfo.isTCPListener() ? s_deferredTCPadds : s_deferredUDPadds) {
t_fdm->addReadFD(deferred.first, deferred.second);
}
}
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.
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,
- log->error(Logr::Error, ae.reason, "Exception in RecursorThread", "exception", Logging::Loggable("PDNSException")))
+ log->error(Logr::Error, ae.reason, "Exception in RecursorThread", "exception", Logging::Loggable("PDNSException")));
}
catch (std::exception& e) {
SLOG(g_log << Logger::Error << "STL Exception: " << e.what() << endl,
- log->error(Logr::Error, e.what(), "Exception in RecursorThread", "exception", Logging::Loggable("std::exception")))
+ log->error(Logr::Error, e.what(), "Exception in RecursorThread", "exception", Logging::Loggable("std::exception")));
}
catch (...) {
SLOG(g_log << Logger::Error << "any other exception in main: " << endl,
}
}
-int main(int argc, char** argv)
+static pair<int, bool> doYamlConfig(Logr::log_t /* startupLog */, int argc, char* argv[]) // NOLINT: Posix API
{
- g_argc = argc;
- g_argv = argv;
- Utility::srandom();
- versionSetProduct(ProductRecursor);
- reportBasicTypes();
- reportOtherTypes();
+ if (!::arg().mustDo("config")) {
+ return {0, false};
+ }
+ const string config = ::arg()["config"];
+ if (config == "diff" || config.empty()) {
+ ::arg().parse(argc, argv);
+ pdns::rust::settings::rec::Recursorsettings settings;
+ pdns::settings::rec::oldStyleSettingsToBridgeStruct(settings);
+ auto yaml = settings.to_yaml_string();
+ cout << yaml << endl;
+ }
+ else if (config == "default") {
+ auto yaml = pdns::settings::rec::defaultsToYaml();
+ cout << yaml << endl;
+ }
+ return {0, true};
+}
- int ret = EXIT_SUCCESS;
+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)) {
+ 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)) {
+ 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)) {
+ 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};
+}
- try {
-#if HAVE_FIBER_SANITIZER
- // Asan needs more stack
+static void handleRuntimeDefaults(Logr::log_t log)
+{
+#ifdef HAVE_FIBER_SANITIZER
+ // Asan needs more stack
+ if (::arg().asNum("stack-size") == 200000) { // the default in table.py
::arg().set("stack-size", "stack size per mthread") = "600000";
-#else
- ::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"
-#ifdef HAVE_SYSTEMD
-#define SYSTEMD_SETID_MSG ". When running inside systemd, use the User and Group settings in the unit-file!"
- SYSTEMD_SETID_MSG
-#endif
- )
- = "";
- ::arg().set("setuid", "If set, change user id to this uid for more security"
-#ifdef HAVE_SYSTEMD
- 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"
-#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");
+
+ const string RUNTIME = "*runtime determined*";
+ if (::arg()["version-string"] == RUNTIME) { // i.e. not set explicitly
+ ::arg().set("version-string") = fullVersionString();
+ }
+
+ if (::arg()["server-id"] == RUNTIME) { // i.e. not set explicitly
+ 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"));
+ }
+ ::arg().set("server-id") = myHostname.has_value() ? *myHostname : "";
+ }
+
+ if (::arg()["socket-dir"].empty()) {
+ 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.";
-
-#ifdef SO_REUSEPORT
- ::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";
-#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") = "";
+ }
- 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);
+ if (::arg()["socket-dir"].empty()) {
+ if (::arg()["chroot"].empty()) {
+ ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
}
- for (size_t idx = 0; idx < 128; idx++) {
- defaultAPIDisabledStats += ", ecs-v6-response-bits-" + std::to_string(idx + 1);
+ else {
+ ::arg().set("socket-dir") = "/";
}
- 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-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("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().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") = "";
+ if (::arg().asNum("threads") == 1) {
+ if (::arg().mustDo("pdns-distributes-queries")) {
+ SLOG(g_log << Logger::Warning << "Only one thread, no need to distribute queries ourselves" << endl,
+ log->info(Logr::Warning, "Only one thread, no need to distribute queries ourselves"));
+ ::arg().set("pdns-distributes-queries") = "no";
+ }
+ }
- ::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";
+ if (::arg().mustDo("pdns-distributes-queries") && ::arg().asNum("distributor-threads") == 0) {
+ SLOG(g_log << Logger::Warning << "Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1" << endl,
+ log->info(Logr::Warning, "Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"));
+ ::arg().set("distributor-threads") = "1";
+ }
- ::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";
+ if (!::arg().mustDo("pdns-distributes-queries") && ::arg().asNum("distributor-threads") > 0) {
+ SLOG(g_log << Logger::Warning << "Not distributing queries, setting distributor threads to 0" << endl,
+ log->info(Logr::Warning, "Not distributing queries, setting distributor threads to 0"));
+ ::arg().set("distributor-threads") = "0";
+ }
+}
- ::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";
+static void setupLogging(const string& logname)
+{
+ if (logname == "systemd-journal") {
+#ifdef HAVE_SYSTEMD
+ if (int fileDesc = sd_journal_stream_fd("pdns-recusor", LOG_DEBUG, 0); fileDesc >= 0) {
+ g_slog = Logging::Logger::create(loggerSDBackend);
+ close(fileDesc);
+ }
+#endif
+ if (g_slog == nullptr) {
+ cerr << "Requested structured logging to systemd-journal, but it is not available" << endl;
+ }
+ }
+ else if (logname == "json") {
+ g_slog = Logging::Logger::create(loggerJSONBackend);
+ if (g_slog == nullptr) {
+ cerr << "JSON logging requested but it is not available" << endl;
+ }
+ }
- ::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";
+ if (g_slog == nullptr) {
+ g_slog = Logging::Logger::create(loggerBackend);
+ }
+}
- ::arg().set("x-dnssec-names", "Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters") = "";
+int main(int argc, char** argv)
+{
+ g_argc = argc;
+ g_argv = argv;
+ versionSetProduct(ProductRecursor);
+ reportBasicTypes();
+ reportOtherTypes();
-#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";
-#endif /* NOD_ENABLED */
+ int ret = EXIT_SUCCESS;
- ::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.");
+ try {
+ pdns::settings::rec::defineOldStyleSettings();
::arg().setDefaults();
g_log.toConsole(Logger::Info);
::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.
g_slogStructured = ::arg().mustDo("structured-logging");
s_structured_logger_backend = ::arg()["structured-logging-backend"];
- if (s_logUrgency < Logger::Error) {
- s_logUrgency = Logger::Error;
- }
if (!g_quiet && s_logUrgency < Logger::Info) { // Logger::Info=6, Logger::Debug=7
s_logUrgency = Logger::Info; // if you do --quiet=no, you need Info to also see the query log
}
g_log.setLoglevel(s_logUrgency);
g_log.toConsole(s_logUrgency);
+ showProductVersion();
+ if (!g_slogStructured) {
+ g_log << Logger::Warning << "Disabling structured logging is deprecated, old-style logging wil be removed in a future release" << endl;
+ }
- string configname = ::arg()["config-dir"] + "/recursor.conf";
+ g_yamlSettings = false;
+ string configname = ::arg()["config-dir"] + "/recursor";
if (!::arg()["config-name"].empty()) {
- configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+ configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
g_programname += "-" + ::arg()["config-name"];
}
cleanSlashes(configname);
}
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);
- }
-
- if (s_structured_logger_backend == "systemd-journal") {
-#ifdef HAVE_SYSTEMD
- if (int fd = sd_journal_stream_fd("pdns-recusor", LOG_DEBUG, 0); fd >= 0) {
- g_slog = Logging::Logger::create(loggerSDBackend);
- close(fd);
- }
-#endif
- if (g_slog == nullptr) {
- cerr << "Structured logging to systemd-journal requested but it is not available" << endl;
- }
+ return 99;
}
- if (g_slog == nullptr) {
- g_slog = Logging::Logger::create(loggerBackend);
- }
+ setupLogging(s_structured_logger_backend);
// Missing: a mechanism to call setVerbosity(x)
auto startupLog = g_slog->withName("config");
::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);
+ const string yamlconfigname = configname + ".yml";
+ string msg;
+ pdns::rust::settings::rec::Recursorsettings settings;
+ // TODO: handle include-dir on command line
+ auto yamlstatus = pdns::settings::rec::readYamlSettings(yamlconfigname, ::arg()["include-dir"], settings, msg, startupLog);
+
+ switch (yamlstatus) {
+ case pdns::settings::rec::YamlSettingsStatus::CannotOpen:
+ SLOG(g_log << Logger::Debug << "No YAML config found for configname '" << yamlconfigname << "': " << msg << endl,
+ startupLog->error(Logr::Debug, msg, "No YAML config found", "configname", Logging::Loggable(yamlconfigname)));
+ break;
+ case pdns::settings::rec::YamlSettingsStatus::PresentButFailed:
+ SLOG(g_log << Logger::Error << "YAML config found for configname '" << yamlconfigname << "' but error ocurred processing it" << endl,
+ startupLog->error(Logr::Error, msg, "YAML config found, but error occurred processsing it", "configname", Logging::Loggable(yamlconfigname)));
+ return 1;
+ break;
+ case pdns::settings::rec::YamlSettingsStatus::OK:
+ g_yamlSettings = true;
+ SLOG(g_log << Logger::Notice << "YAML config found and processed for configname '" << yamlconfigname << "'" << endl,
+ startupLog->info(Logr::Notice, "YAML config found and processed", "configname", Logging::Loggable(yamlconfigname)));
+ pdns::settings::rec::processAPIDir(arg()["include-dir"], settings, startupLog);
+ pdns::settings::rec::bridgeStructToOldStyleSettings(settings);
+ break;
+ }
+
+ if (g_yamlSettings) {
+ bool mustExit = false;
+ std::tie(ret, mustExit) = doYamlConfig(startupLog, argc, argv);
+ if (ret != 0 || mustExit) {
+ return ret;
}
- exit(0);
}
- 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)));
+ if (yamlstatus == pdns::settings::rec::YamlSettingsStatus::CannotOpen) {
+ configname += ".conf";
+ bool mustExit = false;
+ std::tie(ret, mustExit) = doConfig(startupLog, configname, argc, argv);
+ if (ret != 0 || mustExit) {
+ return ret;
+ }
+ if (!::arg().file(configname)) {
+ 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)));
+ }
}
- // Reparse, now with config file as well
+ // Reparse, now with config file as well, both for old-style as for YAML settings
::arg().parse(argc, argv);
g_quiet = ::arg().mustDo("quiet");
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);
- }
-
- if (::arg()["socket-dir"].empty()) {
- if (::arg()["chroot"].empty())
- ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
- else
- ::arg().set("socket-dir") = "/";
- }
-
- if (::arg().asNum("threads") == 1) {
- if (::arg().mustDo("pdns-distributes-queries")) {
- SLOG(g_log << Logger::Warning << "Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1" << endl,
- startupLog->v(1)->info("Only one thread, no need to distribute queries ourselves"));
- ::arg().set("pdns-distributes-queries") = "no";
- }
+ return EXIT_FAILURE;
}
- if (::arg().mustDo("pdns-distributes-queries") && ::arg().asNum("distributor-threads") <= 0) {
- SLOG(g_log << Logger::Warning << "Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1" << endl,
- startupLog->v(1)->info("Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"));
- ::arg().set("distributor-threads") = "1";
- }
-
- if (!::arg().mustDo("pdns-distributes-queries")) {
- ::arg().set("distributor-threads") = "0";
- }
+ handleRuntimeDefaults(startupLog);
g_recCache = std::make_unique<MemRecursorCache>(::arg().asNum("record-cache-shards"));
g_negCache = std::make_unique<NegCache>(::arg().asNum("record-cache-shards") / 8);
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,
RecursorControlChannel::Answer doQueueReloadLuaScript(vector<string>::const_iterator begin, vector<string>::const_iterator end)
{
- if (begin != end)
+ if (begin != end) {
::arg().set("lua-dns-script") = *begin;
+ }
return broadcastAccFunction<RecursorControlChannel::Answer>(doReloadLuaScript);
}
}
t_traceRegex = std::make_shared<Regex>(newRegex);
t_tracefd = file;
- return new string("ok\n");
+ return new string("ok\n"); // NOLINT(cppcoreguidelines-owning-memory): it's the API
}
catch (const PDNSException& ae) {
- return new string(ae.reason + "\n");
+ return new string(ae.reason + "\n"); // NOLINT(cppcoreguidelines-owning-memory): it's the API
}
}
struct WipeCacheResult res;
try {
- res.record_count = g_recCache->doWipeCache(canon, subtree, qtype);
+ res.record_count = static_cast<int>(g_recCache->doWipeCache(canon, subtree, qtype));
// scanbuild complains here about an allocated function object that is being leaked. Needs investigation
if (g_packetCache) {
- res.packet_count = g_packetCache->doWipePacketCache(canon, qtype, subtree);
+ res.packet_count = static_cast<int>(g_packetCache->doWipePacketCache(canon, qtype, subtree));
}
- res.negative_record_count = g_negCache->wipe(canon, subtree);
+ res.negative_record_count = static_cast<int>(g_negCache->wipe(canon, subtree));
if (g_aggressiveNSECCache) {
g_aggressiveNSECCache->removeZoneInfo(canon, subtree);
}
struct DNSComboWriter
{
DNSComboWriter(const std::string& query, const struct timeval& now, shared_ptr<RecursorLua4> luaContext) :
- d_mdp(true, query), d_now(now), d_query(query), d_luaContext(luaContext)
+ d_mdp(true, query), d_now(now), d_query(query), d_luaContext(std::move(luaContext))
{
}
DNSComboWriter(const std::string& query, const struct timeval& now, std::unordered_set<std::string>&& policyTags, shared_ptr<RecursorLua4> luaContext, LuaContext::LuaObject&& data, std::vector<DNSRecord>&& records) :
- d_mdp(true, query), d_now(now), d_query(query), d_policyTags(std::move(policyTags)), d_records(std::move(records)), d_luaContext(luaContext), d_data(std::move(data))
+ d_mdp(true, query), d_now(now), d_query(query), d_policyTags(std::move(policyTags)), d_gettagPolicyTags(d_policyTags), d_records(std::move(records)), d_luaContext(std::move(luaContext)), d_data(std::move(data))
{
}
};
std::string d_query;
std::unordered_set<std::string> d_policyTags;
+ const std::unordered_set<std::string> d_gettagPolicyTags;
std::string d_routingTag;
std::vector<DNSRecord> d_records;
{
}
- LWResult::Result getSocket(const ComboAddress& toaddr, int* fd);
+ LWResult::Result getSocket(const ComboAddress& toaddr, int* fileDesc);
// return a socket to the pool, or simply erase it
- void returnSocket(int fd);
+ void returnSocket(int fileDesc);
private:
// returns -1 for errors which might go away, throws for ones that won't
};
typedef MTasker<std::shared_ptr<PacketID>, PacketBuffer, PacketIDCompare> MT_t;
-extern thread_local std::unique_ptr<MT_t> MT; // the big MTasker
+extern thread_local std::unique_ptr<MT_t> g_multiTasker; // the big MTasker
extern std::unique_ptr<RecursorPacketCache> g_packetCache;
using RemoteLoggerStats_t = std::unordered_map<std::string, RemoteLoggerInterface::Stats>;
+extern bool g_yamlSettings;
extern bool g_logCommonErrors;
extern size_t g_proxyProtocolMaximumSize;
extern std::atomic<bool> g_quiet;
extern double g_balancingFactor;
extern size_t g_maxUDPQueriesPerRound;
extern bool g_useKernelTimestamp;
+extern bool g_allowNoRD;
extern thread_local std::shared_ptr<NetmaskGroup> t_allowFrom;
extern thread_local std::shared_ptr<NetmaskGroup> t_allowNotifyFrom;
extern thread_local std::shared_ptr<notifyset_t> t_allowNotifyFor;
/* without reuseport, all listeners share the same sockets */
typedef vector<pair<int, std::function<void(int, boost::any&)>>> deferredAdd_t;
-extern deferredAdd_t g_deferredAdds;
typedef map<ComboAddress, uint32_t, ComboAddress::addressOnlyLessThan> tcpClientCounts_t;
extern thread_local std::unique_ptr<tcpClientCounts_t> t_tcpClientCounts;
inline MT_t* getMT()
{
- return MT ? MT.get() : nullptr;
+ return g_multiTasker ? g_multiTasker.get() : nullptr;
}
/* this function is called with both a string and a vector<uint8_t> representing a packet */
return s_threadInfos.at(t_id);
}
- static RecThreadInfo& info(unsigned int i)
+ static RecThreadInfo& info(unsigned int index)
{
- return s_threadInfos.at(i);
+ return s_threadInfos.at(index);
}
static vector<RecThreadInfo>& infos()
return s_threadInfos;
}
- bool isDistributor() const
+ [[nodiscard]] bool isDistributor() const
{
if (t_id == 0) {
return false;
return s_weDistributeQueries && listener;
}
- bool isHandler() const
+ [[nodiscard]] bool isHandler() const
{
if (t_id == 0) {
return true;
return handler;
}
- bool isWorker() const
+ [[nodiscard]] bool isWorker() const
{
return worker;
}
- bool isListener() const
+ // UDP or TCP listener?
+ [[nodiscard]] bool isListener() const
{
return listener;
}
- bool isTaskThread() const
+ // A TCP-only listener?
+ [[nodiscard]] bool isTCPListener() const
+ {
+ return tcplistener;
+ }
+
+ [[nodiscard]] bool isTaskThread() const
{
return taskThread;
}
listener = flag;
}
+ void setTCPListener(bool flag = true)
+ {
+ setListener(flag);
+ tcplistener = flag;
+ }
+
void setTaskThread()
{
taskThread = true;
return t_id;
}
- static void setThreadId(unsigned int id)
+ static void setThreadId(unsigned int arg)
{
- t_id = id;
+ t_id = arg;
}
- std::string getName() const
+ [[nodiscard]] std::string getName() const
{
return name;
}
return 1;
}
- static unsigned int numWorkers()
+ static unsigned int numUDPWorkers()
{
- return s_numWorkerThreads;
+ return s_numUDPWorkerThreads;
+ }
+
+ static unsigned int numTCPWorkers()
+ {
+ return s_numTCPWorkerThreads;
}
static unsigned int numDistributors()
s_weDistributeQueries = flag;
}
- static void setNumWorkerThreads(unsigned int n)
+ static void setNumUDPWorkerThreads(unsigned int n)
{
- s_numWorkerThreads = n;
+ s_numUDPWorkerThreads = n;
+ }
+
+ static void setNumTCPWorkerThreads(unsigned int n)
+ {
+ s_numTCPWorkerThreads = n;
}
static void setNumDistributorThreads(unsigned int n)
static unsigned int numRecursorThreads()
{
- return numHandlers() + numDistributors() + numWorkers() + numTaskThreads();
+ return numHandlers() + numDistributors() + numUDPWorkers() + numTCPWorkers() + numTaskThreads();
}
static int runThreads(Logr::log_t);
static void makeThreadPipes(Logr::log_t);
- void setExitCode(int e)
+ void setExitCode(int n)
+ {
+ exitCode = n;
+ }
+
+ std::set<int>& getTCPSockets()
+ {
+ return tcpSockets;
+ }
+
+ void setTCPSockets(std::set<int>& socks)
+ {
+ tcpSockets = socks;
+ }
+
+ deferredAdd_t& getDeferredAdds()
+ {
+ return deferredAdds;
+ }
+
+ const ThreadPipeSet& getPipes() const
{
- exitCode = e;
+ return pipes;
}
+ [[nodiscard]] uint64_t getNumberOfDistributedQueries() const
+ {
+ return numberOfDistributedQueries;
+ }
+
+ void incNumberOfDistributedQueries()
+ {
+ numberOfDistributedQueries++;
+ }
+
+ MT_t* getMT()
+ {
+ return mt;
+ }
+
+ void setMT(MT_t* theMT)
+ {
+ mt = theMT;
+ }
+
+private:
// FD corresponding to TCP sockets this thread is listening on.
// These FDs are also in deferredAdds when we have one socket per
// listener, and in g_deferredAdds instead.
MT_t* mt{nullptr};
uint64_t numberOfDistributedQueries{0};
-private:
- void start(unsigned int id, const string& name, const std::map<unsigned int, std::set<int>>& cpusMap, Logr::log_t);
+ void start(unsigned int tid, const string& tname, const std::map<unsigned int, std::set<int>>& cpusMap, Logr::log_t);
std::string name;
std::thread thread;
bool handler{false};
// accept incoming queries (and distributes them to the workers if pdns-distributes-queries is set)
bool listener{false};
+ // accept incoming TCP queries (and distributes them to the workers if pdns-distributes-queries is set)
+ bool tcplistener{false};
// process queries
bool worker{false};
// run async tasks: from TaskQueue and ZoneToCache
static std::vector<RecThreadInfo> s_threadInfos;
static bool s_weDistributeQueries; // if true, 1 or more threads listen on the incoming query sockets and distribute them to workers
static unsigned int s_numDistributorThreads;
- static unsigned int s_numWorkerThreads;
+ static unsigned int s_numUDPWorkerThreads;
+ static unsigned int s_numTCPWorkerThreads;
};
struct ThreadMSG
#endif
void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
bool& foundECS, EDNSSubnetOpts* ednssubnet, EDNSOptionViewMap* options);
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedSource, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta);
+void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedSource, const Netmask& ednssubnet, bool tcp, uint16_t queryID, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta);
bool isAllowNotifyForZone(DNSName qname);
bool checkForCacheHit(bool qnameParsed, unsigned int tag, const string& data,
DNSName& qname, uint16_t& qtype, uint16_t& qclass,
string& response, uint32_t& qhash,
RecursorPacketCache::OptPBData& pbData, bool tcp, const ComboAddress& source, const ComboAddress& mappedSource);
void protobufLogResponse(pdns::ProtoZero::RecMessage& message);
-void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
+void protobufLogResponse(const struct dnsheader* header, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
const RecursorPacketCache::OptPBData& pbData, const struct timeval& tv,
bool tcp, const ComboAddress& source, const ComboAddress& destination,
const ComboAddress& mappedSource, const EDNSSubnetOpts& ednssubnet,
const boost::uuids::uuid& uniqueId, const string& requestorId, const string& deviceId,
const string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta,
- const RecEventTrace& eventTrace);
+ const RecEventTrace& eventTrace,
+ const std::unordered_set<std::string>& policyTags);
void requestWipeCaches(const DNSName& canon);
-void startDoResolve(void* p);
+void startDoResolve(void*);
bool expectProxyProtocol(const ComboAddress& from);
-void finishTCPReply(std::unique_ptr<DNSComboWriter>& dc, bool hadError, bool updateInFlight);
+void finishTCPReply(std::unique_ptr<DNSComboWriter>&, bool hadError, bool updateInFlight);
void checkFastOpenSysctl(bool active, Logr::log_t);
void checkTFOconnect(Logr::log_t);
void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets, Logr::log_t);
-void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&);
+void handleNewTCPQuestion(int fileDesc, FDMultiplexer::funcparam_t&);
void makeUDPServerSockets(deferredAdd_t& deferredAdds, Logr::log_t);
string doTraceRegex(FDWrapper file, vector<string>::const_iterator begin, vector<string>::const_iterator end);
#include "rec-protozero.hh"
#include <variant>
-void pdns::ProtoZero::RecMessage::addRR(const DNSRecord& record, const std::set<uint16_t>& exportTypes, bool udr)
+void pdns::ProtoZero::RecMessage::addRR(const DNSRecord& record, const std::set<uint16_t>& exportTypes, [[maybe_unused]] bool udr)
{
if (record.d_place != DNSResourceRecord::ANSWER || record.d_class != QClass::IN) {
return;
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
#include <unordered_map>
#define RECURSOR_TRAPS_OID RECURSOR_OID, 10, 0
#define RECURSOR_TRAP_OBJECTS_OID RECURSOR_OID, 11
-static const oid trapReasonOID[] = {RECURSOR_TRAP_OBJECTS_OID, 1, 0};
-static const oid customTrapOID[] = {RECURSOR_TRAPS_OID, 1};
-
-static const oid questionsOID[] = {RECURSOR_STATS_OID, 1};
-static const oid ipv6QuestionsOID[] = {RECURSOR_STATS_OID, 2};
-static const oid tcpQuestionsOID[] = {RECURSOR_STATS_OID, 3};
-static const oid cacheHitsOID[] = {RECURSOR_STATS_OID, 4};
-static const oid cacheMissesOID[] = {RECURSOR_STATS_OID, 5};
-static const oid cacheEntriesOID[] = {RECURSOR_STATS_OID, 6};
-static const oid cacheBytesOID[] = {RECURSOR_STATS_OID, 7};
-static const oid packetcacheHitsOID[] = {RECURSOR_STATS_OID, 8};
-static const oid packetcacheMissesOID[] = {RECURSOR_STATS_OID, 9};
-static const oid packetcacheEntriesOID[] = {RECURSOR_STATS_OID, 10};
-static const oid packetcacheBytesOID[] = {RECURSOR_STATS_OID, 11};
-static const oid mallocBytesOID[] = {RECURSOR_STATS_OID, 12};
-static const oid servfailAnswersOID[] = {RECURSOR_STATS_OID, 13};
-static const oid nxdomainAnswersOID[] = {RECURSOR_STATS_OID, 14};
-static const oid noerrorAnswersOID[] = {RECURSOR_STATS_OID, 15};
-static const oid unauthorizedUdpOID[] = {RECURSOR_STATS_OID, 16};
-static const oid unauthorizedTcpOID[] = {RECURSOR_STATS_OID, 17};
-static const oid tcpClientOverflowOID[] = {RECURSOR_STATS_OID, 18};
-static const oid clientParseErrorsOID[] = {RECURSOR_STATS_OID, 19};
-static const oid serverParseErrorsOID[] = {RECURSOR_STATS_OID, 20};
-static const oid tooOldDropsOID[] = {RECURSOR_STATS_OID, 21};
-static const oid answers01OID[] = {RECURSOR_STATS_OID, 22};
-static const oid answers110OID[] = {RECURSOR_STATS_OID, 23};
-static const oid answers10100OID[] = {RECURSOR_STATS_OID, 24};
-static const oid answers1001000OID[] = {RECURSOR_STATS_OID, 25};
-static const oid answersSlowOID[] = {RECURSOR_STATS_OID, 26};
-static const oid auth4Answers01OID[] = {RECURSOR_STATS_OID, 27};
-static const oid auth4Answers110OID[] = {RECURSOR_STATS_OID, 28};
-static const oid auth4Answers10100OID[] = {RECURSOR_STATS_OID, 29};
-static const oid auth4Answers1001000OID[] = {RECURSOR_STATS_OID, 30};
-static const oid auth4AnswersslowOID[] = {RECURSOR_STATS_OID, 31};
-static const oid auth6Answers01OID[] = {RECURSOR_STATS_OID, 32};
-static const oid auth6Answers110OID[] = {RECURSOR_STATS_OID, 33};
-static const oid auth6Answers10100OID[] = {RECURSOR_STATS_OID, 34};
-static const oid auth6Answers1001000OID[] = {RECURSOR_STATS_OID, 35};
-static const oid auth6AnswersSlowOID[] = {RECURSOR_STATS_OID, 36};
-static const oid qaLatencyOID[] = {RECURSOR_STATS_OID, 37};
-static const oid unexpectedPacketsOID[] = {RECURSOR_STATS_OID, 38};
-static const oid caseMismatchesOID[] = {RECURSOR_STATS_OID, 39};
-static const oid spoofPreventsOID[] = {RECURSOR_STATS_OID, 40};
-static const oid nssetInvalidationsOID[] = {RECURSOR_STATS_OID, 41};
-static const oid resourceLimitsOID[] = {RECURSOR_STATS_OID, 42};
-static const oid overCapacityDropsOID[] = {RECURSOR_STATS_OID, 43};
-static const oid policyDropsOID[] = {RECURSOR_STATS_OID, 44};
-static const oid noPacketErrorOID[] = {RECURSOR_STATS_OID, 45};
-static const oid dlgOnlyDropsOID[] = {RECURSOR_STATS_OID, 46};
-static const oid ignoredPacketsOID[] = {RECURSOR_STATS_OID, 47};
-static const oid maxMthreadStackOID[] = {RECURSOR_STATS_OID, 48};
-static const oid negcacheEntriesOID[] = {RECURSOR_STATS_OID, 49};
-static const oid throttleEntriesOID[] = {RECURSOR_STATS_OID, 50};
-static const oid nsspeedsEntriesOID[] = {RECURSOR_STATS_OID, 51};
-static const oid failedHostEntriesOID[] = {RECURSOR_STATS_OID, 52};
-static const oid concurrentQueriesOID[] = {RECURSOR_STATS_OID, 53};
-static const oid securityStatusOID[] = {RECURSOR_STATS_OID, 54};
-static const oid outgoingTimeoutsOID[] = {RECURSOR_STATS_OID, 55};
-static const oid outgoing4TimeoutsOID[] = {RECURSOR_STATS_OID, 56};
-static const oid outgoing6TimeoutsOID[] = {RECURSOR_STATS_OID, 57};
-static const oid tcpOutqueriesOID[] = {RECURSOR_STATS_OID, 58};
-static const oid allOutqueriesOID[] = {RECURSOR_STATS_OID, 59};
-static const oid ipv6OutqueriesOID[] = {RECURSOR_STATS_OID, 60};
-static const oid throttledOutqueriesOID[] = {RECURSOR_STATS_OID, 61};
-static const oid dontOutqueriesOID[] = {RECURSOR_STATS_OID, 62};
-static const oid unreachablesOID[] = {RECURSOR_STATS_OID, 63};
-static const oid chainResendsOID[] = {RECURSOR_STATS_OID, 64};
-static const oid tcpClientsOID[] = {RECURSOR_STATS_OID, 65};
+using oid10 = std::array<oid, 10>;
+using oid11 = std::array<oid, 11>;
+
+static const oid11 trapReasonOID = {RECURSOR_TRAP_OBJECTS_OID, 1, 0};
+static const oid11 customTrapOID = {RECURSOR_TRAPS_OID, 1};
+
+static const oid10 questionsOID = {RECURSOR_STATS_OID, 1};
+static const oid10 ipv6QuestionsOID = {RECURSOR_STATS_OID, 2};
+static const oid10 tcpQuestionsOID = {RECURSOR_STATS_OID, 3};
+static const oid10 cacheHitsOID = {RECURSOR_STATS_OID, 4};
+static const oid10 cacheMissesOID = {RECURSOR_STATS_OID, 5};
+static const oid10 cacheEntriesOID = {RECURSOR_STATS_OID, 6};
+static const oid10 cacheBytesOID = {RECURSOR_STATS_OID, 7};
+static const oid10 packetcacheHitsOID = {RECURSOR_STATS_OID, 8};
+static const oid10 packetcacheMissesOID = {RECURSOR_STATS_OID, 9};
+static const oid10 packetcacheEntriesOID = {RECURSOR_STATS_OID, 10};
+static const oid10 packetcacheBytesOID = {RECURSOR_STATS_OID, 11};
+static const oid10 mallocBytesOID = {RECURSOR_STATS_OID, 12};
+static const oid10 servfailAnswersOID = {RECURSOR_STATS_OID, 13};
+static const oid10 nxdomainAnswersOID = {RECURSOR_STATS_OID, 14};
+static const oid10 noerrorAnswersOID = {RECURSOR_STATS_OID, 15};
+static const oid10 unauthorizedUdpOID = {RECURSOR_STATS_OID, 16};
+static const oid10 unauthorizedTcpOID = {RECURSOR_STATS_OID, 17};
+static const oid10 tcpClientOverflowOID = {RECURSOR_STATS_OID, 18};
+static const oid10 clientParseErrorsOID = {RECURSOR_STATS_OID, 19};
+static const oid10 serverParseErrorsOID = {RECURSOR_STATS_OID, 20};
+static const oid10 tooOldDropsOID = {RECURSOR_STATS_OID, 21};
+static const oid10 answers01OID = {RECURSOR_STATS_OID, 22};
+static const oid10 answers110OID = {RECURSOR_STATS_OID, 23};
+static const oid10 answers10100OID = {RECURSOR_STATS_OID, 24};
+static const oid10 answers1001000OID = {RECURSOR_STATS_OID, 25};
+static const oid10 answersSlowOID = {RECURSOR_STATS_OID, 26};
+static const oid10 auth4Answers01OID = {RECURSOR_STATS_OID, 27};
+static const oid10 auth4Answers110OID = {RECURSOR_STATS_OID, 28};
+static const oid10 auth4Answers10100OID = {RECURSOR_STATS_OID, 29};
+static const oid10 auth4Answers1001000OID = {RECURSOR_STATS_OID, 30};
+static const oid10 auth4AnswersslowOID = {RECURSOR_STATS_OID, 31};
+static const oid10 auth6Answers01OID = {RECURSOR_STATS_OID, 32};
+static const oid10 auth6Answers110OID = {RECURSOR_STATS_OID, 33};
+static const oid10 auth6Answers10100OID = {RECURSOR_STATS_OID, 34};
+static const oid10 auth6Answers1001000OID = {RECURSOR_STATS_OID, 35};
+static const oid10 auth6AnswersSlowOID = {RECURSOR_STATS_OID, 36};
+static const oid10 qaLatencyOID = {RECURSOR_STATS_OID, 37};
+static const oid10 unexpectedPacketsOID = {RECURSOR_STATS_OID, 38};
+static const oid10 caseMismatchesOID = {RECURSOR_STATS_OID, 39};
+static const oid10 spoofPreventsOID = {RECURSOR_STATS_OID, 40};
+static const oid10 nssetInvalidationsOID = {RECURSOR_STATS_OID, 41};
+static const oid10 resourceLimitsOID = {RECURSOR_STATS_OID, 42};
+static const oid10 overCapacityDropsOID = {RECURSOR_STATS_OID, 43};
+static const oid10 policyDropsOID = {RECURSOR_STATS_OID, 44};
+static const oid10 noPacketErrorOID = {RECURSOR_STATS_OID, 45};
+static const oid10 dlgOnlyDropsOID = {RECURSOR_STATS_OID, 46};
+static const oid10 ignoredPacketsOID = {RECURSOR_STATS_OID, 47};
+static const oid10 maxMthreadStackOID = {RECURSOR_STATS_OID, 48};
+static const oid10 negcacheEntriesOID = {RECURSOR_STATS_OID, 49};
+static const oid10 throttleEntriesOID = {RECURSOR_STATS_OID, 50};
+static const oid10 nsspeedsEntriesOID = {RECURSOR_STATS_OID, 51};
+static const oid10 failedHostEntriesOID = {RECURSOR_STATS_OID, 52};
+static const oid10 concurrentQueriesOID = {RECURSOR_STATS_OID, 53};
+static const oid10 securityStatusOID = {RECURSOR_STATS_OID, 54};
+static const oid10 outgoingTimeoutsOID = {RECURSOR_STATS_OID, 55};
+static const oid10 outgoing4TimeoutsOID = {RECURSOR_STATS_OID, 56};
+static const oid10 outgoing6TimeoutsOID = {RECURSOR_STATS_OID, 57};
+static const oid10 tcpOutqueriesOID = {RECURSOR_STATS_OID, 58};
+static const oid10 allOutqueriesOID = {RECURSOR_STATS_OID, 59};
+static const oid10 ipv6OutqueriesOID = {RECURSOR_STATS_OID, 60};
+static const oid10 throttledOutqueriesOID = {RECURSOR_STATS_OID, 61};
+static const oid10 dontOutqueriesOID = {RECURSOR_STATS_OID, 62};
+static const oid10 unreachablesOID = {RECURSOR_STATS_OID, 63};
+static const oid10 chainResendsOID = {RECURSOR_STATS_OID, 64};
+static const oid10 tcpClientsOID = {RECURSOR_STATS_OID, 65};
#ifdef __linux__
-static const oid udpRecvbufErrorsOID[] = {RECURSOR_STATS_OID, 66};
-static const oid udpSndbufErrorsOID[] = {RECURSOR_STATS_OID, 67};
-static const oid udpNoportErrorsOID[] = {RECURSOR_STATS_OID, 68};
-static const oid udpinErrorsOID[] = {RECURSOR_STATS_OID, 69};
+static const oid10 udpRecvbufErrorsOID = {RECURSOR_STATS_OID, 66};
+static const oid10 udpSndbufErrorsOID = {RECURSOR_STATS_OID, 67};
+static const oid10 udpNoportErrorsOID = {RECURSOR_STATS_OID, 68};
+static const oid10 udpinErrorsOID = {RECURSOR_STATS_OID, 69};
#endif /* __linux__ */
-static const oid ednsPingMatchesOID[] = {RECURSOR_STATS_OID, 70};
-static const oid ednsPingMismatchesOID[] = {RECURSOR_STATS_OID, 71};
-static const oid dnssecQueriesOID[] = {RECURSOR_STATS_OID, 72};
-static const oid nopingOutqueriesOID[] = {RECURSOR_STATS_OID, 73};
-static const oid noednsOutqueriesOID[] = {RECURSOR_STATS_OID, 74};
-static const oid uptimeOID[] = {RECURSOR_STATS_OID, 75};
-static const oid realMemoryUsageOID[] = {RECURSOR_STATS_OID, 76};
-static const oid fdUsageOID[] = {RECURSOR_STATS_OID, 77};
-static const oid userMsecOID[] = {RECURSOR_STATS_OID, 78};
-static const oid sysMsecOID[] = {RECURSOR_STATS_OID, 79};
-static const oid dnssecValidationsOID[] = {RECURSOR_STATS_OID, 80};
-static const oid dnssecResultInsecureOID[] = {RECURSOR_STATS_OID, 81};
-static const oid dnssecResultSecureOID[] = {RECURSOR_STATS_OID, 82};
-static const oid dnssecResultBogusOID[] = {RECURSOR_STATS_OID, 83};
-static const oid dnssecResultIndeterminateOID[] = {RECURSOR_STATS_OID, 84};
-static const oid dnssecResultNtaOID[] = {RECURSOR_STATS_OID, 85};
-static const oid policyResultNoactionOID[] = {RECURSOR_STATS_OID, 86};
-static const oid policyResultDropOID[] = {RECURSOR_STATS_OID, 87};
-static const oid policyResultNxdomainOID[] = {RECURSOR_STATS_OID, 88};
-static const oid policyResultNodataOID[] = {RECURSOR_STATS_OID, 89};
-static const oid policyResultTruncateOID[] = {RECURSOR_STATS_OID, 90};
-static const oid policyResultCustomOID[] = {RECURSOR_STATS_OID, 91};
-static const oid queryPipeFullDropsOID[] = {RECURSOR_STATS_OID, 92};
-static const oid truncatedDropsOID[] = {RECURSOR_STATS_OID, 93};
-static const oid emptyQueriesOID[] = {RECURSOR_STATS_OID, 94};
-static const oid dnssecAuthenticDataQueriesOID[] = {RECURSOR_STATS_OID, 95};
-static const oid dnssecCheckDisabledQueriesOID[] = {RECURSOR_STATS_OID, 96};
-static const oid variableResponsesOID[] = {RECURSOR_STATS_OID, 97};
-static const oid specialMemoryUsageOID[] = {RECURSOR_STATS_OID, 98};
-static const oid rebalancedQueriesOID[] = {RECURSOR_STATS_OID, 99};
-static const oid qnameMinFallbackSuccessOID[] = {RECURSOR_STATS_OID, 100};
-static const oid proxyProtocolInvalidOID[] = {RECURSOR_STATS_OID, 101};
-static const oid recordCacheContendedOID[] = {RECURSOR_STATS_OID, 102};
-static const oid recordCacheAcquiredOID[] = {RECURSOR_STATS_OID, 103};
-static const oid nodLookupsDroppedOversizeOID[] = {RECURSOR_STATS_OID, 104};
-static const oid taskQueuePushedOID[] = {RECURSOR_STATS_OID, 105};
-static const oid taskQueueExpiredOID[] = {RECURSOR_STATS_OID, 106};
-static const oid taskQueueSizeOID[] = {RECURSOR_STATS_OID, 107};
-static const oid aggressiveNSECCacheEntriesOID[] = {RECURSOR_STATS_OID, 108};
-static const oid aggressiveNSECCacheNSECHitsOID[] = {RECURSOR_STATS_OID, 109};
-static const oid aggressiveNSECCacheNSEC3HitsOID[] = {RECURSOR_STATS_OID, 110};
-static const oid aggressiveNSECCacheNSECWCHitsOID[] = {RECURSOR_STATS_OID, 111};
-static const oid aggressiveNSECCacheNSEC3WCHitsOID[] = {RECURSOR_STATS_OID, 112};
-static const oid dotOutqueriesOID[] = {RECURSOR_STATS_OID, 113};
-static const oid dns64PrefixAnswers[] = {RECURSOR_STATS_OID, 114};
-static const oid almostExpiredPushed[] = {RECURSOR_STATS_OID, 115};
-static const oid almostExpiredRun[] = {RECURSOR_STATS_OID, 116};
-static const oid almostExpiredExceptions[] = {RECURSOR_STATS_OID, 117};
+static const oid10 ednsPingMatchesOID = {RECURSOR_STATS_OID, 70};
+static const oid10 ednsPingMismatchesOID = {RECURSOR_STATS_OID, 71};
+static const oid10 dnssecQueriesOID = {RECURSOR_STATS_OID, 72};
+static const oid10 nopingOutqueriesOID = {RECURSOR_STATS_OID, 73};
+static const oid10 noednsOutqueriesOID = {RECURSOR_STATS_OID, 74};
+static const oid10 uptimeOID = {RECURSOR_STATS_OID, 75};
+static const oid10 realMemoryUsageOID = {RECURSOR_STATS_OID, 76};
+static const oid10 fdUsageOID = {RECURSOR_STATS_OID, 77};
+static const oid10 userMsecOID = {RECURSOR_STATS_OID, 78};
+static const oid10 sysMsecOID = {RECURSOR_STATS_OID, 79};
+static const oid10 dnssecValidationsOID = {RECURSOR_STATS_OID, 80};
+static const oid10 dnssecResultInsecureOID = {RECURSOR_STATS_OID, 81};
+static const oid10 dnssecResultSecureOID = {RECURSOR_STATS_OID, 82};
+static const oid10 dnssecResultBogusOID = {RECURSOR_STATS_OID, 83};
+static const oid10 dnssecResultIndeterminateOID = {RECURSOR_STATS_OID, 84};
+static const oid10 dnssecResultNtaOID = {RECURSOR_STATS_OID, 85};
+static const oid10 policyResultNoactionOID = {RECURSOR_STATS_OID, 86};
+static const oid10 policyResultDropOID = {RECURSOR_STATS_OID, 87};
+static const oid10 policyResultNxdomainOID = {RECURSOR_STATS_OID, 88};
+static const oid10 policyResultNodataOID = {RECURSOR_STATS_OID, 89};
+static const oid10 policyResultTruncateOID = {RECURSOR_STATS_OID, 90};
+static const oid10 policyResultCustomOID = {RECURSOR_STATS_OID, 91};
+static const oid10 queryPipeFullDropsOID = {RECURSOR_STATS_OID, 92};
+static const oid10 truncatedDropsOID = {RECURSOR_STATS_OID, 93};
+static const oid10 emptyQueriesOID = {RECURSOR_STATS_OID, 94};
+static const oid10 dnssecAuthenticDataQueriesOID = {RECURSOR_STATS_OID, 95};
+static const oid10 dnssecCheckDisabledQueriesOID = {RECURSOR_STATS_OID, 96};
+static const oid10 variableResponsesOID = {RECURSOR_STATS_OID, 97};
+static const oid10 specialMemoryUsageOID = {RECURSOR_STATS_OID, 98};
+static const oid10 rebalancedQueriesOID = {RECURSOR_STATS_OID, 99};
+static const oid10 qnameMinFallbackSuccessOID = {RECURSOR_STATS_OID, 100};
+static const oid10 proxyProtocolInvalidOID = {RECURSOR_STATS_OID, 101};
+static const oid10 recordCacheContendedOID = {RECURSOR_STATS_OID, 102};
+static const oid10 recordCacheAcquiredOID = {RECURSOR_STATS_OID, 103};
+static const oid10 nodLookupsDroppedOversizeOID = {RECURSOR_STATS_OID, 104};
+static const oid10 taskQueuePushedOID = {RECURSOR_STATS_OID, 105};
+static const oid10 taskQueueExpiredOID = {RECURSOR_STATS_OID, 106};
+static const oid10 taskQueueSizeOID = {RECURSOR_STATS_OID, 107};
+static const oid10 aggressiveNSECCacheEntriesOID = {RECURSOR_STATS_OID, 108};
+static const oid10 aggressiveNSECCacheNSECHitsOID = {RECURSOR_STATS_OID, 109};
+static const oid10 aggressiveNSECCacheNSEC3HitsOID = {RECURSOR_STATS_OID, 110};
+static const oid10 aggressiveNSECCacheNSECWCHitsOID = {RECURSOR_STATS_OID, 111};
+static const oid10 aggressiveNSECCacheNSEC3WCHitsOID = {RECURSOR_STATS_OID, 112};
+static const oid10 dotOutqueriesOID = {RECURSOR_STATS_OID, 113};
+static const oid10 dns64PrefixAnswers = {RECURSOR_STATS_OID, 114};
+static const oid10 almostExpiredPushed = {RECURSOR_STATS_OID, 115};
+static const oid10 almostExpiredRun = {RECURSOR_STATS_OID, 116};
+static const oid10 almostExpiredExceptions = {RECURSOR_STATS_OID, 117};
#ifdef __linux__
-static const oid udpInCsumErrorsOID[] = {RECURSOR_STATS_OID, 118};
-static const oid udp6RecvbufErrorsOID[] = {RECURSOR_STATS_OID, 119};
-static const oid udp6SndbufErrorsOID[] = {RECURSOR_STATS_OID, 120};
-static const oid udp6NoportErrorsOID[] = {RECURSOR_STATS_OID, 121};
-static const oid udp6InErrorsOID[] = {RECURSOR_STATS_OID, 122};
-static const oid udp6InCsumErrorsOID[] = {RECURSOR_STATS_OID, 123};
+static const oid10 udpInCsumErrorsOID = {RECURSOR_STATS_OID, 118};
+static const oid10 udp6RecvbufErrorsOID = {RECURSOR_STATS_OID, 119};
+static const oid10 udp6SndbufErrorsOID = {RECURSOR_STATS_OID, 120};
+static const oid10 udp6NoportErrorsOID = {RECURSOR_STATS_OID, 121};
+static const oid10 udp6InErrorsOID = {RECURSOR_STATS_OID, 122};
+static const oid10 udp6InCsumErrorsOID = {RECURSOR_STATS_OID, 123};
#endif /* __linux__ */
-static const oid sourceDisallowedNotifyOID[] = {RECURSOR_STATS_OID, 124};
-static const oid zoneDisallowedNotifyOID[] = {RECURSOR_STATS_OID, 125};
-static const oid nonResolvingNameserverEntriesOID[] = {RECURSOR_STATS_OID, 126};
-static const oid maintenanceUSecOID[] = {RECURSOR_STATS_OID, 127};
-static const oid maintenanceCallsOID[] = {RECURSOR_STATS_OID, 128};
-
-static const oid rcode0AnswersOID[] = {RECURSOR_STATS_OID, 129};
-static const oid rcode1AnswersOID[] = {RECURSOR_STATS_OID, 130};
-static const oid rcode2AnswersOID[] = {RECURSOR_STATS_OID, 131};
-static const oid rcode3AnswersOID[] = {RECURSOR_STATS_OID, 132};
-static const oid rcode4AnswersOID[] = {RECURSOR_STATS_OID, 133};
-static const oid rcode5AnswersOID[] = {RECURSOR_STATS_OID, 134};
-static const oid rcode6AnswersOID[] = {RECURSOR_STATS_OID, 135};
-static const oid rcode7AnswersOID[] = {RECURSOR_STATS_OID, 136};
-static const oid rcode8AnswersOID[] = {RECURSOR_STATS_OID, 137};
-static const oid rcode9AnswersOID[] = {RECURSOR_STATS_OID, 138};
-static const oid rcode10AnswersOID[] = {RECURSOR_STATS_OID, 139};
-static const oid rcode11AnswersOID[] = {RECURSOR_STATS_OID, 140};
-static const oid rcode12AnswersOID[] = {RECURSOR_STATS_OID, 141};
-static const oid rcode13AnswersOID[] = {RECURSOR_STATS_OID, 142};
-static const oid rcode14AnswersOID[] = {RECURSOR_STATS_OID, 143};
-static const oid rcode15AnswersOID[] = {RECURSOR_STATS_OID, 144};
-
-static const oid packetCacheContendedOID[] = {RECURSOR_STATS_OID, 145};
-static const oid packetCacheAcquiredOID[] = {RECURSOR_STATS_OID, 146};
+static const oid10 sourceDisallowedNotifyOID = {RECURSOR_STATS_OID, 124};
+static const oid10 zoneDisallowedNotifyOID = {RECURSOR_STATS_OID, 125};
+static const oid10 nonResolvingNameserverEntriesOID = {RECURSOR_STATS_OID, 126};
+static const oid10 maintenanceUSecOID = {RECURSOR_STATS_OID, 127};
+static const oid10 maintenanceCallsOID = {RECURSOR_STATS_OID, 128};
+
+static const oid10 rcode0AnswersOID = {RECURSOR_STATS_OID, 129};
+static const oid10 rcode1AnswersOID = {RECURSOR_STATS_OID, 130};
+static const oid10 rcode2AnswersOID = {RECURSOR_STATS_OID, 131};
+static const oid10 rcode3AnswersOID = {RECURSOR_STATS_OID, 132};
+static const oid10 rcode4AnswersOID = {RECURSOR_STATS_OID, 133};
+static const oid10 rcode5AnswersOID = {RECURSOR_STATS_OID, 134};
+static const oid10 rcode6AnswersOID = {RECURSOR_STATS_OID, 135};
+static const oid10 rcode7AnswersOID = {RECURSOR_STATS_OID, 136};
+static const oid10 rcode8AnswersOID = {RECURSOR_STATS_OID, 137};
+static const oid10 rcode9AnswersOID = {RECURSOR_STATS_OID, 138};
+static const oid10 rcode10AnswersOID = {RECURSOR_STATS_OID, 139};
+static const oid10 rcode11AnswersOID = {RECURSOR_STATS_OID, 140};
+static const oid10 rcode12AnswersOID = {RECURSOR_STATS_OID, 141};
+static const oid10 rcode13AnswersOID = {RECURSOR_STATS_OID, 142};
+static const oid10 rcode14AnswersOID = {RECURSOR_STATS_OID, 143};
+static const oid10 rcode15AnswersOID = {RECURSOR_STATS_OID, 144};
+
+static const oid10 packetCacheContendedOID = {RECURSOR_STATS_OID, 145};
+static const oid10 packetCacheAcquiredOID = {RECURSOR_STATS_OID, 146};
+static const oid10 nodEventsOID = {RECURSOR_STATS_OID, 147};
+static const oid10 udrEventsOID = {RECURSOR_STATS_OID, 148};
static std::unordered_map<oid, std::string> s_statsMap;
return SNMP_ERR_GENERR;
}
- const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
- if (it == s_statsMap.end()) {
+ const auto& iter = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) it's the API
+ if (iter == s_statsMap.end()) {
return SNMP_ERR_GENERR;
}
- auto value = getStatByName(it->second);
+ auto value = getStatByName(iter->second);
if (value) {
return RecursorSNMPAgent::setCounter64Value(requests, *value);
}
- else {
- return RecursorSNMPAgent::setCounter64Value(requests, 0);
- }
+ return RecursorSNMPAgent::setCounter64Value(requests, 0);
}
static int handleDisabledCounter64Stats(netsnmp_mib_handler* /* handler */,
return RecursorSNMPAgent::setCounter64Value(requests, 0);
}
-static void registerCounter64Stat(const std::string& name, const oid statOID[], size_t statOIDLength)
+static void registerCounter64Stat(const std::string& name, const oid10& statOID)
{
- if (statOIDLength != OID_LENGTH(questionsOID)) {
+ if (statOID.size() != OID_LENGTH(questionsOID)) {
SLOG(g_log << Logger::Error << "Invalid OID for SNMP Counter64 statistic " << name << endl,
g_slog->withName("snmp")->info(Logr::Error, "Invalid OID for SNMP Counter64 statistic", "name", Logging::Loggable(name)));
return;
}
- if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
+ if (s_statsMap.find(statOID.at(statOID.size() - 1)) != s_statsMap.end()) {
SLOG(g_log << Logger::Error << "OID for SNMP Counter64 statistic " << name << " has already been registered" << endl,
g_slog->withName("snmp")->info(Logr::Error, "OID for SNMP Counter64 statistic has already been registered", "name", Logging::Loggable(name)));
return;
}
- s_statsMap[statOID[statOIDLength - 1]] = name.c_str();
+ s_statsMap[statOID.at(statOID.size() - 1)] = name;
netsnmp_register_scalar(netsnmp_create_handler_registration(name.c_str(),
isStatDisabled(StatComponent::SNMP, name) ? handleDisabledCounter64Stats : handleCounter64Stats,
- statOID,
- statOIDLength,
+ statOID.data(),
+ statOID.size(),
HANDLER_CAN_RONLY));
}
std::shared_ptr<RecursorSNMPAgent> g_snmpAgent{nullptr};
-bool RecursorSNMPAgent::sendCustomTrap(const std::string& reason)
+bool RecursorSNMPAgent::sendCustomTrap([[maybe_unused]] const std::string& reason)
{
#ifdef HAVE_NET_SNMP
netsnmp_variable_list* varList = nullptr;
snmp_varlist_add_variable(&varList,
- snmpTrapOID,
- snmpTrapOIDLen,
+ snmpTrapOID.data(),
+ snmpTrapOID.size(),
ASN_OBJECT_ID,
- customTrapOID,
- OID_LENGTH(customTrapOID) * sizeof(oid));
+ customTrapOID.data(),
+ customTrapOID.size() * sizeof(oid));
snmp_varlist_add_variable(&varList,
- trapReasonOID,
- OID_LENGTH(trapReasonOID),
+ trapReasonOID.data(),
+ trapReasonOID.size(),
ASN_OCTET_STR,
reason.c_str(),
reason.size());
- return sendTrap(d_trapPipe[1], varList);
+ return sendTrap(d_sender, varList);
#endif /* HAVE_NET_SNMP */
return true;
}
-RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string& masterSocket) :
- SNMPAgent(name, masterSocket)
+RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string& daemonSocket) :
+ SNMPAgent(name, daemonSocket)
{
#ifdef HAVE_NET_SNMP
- registerCounter64Stat("questions", questionsOID, OID_LENGTH(questionsOID));
- registerCounter64Stat("ipv6-questions", ipv6QuestionsOID, OID_LENGTH(ipv6QuestionsOID));
- registerCounter64Stat("tcp-questions", tcpQuestionsOID, OID_LENGTH(tcpQuestionsOID));
- registerCounter64Stat("cache-hits", cacheHitsOID, OID_LENGTH(cacheHitsOID));
- registerCounter64Stat("cache-misses", cacheMissesOID, OID_LENGTH(cacheMissesOID));
- registerCounter64Stat("cache-entries", cacheEntriesOID, OID_LENGTH(cacheEntriesOID));
- registerCounter64Stat("cache-bytes", cacheBytesOID, OID_LENGTH(cacheBytesOID));
- registerCounter64Stat("packetcache-hits", packetcacheHitsOID, OID_LENGTH(packetcacheHitsOID));
- registerCounter64Stat("packetcache-misses", packetcacheMissesOID, OID_LENGTH(packetcacheMissesOID));
- registerCounter64Stat("packetcache-entries", packetcacheEntriesOID, OID_LENGTH(packetcacheEntriesOID));
- registerCounter64Stat("packetcache-bytes", packetcacheBytesOID, OID_LENGTH(packetcacheBytesOID));
- registerCounter64Stat("malloc-bytes", mallocBytesOID, OID_LENGTH(mallocBytesOID));
- registerCounter64Stat("servfail-answers", servfailAnswersOID, OID_LENGTH(servfailAnswersOID));
- registerCounter64Stat("nxdomain-answers", nxdomainAnswersOID, OID_LENGTH(nxdomainAnswersOID));
- registerCounter64Stat("noerror-answers", noerrorAnswersOID, OID_LENGTH(noerrorAnswersOID));
- registerCounter64Stat("unauthorized-udp", unauthorizedUdpOID, OID_LENGTH(unauthorizedUdpOID));
- registerCounter64Stat("unauthorized-tcp", unauthorizedTcpOID, OID_LENGTH(unauthorizedTcpOID));
- registerCounter64Stat("source-disallowed-notify", sourceDisallowedNotifyOID, OID_LENGTH(sourceDisallowedNotifyOID));
- registerCounter64Stat("zone-disallowed-notify", zoneDisallowedNotifyOID, OID_LENGTH(zoneDisallowedNotifyOID));
- registerCounter64Stat("tcp-client-overflow", tcpClientOverflowOID, OID_LENGTH(tcpClientOverflowOID));
- registerCounter64Stat("client-parse-errors", clientParseErrorsOID, OID_LENGTH(clientParseErrorsOID));
- registerCounter64Stat("server-parse-errors", serverParseErrorsOID, OID_LENGTH(serverParseErrorsOID));
- registerCounter64Stat("too-old-drops", tooOldDropsOID, OID_LENGTH(tooOldDropsOID));
- registerCounter64Stat("query-pipe-full-drops", queryPipeFullDropsOID, OID_LENGTH(queryPipeFullDropsOID));
- registerCounter64Stat("truncated-drops", truncatedDropsOID, OID_LENGTH(truncatedDropsOID));
- registerCounter64Stat("empty-queries", emptyQueriesOID, OID_LENGTH(emptyQueriesOID));
- registerCounter64Stat("variable-responses", variableResponsesOID, OID_LENGTH(variableResponsesOID));
- registerCounter64Stat("answers0-1", answers01OID, OID_LENGTH(answers01OID));
- registerCounter64Stat("answers1-10", answers110OID, OID_LENGTH(answers110OID));
- registerCounter64Stat("answers10-100", answers10100OID, OID_LENGTH(answers10100OID));
- registerCounter64Stat("answers100-1000", answers1001000OID, OID_LENGTH(answers1001000OID));
- registerCounter64Stat("answers-slow", answersSlowOID, OID_LENGTH(answersSlowOID));
- registerCounter64Stat("auth4-answers0-1", auth4Answers01OID, OID_LENGTH(auth4Answers01OID));
- registerCounter64Stat("auth4-answers1-10", auth4Answers110OID, OID_LENGTH(auth4Answers110OID));
- registerCounter64Stat("auth4-answers10-100", auth4Answers10100OID, OID_LENGTH(auth4Answers10100OID));
- registerCounter64Stat("auth4-answers100-1000", auth4Answers1001000OID, OID_LENGTH(auth4Answers1001000OID));
- registerCounter64Stat("auth4-answers-slow", auth4AnswersslowOID, OID_LENGTH(auth4AnswersslowOID));
- registerCounter64Stat("auth6-answers0-1", auth6Answers01OID, OID_LENGTH(auth6Answers01OID));
- registerCounter64Stat("auth6-answers1-10", auth6Answers110OID, OID_LENGTH(auth6Answers110OID));
- registerCounter64Stat("auth6-answers10-100", auth6Answers10100OID, OID_LENGTH(auth6Answers10100OID));
- registerCounter64Stat("auth6-answers100-1000", auth6Answers1001000OID, OID_LENGTH(auth6Answers1001000OID));
- registerCounter64Stat("auth6-answers-slow", auth6AnswersSlowOID, OID_LENGTH(auth6AnswersSlowOID));
- registerCounter64Stat("qa-latency", qaLatencyOID, OID_LENGTH(qaLatencyOID));
- registerCounter64Stat("unexpected-packets", unexpectedPacketsOID, OID_LENGTH(unexpectedPacketsOID));
- registerCounter64Stat("case-mismatches", caseMismatchesOID, OID_LENGTH(caseMismatchesOID));
- registerCounter64Stat("spoof-prevents", spoofPreventsOID, OID_LENGTH(spoofPreventsOID));
- registerCounter64Stat("nsset-invalidations", nssetInvalidationsOID, OID_LENGTH(nssetInvalidationsOID));
- registerCounter64Stat("resource-limits", resourceLimitsOID, OID_LENGTH(resourceLimitsOID));
- registerCounter64Stat("over-capacity-drops", overCapacityDropsOID, OID_LENGTH(overCapacityDropsOID));
- registerCounter64Stat("policy-drops", policyDropsOID, OID_LENGTH(policyDropsOID));
- registerCounter64Stat("no-packet-error", noPacketErrorOID, OID_LENGTH(noPacketErrorOID));
- registerCounter64Stat("dlg-only-drops", dlgOnlyDropsOID, OID_LENGTH(dlgOnlyDropsOID));
- registerCounter64Stat("ignored-packets", ignoredPacketsOID, OID_LENGTH(ignoredPacketsOID));
- registerCounter64Stat("max-mthread-stack", maxMthreadStackOID, OID_LENGTH(maxMthreadStackOID));
- registerCounter64Stat("negcache-entries", negcacheEntriesOID, OID_LENGTH(negcacheEntriesOID));
- registerCounter64Stat("throttle-entries", throttleEntriesOID, OID_LENGTH(throttleEntriesOID));
- registerCounter64Stat("nsspeeds-entries", nsspeedsEntriesOID, OID_LENGTH(nsspeedsEntriesOID));
- registerCounter64Stat("failed-host-entries", failedHostEntriesOID, OID_LENGTH(failedHostEntriesOID));
- registerCounter64Stat("concurrent-queries", concurrentQueriesOID, OID_LENGTH(concurrentQueriesOID));
- registerCounter64Stat("security-status", securityStatusOID, OID_LENGTH(securityStatusOID));
- registerCounter64Stat("outgoing-timeouts", outgoingTimeoutsOID, OID_LENGTH(outgoingTimeoutsOID));
- registerCounter64Stat("outgoing4-timeouts", outgoing4TimeoutsOID, OID_LENGTH(outgoing4TimeoutsOID));
- registerCounter64Stat("outgoing6-timeouts", outgoing6TimeoutsOID, OID_LENGTH(outgoing6TimeoutsOID));
- registerCounter64Stat("tcp-outqueries", tcpOutqueriesOID, OID_LENGTH(tcpOutqueriesOID));
- registerCounter64Stat("all-outqueries", allOutqueriesOID, OID_LENGTH(allOutqueriesOID));
- registerCounter64Stat("ipv6-outqueries", ipv6OutqueriesOID, OID_LENGTH(ipv6OutqueriesOID));
- registerCounter64Stat("throttled-outqueries", throttledOutqueriesOID, OID_LENGTH(throttledOutqueriesOID));
- registerCounter64Stat("dont-outqueries", dontOutqueriesOID, OID_LENGTH(dontOutqueriesOID));
- registerCounter64Stat("qname-min-fallback-success", qnameMinFallbackSuccessOID, OID_LENGTH(qnameMinFallbackSuccessOID));
- registerCounter64Stat("unreachables", unreachablesOID, OID_LENGTH(unreachablesOID));
- registerCounter64Stat("chain-resends", chainResendsOID, OID_LENGTH(chainResendsOID));
- registerCounter64Stat("tcp-clients", tcpClientsOID, OID_LENGTH(tcpClientsOID));
+ registerCounter64Stat("questions", questionsOID);
+ registerCounter64Stat("ipv6-questions", ipv6QuestionsOID);
+ registerCounter64Stat("tcp-questions", tcpQuestionsOID);
+ registerCounter64Stat("cache-hits", cacheHitsOID);
+ registerCounter64Stat("cache-misses", cacheMissesOID);
+ registerCounter64Stat("cache-entries", cacheEntriesOID);
+ registerCounter64Stat("cache-bytes", cacheBytesOID);
+ registerCounter64Stat("packetcache-hits", packetcacheHitsOID);
+ registerCounter64Stat("packetcache-misses", packetcacheMissesOID);
+ registerCounter64Stat("packetcache-entries", packetcacheEntriesOID);
+ registerCounter64Stat("packetcache-bytes", packetcacheBytesOID);
+ registerCounter64Stat("malloc-bytes", mallocBytesOID);
+ registerCounter64Stat("servfail-answers", servfailAnswersOID);
+ registerCounter64Stat("nxdomain-answers", nxdomainAnswersOID);
+ registerCounter64Stat("noerror-answers", noerrorAnswersOID);
+ registerCounter64Stat("unauthorized-udp", unauthorizedUdpOID);
+ registerCounter64Stat("unauthorized-tcp", unauthorizedTcpOID);
+ registerCounter64Stat("source-disallowed-notify", sourceDisallowedNotifyOID);
+ registerCounter64Stat("zone-disallowed-notify", zoneDisallowedNotifyOID);
+ registerCounter64Stat("tcp-client-overflow", tcpClientOverflowOID);
+ registerCounter64Stat("client-parse-errors", clientParseErrorsOID);
+ registerCounter64Stat("server-parse-errors", serverParseErrorsOID);
+ registerCounter64Stat("too-old-drops", tooOldDropsOID);
+ registerCounter64Stat("query-pipe-full-drops", queryPipeFullDropsOID);
+ registerCounter64Stat("truncated-drops", truncatedDropsOID);
+ registerCounter64Stat("empty-queries", emptyQueriesOID);
+ registerCounter64Stat("variable-responses", variableResponsesOID);
+ registerCounter64Stat("answers0-1", answers01OID);
+ registerCounter64Stat("answers1-10", answers110OID);
+ registerCounter64Stat("answers10-100", answers10100OID);
+ registerCounter64Stat("answers100-1000", answers1001000OID);
+ registerCounter64Stat("answers-slow", answersSlowOID);
+ registerCounter64Stat("auth4-answers0-1", auth4Answers01OID);
+ registerCounter64Stat("auth4-answers1-10", auth4Answers110OID);
+ registerCounter64Stat("auth4-answers10-100", auth4Answers10100OID);
+ registerCounter64Stat("auth4-answers100-1000", auth4Answers1001000OID);
+ registerCounter64Stat("auth4-answers-slow", auth4AnswersslowOID);
+ registerCounter64Stat("auth6-answers0-1", auth6Answers01OID);
+ registerCounter64Stat("auth6-answers1-10", auth6Answers110OID);
+ registerCounter64Stat("auth6-answers10-100", auth6Answers10100OID);
+ registerCounter64Stat("auth6-answers100-1000", auth6Answers1001000OID);
+ registerCounter64Stat("auth6-answers-slow", auth6AnswersSlowOID);
+ registerCounter64Stat("qa-latency", qaLatencyOID);
+ registerCounter64Stat("unexpected-packets", unexpectedPacketsOID);
+ registerCounter64Stat("case-mismatches", caseMismatchesOID);
+ registerCounter64Stat("spoof-prevents", spoofPreventsOID);
+ registerCounter64Stat("nsset-invalidations", nssetInvalidationsOID);
+ registerCounter64Stat("resource-limits", resourceLimitsOID);
+ registerCounter64Stat("over-capacity-drops", overCapacityDropsOID);
+ registerCounter64Stat("policy-drops", policyDropsOID);
+ registerCounter64Stat("no-packet-error", noPacketErrorOID);
+ registerCounter64Stat("dlg-only-drops", dlgOnlyDropsOID);
+ registerCounter64Stat("ignored-packets", ignoredPacketsOID);
+ registerCounter64Stat("max-mthread-stack", maxMthreadStackOID);
+ registerCounter64Stat("negcache-entries", negcacheEntriesOID);
+ registerCounter64Stat("throttle-entries", throttleEntriesOID);
+ registerCounter64Stat("nsspeeds-entries", nsspeedsEntriesOID);
+ registerCounter64Stat("failed-host-entries", failedHostEntriesOID);
+ registerCounter64Stat("concurrent-queries", concurrentQueriesOID);
+ registerCounter64Stat("security-status", securityStatusOID);
+ registerCounter64Stat("outgoing-timeouts", outgoingTimeoutsOID);
+ registerCounter64Stat("outgoing4-timeouts", outgoing4TimeoutsOID);
+ registerCounter64Stat("outgoing6-timeouts", outgoing6TimeoutsOID);
+ registerCounter64Stat("tcp-outqueries", tcpOutqueriesOID);
+ registerCounter64Stat("all-outqueries", allOutqueriesOID);
+ registerCounter64Stat("ipv6-outqueries", ipv6OutqueriesOID);
+ registerCounter64Stat("throttled-outqueries", throttledOutqueriesOID);
+ registerCounter64Stat("dont-outqueries", dontOutqueriesOID);
+ registerCounter64Stat("qname-min-fallback-success", qnameMinFallbackSuccessOID);
+ registerCounter64Stat("unreachables", unreachablesOID);
+ registerCounter64Stat("chain-resends", chainResendsOID);
+ registerCounter64Stat("tcp-clients", tcpClientsOID);
#ifdef __linux__
- registerCounter64Stat("udp-recvbuf-errors", udpRecvbufErrorsOID, OID_LENGTH(udpRecvbufErrorsOID));
- registerCounter64Stat("udp-sndbuf-errors", udpSndbufErrorsOID, OID_LENGTH(udpSndbufErrorsOID));
- registerCounter64Stat("udp-noport-errors", udpNoportErrorsOID, OID_LENGTH(udpNoportErrorsOID));
- registerCounter64Stat("udp-in-errors", udpinErrorsOID, OID_LENGTH(udpinErrorsOID));
- registerCounter64Stat("udp-in-csums-errors", udpInCsumErrorsOID, OID_LENGTH(udpInCsumErrorsOID));
- registerCounter64Stat("udp6-recvbuf-errors", udp6RecvbufErrorsOID, OID_LENGTH(udp6RecvbufErrorsOID));
- registerCounter64Stat("udp6-sndbuf-errors", udp6SndbufErrorsOID, OID_LENGTH(udp6SndbufErrorsOID));
- registerCounter64Stat("udp6-noport-errors", udp6NoportErrorsOID, OID_LENGTH(udp6NoportErrorsOID));
- registerCounter64Stat("udp6-in-errors", udp6InErrorsOID, OID_LENGTH(udp6InErrorsOID));
- registerCounter64Stat("udp6-in-csums-errors", udp6InCsumErrorsOID, OID_LENGTH(udp6InCsumErrorsOID));
+ registerCounter64Stat("udp-recvbuf-errors", udpRecvbufErrorsOID);
+ registerCounter64Stat("udp-sndbuf-errors", udpSndbufErrorsOID);
+ registerCounter64Stat("udp-noport-errors", udpNoportErrorsOID);
+ registerCounter64Stat("udp-in-errors", udpinErrorsOID);
+ registerCounter64Stat("udp-in-csums-errors", udpInCsumErrorsOID);
+ registerCounter64Stat("udp6-recvbuf-errors", udp6RecvbufErrorsOID);
+ registerCounter64Stat("udp6-sndbuf-errors", udp6SndbufErrorsOID);
+ registerCounter64Stat("udp6-noport-errors", udp6NoportErrorsOID);
+ registerCounter64Stat("udp6-in-errors", udp6InErrorsOID);
+ registerCounter64Stat("udp6-in-csums-errors", udp6InCsumErrorsOID);
#endif /* __linux__ */
- registerCounter64Stat("edns-ping-matches", ednsPingMatchesOID, OID_LENGTH(ednsPingMatchesOID));
- registerCounter64Stat("edns-ping-mismatches", ednsPingMismatchesOID, OID_LENGTH(ednsPingMismatchesOID));
- registerCounter64Stat("dnssec-queries", dnssecQueriesOID, OID_LENGTH(dnssecQueriesOID));
- registerCounter64Stat("dnssec-authentic-data-queries", dnssecAuthenticDataQueriesOID, OID_LENGTH(dnssecAuthenticDataQueriesOID));
- registerCounter64Stat("dnssec-check-disabled-queries", dnssecCheckDisabledQueriesOID, OID_LENGTH(dnssecCheckDisabledQueriesOID));
- registerCounter64Stat("noping-outqueries", nopingOutqueriesOID, OID_LENGTH(nopingOutqueriesOID));
- registerCounter64Stat("noedns-outqueries", noednsOutqueriesOID, OID_LENGTH(noednsOutqueriesOID));
- registerCounter64Stat("uptime", uptimeOID, OID_LENGTH(uptimeOID));
- registerCounter64Stat("real-memory-usage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID));
- registerCounter64Stat("fd-usage", fdUsageOID, OID_LENGTH(fdUsageOID));
- registerCounter64Stat("user-msec", userMsecOID, OID_LENGTH(userMsecOID));
- registerCounter64Stat("sys-msec", sysMsecOID, OID_LENGTH(sysMsecOID));
- registerCounter64Stat("dnssec-validations", dnssecValidationsOID, OID_LENGTH(dnssecValidationsOID));
- registerCounter64Stat("dnssec-result-insecure", dnssecResultInsecureOID, OID_LENGTH(dnssecResultInsecureOID));
- registerCounter64Stat("dnssec-result-secure", dnssecResultSecureOID, OID_LENGTH(dnssecResultSecureOID));
- registerCounter64Stat("dnssec-result-bogus", dnssecResultBogusOID, OID_LENGTH(dnssecResultBogusOID));
- registerCounter64Stat("dnssec-result-indeterminate", dnssecResultIndeterminateOID, OID_LENGTH(dnssecResultIndeterminateOID));
- registerCounter64Stat("dnssec-result-nta", dnssecResultNtaOID, OID_LENGTH(dnssecResultNtaOID));
- registerCounter64Stat("policy-result-noaction", policyResultNoactionOID, OID_LENGTH(policyResultNoactionOID));
- registerCounter64Stat("policy-result-drop", policyResultDropOID, OID_LENGTH(policyResultDropOID));
- registerCounter64Stat("policy-result-nxdomain", policyResultNxdomainOID, OID_LENGTH(policyResultNxdomainOID));
- registerCounter64Stat("policy-result-nodata", policyResultNodataOID, OID_LENGTH(policyResultNodataOID));
- registerCounter64Stat("policy-result-truncate", policyResultTruncateOID, OID_LENGTH(policyResultTruncateOID));
- registerCounter64Stat("policy-result-custom", policyResultCustomOID, OID_LENGTH(policyResultCustomOID));
- registerCounter64Stat("special-memory-usage", specialMemoryUsageOID, OID_LENGTH(specialMemoryUsageOID));
- registerCounter64Stat("rebalanced-queries", rebalancedQueriesOID, OID_LENGTH(rebalancedQueriesOID));
- registerCounter64Stat("proxy-protocol-invalid", proxyProtocolInvalidOID, OID_LENGTH(proxyProtocolInvalidOID));
- registerCounter64Stat("record-cache-contended", recordCacheContendedOID, OID_LENGTH(recordCacheContendedOID));
- registerCounter64Stat("record-cache-acquired", recordCacheAcquiredOID, OID_LENGTH(recordCacheAcquiredOID));
- registerCounter64Stat("nod-lookups-dropped-oversize", nodLookupsDroppedOversizeOID, OID_LENGTH(nodLookupsDroppedOversizeOID));
- registerCounter64Stat("tasqueue-pushed", taskQueuePushedOID, OID_LENGTH(taskQueuePushedOID));
- registerCounter64Stat("taskqueue-expired", taskQueueExpiredOID, OID_LENGTH(taskQueueExpiredOID));
- registerCounter64Stat("taskqueue-size", taskQueueSizeOID, OID_LENGTH(taskQueueSizeOID));
- registerCounter64Stat("aggressive-nsec-cache-entries", aggressiveNSECCacheEntriesOID, OID_LENGTH(aggressiveNSECCacheEntriesOID));
- registerCounter64Stat("aggressive-nsec-cache-nsec-hits", aggressiveNSECCacheNSECHitsOID, OID_LENGTH(aggressiveNSECCacheNSECHitsOID));
- registerCounter64Stat("aggressive-nsec-cache-nsec3-hits", aggressiveNSECCacheNSEC3HitsOID, OID_LENGTH(aggressiveNSECCacheNSEC3HitsOID));
- registerCounter64Stat("aggressive-nsec-cache-nsec-wc-hits", aggressiveNSECCacheNSECWCHitsOID, OID_LENGTH(aggressiveNSECCacheNSECWCHitsOID));
- registerCounter64Stat("aggressive-nsec-cache-nsec-wc3-hits", aggressiveNSECCacheNSEC3WCHitsOID, OID_LENGTH(aggressiveNSECCacheNSEC3WCHitsOID));
- registerCounter64Stat("dot-outqueries", dotOutqueriesOID, OID_LENGTH(dotOutqueriesOID));
- registerCounter64Stat("dns64-prefix-answers", dns64PrefixAnswers, OID_LENGTH(dns64PrefixAnswers));
- registerCounter64Stat("almost-expired-pushed", almostExpiredPushed, OID_LENGTH(almostExpiredPushed));
- registerCounter64Stat("almost-expired-run", almostExpiredRun, OID_LENGTH(almostExpiredRun));
- registerCounter64Stat("almost-expired-exceptions", almostExpiredExceptions, OID_LENGTH(almostExpiredExceptions));
- registerCounter64Stat("non-resolving-nameserver-entries", nonResolvingNameserverEntriesOID, OID_LENGTH(nonResolvingNameserverEntriesOID));
- registerCounter64Stat("maintenance-usec", maintenanceUSecOID, OID_LENGTH(maintenanceUSecOID));
- registerCounter64Stat("maintenance-calls", maintenanceCallsOID, OID_LENGTH(maintenanceCallsOID));
- registerCounter64Stat("packetcache-contended", packetCacheContendedOID, OID_LENGTH(packetCacheContendedOID));
- registerCounter64Stat("packetcache-acquired", packetCacheAcquiredOID, OID_LENGTH(packetCacheAcquiredOID));
-
-#define RCODE(num) registerCounter64Stat("auth-" + RCode::to_short_s(num) + "-answers", rcode##num##AnswersOID, OID_LENGTH(rcode##num##AnswersOID))
+ registerCounter64Stat("edns-ping-matches", ednsPingMatchesOID);
+ registerCounter64Stat("edns-ping-mismatches", ednsPingMismatchesOID);
+ registerCounter64Stat("dnssec-queries", dnssecQueriesOID);
+ registerCounter64Stat("dnssec-authentic-data-queries", dnssecAuthenticDataQueriesOID);
+ registerCounter64Stat("dnssec-check-disabled-queries", dnssecCheckDisabledQueriesOID);
+ registerCounter64Stat("noping-outqueries", nopingOutqueriesOID);
+ registerCounter64Stat("noedns-outqueries", noednsOutqueriesOID);
+ registerCounter64Stat("uptime", uptimeOID);
+ registerCounter64Stat("real-memory-usage", realMemoryUsageOID);
+ registerCounter64Stat("fd-usage", fdUsageOID);
+ registerCounter64Stat("user-msec", userMsecOID);
+ registerCounter64Stat("sys-msec", sysMsecOID);
+ registerCounter64Stat("dnssec-validations", dnssecValidationsOID);
+ registerCounter64Stat("dnssec-result-insecure", dnssecResultInsecureOID);
+ registerCounter64Stat("dnssec-result-secure", dnssecResultSecureOID);
+ registerCounter64Stat("dnssec-result-bogus", dnssecResultBogusOID);
+ registerCounter64Stat("dnssec-result-indeterminate", dnssecResultIndeterminateOID);
+ registerCounter64Stat("dnssec-result-nta", dnssecResultNtaOID);
+ registerCounter64Stat("policy-result-noaction", policyResultNoactionOID);
+ registerCounter64Stat("policy-result-drop", policyResultDropOID);
+ registerCounter64Stat("policy-result-nxdomain", policyResultNxdomainOID);
+ registerCounter64Stat("policy-result-nodata", policyResultNodataOID);
+ registerCounter64Stat("policy-result-truncate", policyResultTruncateOID);
+ registerCounter64Stat("policy-result-custom", policyResultCustomOID);
+ registerCounter64Stat("special-memory-usage", specialMemoryUsageOID);
+ registerCounter64Stat("rebalanced-queries", rebalancedQueriesOID);
+ registerCounter64Stat("proxy-protocol-invalid", proxyProtocolInvalidOID);
+ registerCounter64Stat("record-cache-contended", recordCacheContendedOID);
+ registerCounter64Stat("record-cache-acquired", recordCacheAcquiredOID);
+ registerCounter64Stat("nod-lookups-dropped-oversize", nodLookupsDroppedOversizeOID);
+ registerCounter64Stat("tasqueue-pushed", taskQueuePushedOID);
+ registerCounter64Stat("taskqueue-expired", taskQueueExpiredOID);
+ registerCounter64Stat("taskqueue-size", taskQueueSizeOID);
+ registerCounter64Stat("aggressive-nsec-cache-entries", aggressiveNSECCacheEntriesOID);
+ registerCounter64Stat("aggressive-nsec-cache-nsec-hits", aggressiveNSECCacheNSECHitsOID);
+ registerCounter64Stat("aggressive-nsec-cache-nsec3-hits", aggressiveNSECCacheNSEC3HitsOID);
+ registerCounter64Stat("aggressive-nsec-cache-nsec-wc-hits", aggressiveNSECCacheNSECWCHitsOID);
+ registerCounter64Stat("aggressive-nsec-cache-nsec-wc3-hits", aggressiveNSECCacheNSEC3WCHitsOID);
+ registerCounter64Stat("dot-outqueries", dotOutqueriesOID);
+ registerCounter64Stat("dns64-prefix-answers", dns64PrefixAnswers);
+ registerCounter64Stat("almost-expired-pushed", almostExpiredPushed);
+ registerCounter64Stat("almost-expired-run", almostExpiredRun);
+ registerCounter64Stat("almost-expired-exceptions", almostExpiredExceptions);
+ registerCounter64Stat("non-resolving-nameserver-entries", nonResolvingNameserverEntriesOID);
+ registerCounter64Stat("maintenance-usec", maintenanceUSecOID);
+ registerCounter64Stat("maintenance-calls", maintenanceCallsOID);
+
+#define RCODE(num) registerCounter64Stat("auth-" + RCode::to_short_s(num) + "-answers", rcode##num##AnswersOID) // NOLINT(cppcoreguidelines-macro-usage)
RCODE(0);
RCODE(1);
RCODE(2);
RCODE(14);
RCODE(15);
+ registerCounter64Stat("packetcache-contended", packetCacheContendedOID);
+ registerCounter64Stat("packetcache-acquired", packetCacheAcquiredOID);
+ registerCounter64Stat("nod-events", nodEventsOID);
+ registerCounter64Stat("udr-events", udrEventsOID);
+
#endif /* HAVE_NET_SNMP */
}
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+
#include "rec-taskqueue.hh"
#include "taskqueue.hh"
#include "lock.hh"
class TimedSet
{
public:
- TimedSet(time_t t) :
- d_expiry_seconds(t)
+ TimedSet(time_t time) :
+ d_expiry_seconds(time)
{
}
// This purge is relatively cheap, as we're walking an ordered index
uint64_t erased = 0;
auto& ind = d_set.template get<time_t>();
- auto it = ind.begin();
- while (it != ind.end()) {
- if (it->d_ttd < now) {
+ auto iter = ind.begin();
+ while (iter != ind.end()) {
+ if (iter->d_ttd < now) {
++erased;
- it = ind.erase(it);
+ iter = ind.erase(iter);
}
else {
break;
private:
struct Entry
{
- Entry(const pdns::ResolveTask& task, time_t ttd) :
- d_task(task), d_ttd(ttd) {}
+ Entry(pdns::ResolveTask task, time_t ttd) :
+ d_task(std::move(task)), d_ttd(ttd) {}
pdns::ResolveTask d_task;
time_t d_ttd;
};
- typedef multi_index_container<Entry,
- indexed_by<
- ordered_unique<tag<pdns::ResolveTask>, member<Entry, pdns::ResolveTask, &Entry::d_task>>,
- ordered_non_unique<tag<time_t>, member<Entry, time_t, &Entry::d_ttd>>>>
- timed_set_t;
+ using timed_set_t = multi_index_container<
+ Entry,
+ indexed_by<ordered_unique<tag<pdns::ResolveTask>,
+ member<Entry, pdns::ResolveTask, &Entry::d_task>>,
+ ordered_non_unique<tag<time_t>,
+ member<Entry, time_t, &Entry::d_ttd>>>>;
timed_set_t d_set;
time_t d_expiry_seconds;
unsigned int d_count{0};
static struct taskstats s_almost_expired_tasks;
static struct taskstats s_resolve_tasks;
-static void resolve(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
+// forceNoQM is true means resolve using no qm, false means use default value
+static void resolveInternal(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task, bool forceNoQM) noexcept
{
auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "netmask", Logging::Loggable(task.d_netmask.empty() ? "" : task.d_netmask.toString()));
const string msg = "Exception while running a background ResolveTask";
- SyncRes sr(now);
+ SyncRes resolver(now);
vector<DNSRecord> ret;
- sr.setRefreshAlmostExpired(task.d_refreshMode);
- sr.setQuerySource(task.d_netmask);
- bool ex = true;
+ resolver.setRefreshAlmostExpired(task.d_refreshMode);
+ resolver.setQuerySource(task.d_netmask);
+ if (forceNoQM) {
+ resolver.setQNameMinimization(false);
+ }
+ bool exceptionOccurred = true;
try {
log->info(Logr::Debug, "resolving", "refresh", Logging::Loggable(task.d_refreshMode));
- int res = sr.beginResolve(task.d_qname, QType(task.d_qtype), QClass::IN, ret);
- ex = false;
+ int res = resolver.beginResolve(task.d_qname, QType(task.d_qtype), QClass::IN, ret);
+ exceptionOccurred = false;
log->info(Logr::Debug, "done", "rcode", Logging::Loggable(res), "records", Logging::Loggable(ret.size()));
}
catch (const std::exception& e) {
- log->error(Logr::Error, msg, e.what());
+ log->error(Logr::Warning, msg, e.what());
}
catch (const PDNSException& e) {
- log->error(Logr::Error, msg, e.reason);
+ log->error(Logr::Warning, msg, e.reason);
}
catch (const ImmediateServFailException& e) {
if (logErrors) {
- log->error(Logr::Error, msg, e.reason);
+ log->error(Logr::Warning, msg, e.reason);
}
}
catch (const PolicyHitException& e) {
if (logErrors) {
- log->error(Logr::Notice, msg, "PolicyHit");
+ log->error(Logr::Warning, msg, "PolicyHit");
}
}
catch (...) {
- log->error(Logr::Error, msg, "Unexpectec exception");
+ log->error(Logr::Warning, msg, "Unexpected exception");
}
- if (ex) {
+ if (exceptionOccurred) {
if (task.d_refreshMode) {
++s_almost_expired_tasks.exceptions;
}
}
}
+static void resolveForceNoQM(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
+{
+ resolveInternal(now, logErrors, task, true);
+}
+
+static void resolve(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
+{
+ resolveInternal(now, logErrors, task, false);
+}
+
static void tryDoT(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
{
auto log = g_slog->withName("taskq")->withValues("method", Logging::Loggable("tryDoT"), "name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "ip", Logging::Loggable(task.d_ip));
const string msg = "Exception while running a background tryDoT task";
- SyncRes sr(now);
+ SyncRes resolver(now);
vector<DNSRecord> ret;
- sr.setRefreshAlmostExpired(false);
- bool ex = true;
+ resolver.setRefreshAlmostExpired(false);
+ bool exceptionOccurred = true;
try {
log->info(Logr::Debug, "trying DoT");
- bool ok = sr.tryDoT(task.d_qname, QType(task.d_qtype), task.d_nsname, task.d_ip, now.tv_sec);
- ex = false;
- log->info(Logr::Debug, "done", "ok", Logging::Loggable(ok));
+ bool tryOK = resolver.tryDoT(task.d_qname, QType(task.d_qtype), task.d_nsname, task.d_ip, now.tv_sec);
+ exceptionOccurred = false;
+ log->info(Logr::Debug, "done", "ok", Logging::Loggable(tryOK));
}
catch (const std::exception& e) {
- log->error(Logr::Error, msg, e.what());
+ log->error(Logr::Warning, msg, e.what());
}
catch (const PDNSException& e) {
- log->error(Logr::Error, msg, e.reason);
+ log->error(Logr::Warning, msg, e.reason);
}
catch (const ImmediateServFailException& e) {
if (logErrors) {
- log->error(Logr::Error, msg, e.reason);
+ log->error(Logr::Warning, msg, e.reason);
}
}
catch (const PolicyHitException& e) {
}
}
catch (...) {
- log->error(Logr::Error, msg, "Unexpected exception");
+ log->error(Logr::Warning, msg, "Unexpected exception");
}
- if (ex) {
+ if (exceptionOccurred) {
++s_resolve_tasks.exceptions;
}
else {
}
}
-void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline)
+void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline, bool forceQMOff)
{
if (SyncRes::isUnsupported(qtype)) {
auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
log->error(Logr::Error, "Cannot push task", "qtype unsupported");
return;
}
- pdns::ResolveTask task{qname, qtype, deadline, false, resolve, {}, {}, {}};
+ auto func = forceQMOff ? resolveForceNoQM : resolve;
+ pdns::ResolveTask task{qname, qtype, deadline, false, func, {}, {}, {}};
auto lock = s_taskQueue.lock();
bool inserted = lock->rateLimitSet.insert(now, task);
if (inserted) {
}
}
-bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip, time_t deadline, const DNSName& nsname)
+bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ipAddress, time_t deadline, const DNSName& nsname)
{
if (SyncRes::isUnsupported(qtype)) {
auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
return false;
}
- pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ip, nsname, {}};
+ pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ipAddress, nsname, {}};
bool pushed = s_taskQueue.lock()->queue.push(std::move(task));
if (pushed) {
++s_almost_expired_tasks.pushed;
{
return s_almost_expired_tasks.exceptions;
}
+
+bool taskQTypeIsSupported(QType qtype)
+{
+ return !SyncRes::isUnsupported(qtype);
+}
#pragma once
#include <cstdint>
-#include <time.h>
+#include <ctime>
+#include <qtype.hh>
class DNSName;
union ComboAddress;
void runTasks(size_t max, bool logErrors);
bool runTaskOnce(bool logErrors);
void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask);
-void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline);
-bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip, time_t deadline, const DNSName& nsname);
+void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline, bool forceQMOff);
+bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ipAddress, time_t deadline, const DNSName& nsname);
void taskQueueClear();
pdns::ResolveTask taskQueuePop();
uint64_t getAlmostExpiredTasksPushed();
uint64_t getAlmostExpiredTasksRun();
uint64_t getAlmostExpiredTaskExceptions();
+
+bool taskQTypeIsSupported(QType qtype);
dns64prefixanswers,
maintenanceUsec,
maintenanceCalls,
+ nodCount,
+ udrCount,
numberOfCounters
};
#include "mplexer.hh"
#include "uuid-utils.hh"
+// OLD PRE 5.0.0 situation:
+//
+// When pdns-distributes-queries is false with reuseport true (the default since 4.9.0), TCP queries
+// are read and handled by worker threads. If the kernel balancing is OK for TCP sockets (observed
+// to be good on Debian bullseye, but not good on e.g. MacOS), the TCP handling is no extra burden.
+// In the case of MacOS all incoming TCP queries are handled by a single worker, while incoming UDP
+// queries do get distributed round-robin over the worker threads. Do note the TCP queries might
+// need to wait until the g_maxUDPQueriesPerRound is reached.
+//
+// In the case of pdns-distributes-queries true and reuseport false the queries were read and
+// initially processed by the distributor thread(s).
+//
+// Initial processing consist of parsing, calling gettag and checking if we have a packet cache
+// hit. If that does not produce a hit, the query is passed to an mthread in the same way as with
+// UDP queries, but do note that the mthread processing is serviced by the distributor thread. The
+// final answer will be sent by the same distributor thread that originally picked up the query.
+//
+// Changing this, and having incoming TCP queries handled by worker threads is somewhat more complex
+// than UDP, as the socket must remain available in the distributor thread (for reading more
+// queries), but the TCP socket must also be passed to a worker thread so it can write its
+// answer. The in-flight bookkeeping also has to be aware of how a query is handled to do the
+// accounting properly. I am not sure if changing the current setup is worth all this trouble,
+// especially since the default is now to not use pdns-distributes-queries, which works well in many
+// cases.
+//
+// NEW SITUATION SINCE 5.0.0:
+//
+// The drawback mentioned in https://github.com/PowerDNS/pdns/issues/8394 are not longer true, so an
+// alternative approach would be to introduce dedicated TCP worker thread(s).
+//
+// This approach was implemented in https://github.com/PowerDNS/pdns/pull/13195. The distributor and
+// worker thread(s) now no longer process TCP queries.
+
size_t g_tcpMaxQueriesPerConn;
unsigned int g_maxTCPPerClient;
int g_tcpTimeout;
thread_local std::unique_ptr<tcpClientCounts_t> t_tcpClientCounts;
-static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var);
+static void handleRunningTCPQuestion(int fileDesc, FDMultiplexer::funcparam_t& var);
#if 0
#define TCPLOG(tcpsock, x) \
cerr << []() { timeval t; gettimeofday(&t, nullptr); return t.tv_sec % 10 + t.tv_usec/1000000.0; }() << " FD " << (tcpsock) << ' ' << x; \
} while (0)
#else
-#define TCPLOG(pid, x)
+// We do not define this as empty since that produces a duplicate case label warning from clang-tidy
+#define TCPLOG(pid, x) /* NOLINT(cppcoreguidelines-macro-usage) */ \
+ while (false) { \
+ cerr << x; /* NOLINT(bugprone-macro-parentheses) */ \
+ }
#endif
std::atomic<uint32_t> TCPConnection::s_currentConnections;
-TCPConnection::TCPConnection(int fd, const ComboAddress& addr) :
- data(2, 0), d_remote(addr), d_fd(fd)
+TCPConnection::TCPConnection(int fileDesc, const ComboAddress& addr) :
+ data(2, 0), d_remote(addr), d_fd(fileDesc)
{
++s_currentConnections;
(*t_tcpClientCounts)[d_remote]++;
TCPConnection::~TCPConnection()
{
try {
- if (closesocket(d_fd) < 0)
+ if (closesocket(d_fd) < 0) {
SLOG(g_log << Logger::Error << "Error closing socket for TCPConnection" << endl,
g_slogtcpin->info(Logr::Error, "Error closing socket for TCPConnection"));
+ }
}
catch (const PDNSException& e) {
SLOG(g_log << Logger::Error << "Error closing TCPConnection socket: " << e.reason << endl,
g_slogtcpin->error(Logr::Error, e.reason, "Error closing TCPConnection socket", "exception", Logging::Loggable("PDNSException")));
}
- if (t_tcpClientCounts->count(d_remote) && !(*t_tcpClientCounts)[d_remote]--)
+ if (t_tcpClientCounts->count(d_remote) != 0 && (*t_tcpClientCounts)[d_remote]-- == 0) {
t_tcpClientCounts->erase(d_remote);
+ }
--s_currentConnections;
}
-static void terminateTCPConnection(int fd)
+static void terminateTCPConnection(int fileDesc)
{
try {
- t_fdm->removeReadFD(fd);
+ t_fdm->removeReadFD(fileDesc);
}
catch (const FDMultiplexerException& fde) {
}
}
-static void sendErrorOverTCP(std::unique_ptr<DNSComboWriter>& dc, int rcode)
+static void sendErrorOverTCP(std::unique_ptr<DNSComboWriter>& comboWriter, int rcode)
{
std::vector<uint8_t> packet;
- if (dc->d_mdp.d_header.qdcount == 0) {
+ if (comboWriter->d_mdp.d_header.qdcount == 0U) {
/* header-only */
packet.resize(sizeof(dnsheader));
}
else {
- DNSPacketWriter pw(packet, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass);
- if (dc->d_mdp.hasEDNS()) {
+ DNSPacketWriter packetWriter(packet, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass);
+ if (comboWriter->d_mdp.hasEDNS()) {
/* we try to add the EDNS OPT RR even for truncated answers,
as rfc6891 states:
"The minimal response MUST be the DNS header, question section, and an
OPT record. This MUST also occur when a truncated response (using
the DNS header's TC bit) is returned."
*/
- pw.addOpt(512, 0, 0);
- pw.commit();
+ packetWriter.addOpt(512, 0, 0);
+ packetWriter.commit();
}
}
- dnsheader& header = reinterpret_cast<dnsheader&>(packet.at(0));
+ auto& header = reinterpret_cast<dnsheader&>(packet.at(0)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) safe cast
header.aa = 0;
header.ra = 1;
header.qr = 1;
header.tc = 0;
- header.id = dc->d_mdp.d_header.id;
- header.rd = dc->d_mdp.d_header.rd;
- header.cd = dc->d_mdp.d_header.cd;
+ header.id = comboWriter->d_mdp.d_header.id;
+ header.rd = comboWriter->d_mdp.d_header.rd;
+ header.cd = comboWriter->d_mdp.d_header.cd;
header.rcode = rcode;
- sendResponseOverTCP(dc, packet);
+ sendResponseOverTCP(comboWriter, packet);
}
-void finishTCPReply(std::unique_ptr<DNSComboWriter>& dc, bool hadError, bool updateInFlight)
+void finishTCPReply(std::unique_ptr<DNSComboWriter>& comboWriter, bool hadError, bool updateInFlight)
{
// update tcp connection status, closing if needed and doing the fd multiplexer accounting
- if (updateInFlight && dc->d_tcpConnection->d_requestsInFlight > 0) {
- dc->d_tcpConnection->d_requestsInFlight--;
+ if (updateInFlight && comboWriter->d_tcpConnection->d_requestsInFlight > 0) {
+ comboWriter->d_tcpConnection->d_requestsInFlight--;
}
// In the code below, we try to remove the fd from the set, but
// "Tried to remove unlisted fd" exception. Not that an inflight < limit test
// will not work since we do not know if the other mthread got an error or not.
if (hadError) {
- terminateTCPConnection(dc->d_socket);
- dc->d_socket = -1;
+ terminateTCPConnection(comboWriter->d_socket);
+ comboWriter->d_socket = -1;
return;
}
- dc->d_tcpConnection->queriesCount++;
- if ((g_tcpMaxQueriesPerConn && dc->d_tcpConnection->queriesCount >= g_tcpMaxQueriesPerConn) || (dc->d_tcpConnection->isDropOnIdle() && dc->d_tcpConnection->d_requestsInFlight == 0)) {
+ comboWriter->d_tcpConnection->queriesCount++;
+ if ((g_tcpMaxQueriesPerConn > 0 && comboWriter->d_tcpConnection->queriesCount >= g_tcpMaxQueriesPerConn) || (comboWriter->d_tcpConnection->isDropOnIdle() && comboWriter->d_tcpConnection->d_requestsInFlight == 0)) {
try {
- t_fdm->removeReadFD(dc->d_socket);
+ t_fdm->removeReadFD(comboWriter->d_socket);
}
catch (FDMultiplexerException&) {
}
- dc->d_socket = -1;
+ comboWriter->d_socket = -1;
return;
}
struct timeval ttd = g_now;
// If we cross from max to max-1 in flight requests, the fd was not listened to, add it back
- if (updateInFlight && dc->d_tcpConnection->d_requestsInFlight == TCPConnection::s_maxInFlight - 1) {
+ if (updateInFlight && comboWriter->d_tcpConnection->d_requestsInFlight == TCPConnection::s_maxInFlight - 1) {
// A read error might have happened. If we add the fd back, it will most likely error again.
// This is not a big issue, the next handleTCPClientReadable() will see another read error
// and take action.
ttd.tv_sec += g_tcpTimeout;
- t_fdm->addReadFD(dc->d_socket, handleRunningTCPQuestion, dc->d_tcpConnection, &ttd);
+ t_fdm->addReadFD(comboWriter->d_socket, handleRunningTCPQuestion, comboWriter->d_tcpConnection, &ttd);
return;
}
// fd might have been removed by read error code, or a read timeout, so expect an exception
try {
- t_fdm->setReadTTD(dc->d_socket, ttd, g_tcpTimeout);
+ t_fdm->setReadTTD(comboWriter->d_socket, ttd, g_tcpTimeout);
}
catch (const FDMultiplexerException&) {
// but if the FD was removed because of a timeout while we were sending a response,
// we need to re-arm it. If it was an error it will error again.
ttd.tv_sec += g_tcpTimeout;
- t_fdm->addReadFD(dc->d_socket, handleRunningTCPQuestion, dc->d_tcpConnection, &ttd);
+ t_fdm->addReadFD(comboWriter->d_socket, handleRunningTCPQuestion, comboWriter->d_tcpConnection, &ttd);
}
}
class RunningTCPQuestionGuard
{
public:
- RunningTCPQuestionGuard(int fd)
- {
- d_fd = fd;
- }
+ RunningTCPQuestionGuard(const RunningTCPQuestionGuard&) = default;
+ RunningTCPQuestionGuard(RunningTCPQuestionGuard&&) = delete;
+ RunningTCPQuestionGuard& operator=(const RunningTCPQuestionGuard&) = default;
+ RunningTCPQuestionGuard& operator=(RunningTCPQuestionGuard&&) = delete;
+ RunningTCPQuestionGuard(int fileDesc) :
+ d_fd(fileDesc) {}
~RunningTCPQuestionGuard()
{
if (d_fd != -1) {
/* EOF */
return false;
}
- else if (bytes < 0) {
+ if (bytes < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
return false;
}
int d_fd{-1};
};
-static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
+static void handleNotify(std::unique_ptr<DNSComboWriter>& comboWriter, const DNSName& qname)
+{
+ if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(comboWriter->d_mappedSource)) {
+ if (!g_quiet) {
+ SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping TCP NOTIFY from " << comboWriter->d_mappedSource.toString() << ", address not matched by allow-notify-from" << endl,
+ g_slogtcpin->info(Logr::Error, "Dropping TCP NOTIFY, address not matched by allow-notify-from", "source", Logging::Loggable(comboWriter->d_mappedSource)));
+ }
+
+ t_Counters.at(rec::Counter::sourceDisallowedNotify)++;
+ return;
+ }
+
+ if (!isAllowNotifyForZone(qname)) {
+ if (!g_quiet) {
+ SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping TCP NOTIFY from " << comboWriter->d_mappedSource.toString() << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl,
+ g_slogtcpin->info(Logr::Error, "Dropping TCP NOTIFY, zone not matched by allow-notify-for", "source", Logging::Loggable(comboWriter->d_mappedSource), "zone", Logging::Loggable(qname)));
+ }
+
+ t_Counters.at(rec::Counter::zoneDisallowedNotify)++;
+ return;
+ }
+}
+
+static void doProtobufLogQuery(bool logQuery, LocalStateHolder<LuaConfigItems>& luaconfsLocal, const std::unique_ptr<DNSComboWriter>& comboWriter, const DNSName& qname, QType qtype, QClass qclass, const dnsheader* dnsheader, const shared_ptr<TCPConnection>& conn)
+{
+ try {
+ if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && comboWriter->d_policyTags.empty())) {
+ protobufLogQuery(luaconfsLocal, comboWriter->d_uuid, comboWriter->d_source, comboWriter->d_destination, comboWriter->d_mappedSource, comboWriter->d_ednssubnet.source, true, dnsheader->id, conn->qlen, qname, qtype, qclass, comboWriter->d_policyTags, comboWriter->d_requestorId, comboWriter->d_deviceId, comboWriter->d_deviceName, comboWriter->d_meta);
+ }
+ }
+ catch (const std::exception& e) {
+ if (g_logCommonErrors) {
+ SLOG(g_log << Logger::Warning << "Error parsing a TCP query packet for edns subnet: " << e.what() << endl,
+ g_slogtcpin->error(Logr::Warning, e.what(), "Error parsing a TCP query packet for edns subnet", "exception", Logging::Loggable("std::exception"), "remote", Logging::Loggable(conn->d_remote)));
+ }
+ }
+}
+
+static void doProcessTCPQuestion(std::unique_ptr<DNSComboWriter>& comboWriter, shared_ptr<TCPConnection>& conn, RunningTCPQuestionGuard& tcpGuard, int fileDesc)
{
- shared_ptr<TCPConnection> conn = boost::any_cast<shared_ptr<TCPConnection>>(var);
+ RecThreadInfo::self().incNumberOfDistributedQueries();
+ struct timeval start
+ {
+ };
+ Utility::gettimeofday(&start, nullptr);
+
+ DNSName qname;
+ uint16_t qtype = 0;
+ uint16_t qclass = 0;
+ bool needECS = false;
+ string requestorId;
+ string deviceId;
+ string deviceName;
+ bool logQuery = false;
+ bool qnameParsed = false;
+
+ comboWriter->d_eventTrace.setEnabled(SyncRes::s_event_trace_enabled != 0);
+ comboWriter->d_eventTrace.add(RecEventTrace::ReqRecv);
+ auto luaconfsLocal = g_luaconfs.getLocal();
+ if (checkProtobufExport(luaconfsLocal)) {
+ needECS = true;
+ }
+ logQuery = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logQueries;
+ comboWriter->d_logResponse = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logResponses;
- RunningTCPQuestionGuard tcpGuard{fd};
+ if (needECS || (t_pdl && (t_pdl->hasGettagFFIFunc() || t_pdl->hasGettagFunc())) || comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) {
+
+ try {
+ EDNSOptionViewMap ednsOptions;
+ comboWriter->d_ecsParsed = true;
+ comboWriter->d_ecsFound = false;
+ getQNameAndSubnet(conn->data, &qname, &qtype, &qclass,
+ comboWriter->d_ecsFound, &comboWriter->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
+ qnameParsed = true;
+
+ if (t_pdl) {
+ try {
+ if (t_pdl->hasGettagFFIFunc()) {
+ RecursorLua4::FFIParams params(qname, qtype, comboWriter->d_destination, comboWriter->d_source, comboWriter->d_ednssubnet.source, comboWriter->d_data, comboWriter->d_policyTags, comboWriter->d_records, ednsOptions, comboWriter->d_proxyProtocolValues, requestorId, deviceId, deviceName, comboWriter->d_routingTag, comboWriter->d_rcode, comboWriter->d_ttlCap, comboWriter->d_variable, true, logQuery, comboWriter->d_logResponse, comboWriter->d_followCNAMERecords, comboWriter->d_extendedErrorCode, comboWriter->d_extendedErrorExtra, comboWriter->d_responsePaddingDisabled, comboWriter->d_meta);
+ comboWriter->d_eventTrace.add(RecEventTrace::LuaGetTagFFI);
+ comboWriter->d_tag = t_pdl->gettag_ffi(params);
+ comboWriter->d_eventTrace.add(RecEventTrace::LuaGetTagFFI, comboWriter->d_tag, false);
+ }
+ else if (t_pdl->hasGettagFunc()) {
+ comboWriter->d_eventTrace.add(RecEventTrace::LuaGetTag);
+ comboWriter->d_tag = t_pdl->gettag(comboWriter->d_source, comboWriter->d_ednssubnet.source, comboWriter->d_destination, qname, qtype, &comboWriter->d_policyTags, comboWriter->d_data, ednsOptions, true, requestorId, deviceId, deviceName, comboWriter->d_routingTag, comboWriter->d_proxyProtocolValues);
+ comboWriter->d_eventTrace.add(RecEventTrace::LuaGetTag, comboWriter->d_tag, false);
+ }
+ }
+ catch (const std::exception& e) {
+ if (g_logCommonErrors) {
+ SLOG(g_log << Logger::Warning << "Error parsing a query packet qname='" << qname << "' for tag determination, setting tag=0: " << e.what() << endl,
+ g_slogtcpin->info(Logr::Warning, "Error parsing a query packet for tag determination, setting tag=0", "remote", Logging::Loggable(conn->d_remote), "qname", Logging::Loggable(qname)));
+ }
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ if (g_logCommonErrors) {
+ SLOG(g_log << Logger::Warning << "Error parsing a query packet for tag determination, setting tag=0: " << e.what() << endl,
+ g_slogtcpin->error(Logr::Warning, e.what(), "Error parsing a query packet for tag determination, setting tag=0", "exception", Logging::Loggable("std::exception"), "remote", Logging::Loggable(conn->d_remote)));
+ }
+ }
+ }
+
+ if (comboWriter->d_tag == 0 && !comboWriter->d_responsePaddingDisabled && g_paddingFrom.match(comboWriter->d_remote)) {
+ comboWriter->d_tag = g_paddingTag;
+ }
+
+ const dnsheader_aligned headerdata(conn->data.data());
+ const struct dnsheader* dnsheader = headerdata.get();
+
+ if (t_protobufServers.servers || t_outgoingProtobufServers.servers) {
+ comboWriter->d_requestorId = std::move(requestorId);
+ comboWriter->d_deviceId = std::move(deviceId);
+ comboWriter->d_deviceName = std::move(deviceName);
+ comboWriter->d_uuid = getUniqueID();
+ }
+
+ if (t_protobufServers.servers) {
+ doProtobufLogQuery(logQuery, luaconfsLocal, comboWriter, qname, qtype, qclass, dnsheader, conn);
+ }
+
+ if (t_pdl) {
+ bool ipf = t_pdl->ipfilter(comboWriter->d_source, comboWriter->d_destination, *dnsheader, comboWriter->d_eventTrace);
+ if (ipf) {
+ if (!g_quiet) {
+ SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] DROPPED TCP question from " << comboWriter->d_source.toStringWithPort() << (comboWriter->d_source != comboWriter->d_remote ? " (via " + comboWriter->d_remote.toStringWithPort() + ")" : "") << " based on policy" << endl,
+ g_slogtcpin->info(Logr::Info, "Dropped TCP question based on policy", "remote", Logging::Loggable(conn->d_remote), "source", Logging::Loggable(comboWriter->d_source)));
+ }
+ t_Counters.at(rec::Counter::policyDrops)++;
+ return;
+ }
+ }
+
+ if (comboWriter->d_mdp.d_header.qr) {
+ t_Counters.at(rec::Counter::ignoredCount)++;
+ if (g_logCommonErrors) {
+ SLOG(g_log << Logger::Error << "Ignoring answer from TCP client " << comboWriter->getRemote() << " on server socket!" << endl,
+ g_slogtcpin->info(Logr::Error, "Ignoring answer from TCP client on server socket", "remote", Logging::Loggable(comboWriter->getRemote())));
+ }
+ return;
+ }
+ if (comboWriter->d_mdp.d_header.opcode != static_cast<unsigned>(Opcode::Query) && comboWriter->d_mdp.d_header.opcode != static_cast<unsigned>(Opcode::Notify)) {
+ t_Counters.at(rec::Counter::ignoredCount)++;
+ if (g_logCommonErrors) {
+ SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(comboWriter->d_mdp.d_header.opcode) << " from TCP client " << comboWriter->getRemote() << " on server socket!" << endl,
+ g_slogtcpin->info(Logr::Error, "Ignoring unsupported opcode from TCP client", "remote", Logging::Loggable(comboWriter->getRemote()), "opcode", Logging::Loggable(Opcode::to_s(comboWriter->d_mdp.d_header.opcode))));
+ }
+ sendErrorOverTCP(comboWriter, RCode::NotImp);
+ tcpGuard.keep();
+ return;
+ }
+ if (dnsheader->qdcount == 0U) {
+ t_Counters.at(rec::Counter::emptyQueriesCount)++;
+ if (g_logCommonErrors) {
+ SLOG(g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << comboWriter->getRemote() << " on server socket!" << endl,
+ g_slogtcpin->info(Logr::Error, "Ignoring empty (qdcount == 0) query on server socket", "remote", Logging::Loggable(comboWriter->getRemote())));
+ }
+ sendErrorOverTCP(comboWriter, RCode::NotImp);
+ tcpGuard.keep();
+ return;
+ }
+ {
+ // We have read a proper query
+ ++t_Counters.at(rec::Counter::qcounter);
+ ++t_Counters.at(rec::Counter::tcpqcounter);
+ if (comboWriter->d_source.sin4.sin_family == AF_INET6) {
+ ++t_Counters.at(rec::Counter::ipv6qcounter);
+ }
+
+ if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) {
+ handleNotify(comboWriter, qname);
+ }
+
+ string response;
+ RecursorPacketCache::OptPBData pbData{boost::none};
+
+ if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Query)) {
+ /* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable,
+ but it means that the hash would not be computed. If some script decides at a later time to mark back the answer
+ as cacheable we would cache it with a wrong tag, so better safe than sorry. */
+ comboWriter->d_eventTrace.add(RecEventTrace::PCacheCheck);
+ bool cacheHit = checkForCacheHit(qnameParsed, comboWriter->d_tag, conn->data, qname, qtype, qclass, g_now, response, comboWriter->d_qhash, pbData, true, comboWriter->d_source, comboWriter->d_mappedSource);
+ comboWriter->d_eventTrace.add(RecEventTrace::PCacheCheck, cacheHit, false);
+
+ if (cacheHit) {
+ if (!g_quiet) {
+ SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " TCP question answered from packet cache tag=" << comboWriter->d_tag << " from " << comboWriter->d_source.toStringWithPort() << (comboWriter->d_source != comboWriter->d_remote ? " (via " + comboWriter->d_remote.toStringWithPort() + ")" : "") << endl,
+ g_slogtcpin->info(Logr::Notice, "TCP question answered from packet cache", "tag", Logging::Loggable(comboWriter->d_tag),
+ "qname", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype)),
+ "source", Logging::Loggable(comboWriter->d_source), "remote", Logging::Loggable(comboWriter->d_remote)));
+ }
+
+ bool hadError = sendResponseOverTCP(comboWriter, response);
+ finishTCPReply(comboWriter, hadError, false);
+ struct timeval now
+ {
+ };
+ Utility::gettimeofday(&now, nullptr);
+ uint64_t spentUsec = uSec(now - start);
+ t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
+ comboWriter->d_eventTrace.add(RecEventTrace::AnswerSent);
+
+ if (t_protobufServers.servers && comboWriter->d_logResponse && (!luaconfsLocal->protobufExportConfig.taggedOnly || !pbData || pbData->d_tagged)) {
+ struct timeval tval
+ {
+ 0, 0
+ };
+ protobufLogResponse(dnsheader, luaconfsLocal, pbData, tval, true, comboWriter->d_source, comboWriter->d_destination, comboWriter->d_mappedSource, comboWriter->d_ednssubnet, comboWriter->d_uuid, comboWriter->d_requestorId, comboWriter->d_deviceId, comboWriter->d_deviceName, comboWriter->d_meta, comboWriter->d_eventTrace, comboWriter->d_policyTags);
+ }
+
+ if (comboWriter->d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) != 0) {
+ SLOG(g_log << Logger::Info << comboWriter->d_eventTrace.toString() << endl,
+ g_slogtcpin->info(Logr::Info, comboWriter->d_eventTrace.toString())); // More fancy?
+ }
+ tcpGuard.keep();
+ t_Counters.updateSnap(g_regressionTestMode);
+ return;
+ } // cache hit
+ } // query opcode
+
+ if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) {
+ if (!g_quiet) {
+ SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << comboWriter->d_source.toStringWithPort() << (comboWriter->d_source != comboWriter->d_remote ? " (via " + comboWriter->d_remote.toStringWithPort() + ")" : "") << endl,
+ g_slogtcpin->info(Logr::Notice, "Got NOTIFY", "qname", Logging::Loggable(qname), "source", Logging::Loggable(comboWriter->d_source), "remote", Logging::Loggable(comboWriter->d_remote)));
+ }
+
+ requestWipeCaches(qname);
+
+ // the operation will now be treated as a Query, generating
+ // a normal response, as the rest of the code does not
+ // check dh->opcode, but we need to ensure that the response
+ // to this request does not get put into the packet cache
+ comboWriter->d_variable = true;
+ }
+
+ // setup for startDoResolve() in an mthread
+ ++conn->d_requestsInFlight;
+ if (conn->d_requestsInFlight >= TCPConnection::s_maxInFlight) {
+ t_fdm->removeReadFD(fileDesc); // should no longer awake ourselves when there is data to read
+ }
+ else {
+ Utility::gettimeofday(&g_now, nullptr); // needed?
+ struct timeval ttd = g_now;
+ t_fdm->setReadTTD(fileDesc, ttd, g_tcpTimeout);
+ }
+ tcpGuard.keep();
+ g_multiTasker->makeThread(startDoResolve, comboWriter.release()); // deletes dc
+ } // good query
+}
+
+static void handleRunningTCPQuestion(int fileDesc, FDMultiplexer::funcparam_t& var)
+{
+ auto conn = boost::any_cast<shared_ptr<TCPConnection>>(var);
+
+ RunningTCPQuestionGuard tcpGuard{fileDesc};
if (conn->state == TCPConnection::PROXYPROTOCOLHEADER) {
ssize_t bytes = recv(conn->getFD(), &conn->data.at(conn->proxyProtocolGot), conn->proxyProtocolNeed, 0);
if (bytes <= 0) {
- tcpGuard.handleTCPReadResult(fd, bytes);
+ tcpGuard.handleTCPReadResult(fileDesc, bytes);
return;
}
++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
return;
}
- else if (remaining < 0) {
+ if (remaining < 0) {
conn->proxyProtocolNeed = -remaining;
conn->data.resize(conn->proxyProtocolGot + conn->proxyProtocolNeed);
tcpGuard.keep();
return;
}
- else {
+ {
/* proxy header received */
/* we ignore the TCP field for now, but we could properly set whether
the connection was received over UDP or TCP if needed */
- bool tcp;
+ bool tcp = false;
bool proxy = false;
size_t used = parseProxyHeader(conn->data, proxy, conn->d_source, conn->d_destination, tcp, conn->proxyProtocolValues);
if (used <= 0) {
++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
return;
}
- else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+ if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
if (g_logCommonErrors) {
SLOG(g_log << Logger::Error << "Proxy protocol header in packet from TCP client " << conn->d_remote.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping" << endl,
g_slogtcpin->info(Logr::Error, "Proxy protocol header in packet from TCP client is larger than proxy-protocol-maximum-size", "remote", Logging::Loggable(conn->d_remote), "size", Logging::Loggable(used)));
/* note that if the proxy header used a 'LOCAL' command, the original source and destination are untouched so everything should be fine */
conn->d_mappedSource = conn->d_source;
if (t_proxyMapping) {
- if (auto it = t_proxyMapping->lookup(conn->d_source)) {
- conn->d_mappedSource = it->second.address;
- ++it->second.stats.netmaskMatches;
+ if (const auto* iter = t_proxyMapping->lookup(conn->d_source)) {
+ conn->d_mappedSource = iter->second.address;
+ ++iter->second.stats.netmaskMatches;
}
}
if (t_allowFrom && !t_allowFrom->match(&conn->d_mappedSource)) {
if (!g_quiet) {
- SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << conn->d_mappedSource.toString() << ", address not matched by allow-from" << endl,
+ SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping TCP query from " << conn->d_mappedSource.toString() << ", address not matched by allow-from" << endl,
g_slogtcpin->info(Logr::Error, "Dropping TCP query, address not matched by allow-from", "remote", Logging::Loggable(conn->d_remote)));
}
}
if (conn->state == TCPConnection::BYTE0) {
- ssize_t bytes = recv(conn->getFD(), &conn->data[0], 2, 0);
- if (bytes == 1)
+ ssize_t bytes = recv(conn->getFD(), conn->data.data(), 2, 0);
+ if (bytes == 1) {
conn->state = TCPConnection::BYTE1;
+ }
if (bytes == 2) {
conn->qlen = (((unsigned char)conn->data[0]) << 8) + (unsigned char)conn->data[1];
conn->data.resize(conn->qlen);
conn->state = TCPConnection::GETQUESTION;
}
if (bytes <= 0) {
- tcpGuard.handleTCPReadResult(fd, bytes);
+ tcpGuard.handleTCPReadResult(fileDesc, bytes);
return;
}
}
conn->bytesread = 0;
}
if (bytes <= 0) {
- if (!tcpGuard.handleTCPReadResult(fd, bytes)) {
+ if (!tcpGuard.handleTCPReadResult(fileDesc, bytes)) {
if (g_logCommonErrors) {
SLOG(g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " disconnected after first byte" << endl,
g_slogtcpin->info(Logr::Error, "TCP client disconnected after first byte", "remote", Logging::Loggable(conn->d_remote)));
if (conn->state == TCPConnection::GETQUESTION) {
ssize_t bytes = recv(conn->getFD(), &conn->data[conn->bytesread], conn->qlen - conn->bytesread, 0);
if (bytes <= 0) {
- if (!tcpGuard.handleTCPReadResult(fd, bytes)) {
+ if (!tcpGuard.handleTCPReadResult(fileDesc, bytes)) {
if (g_logCommonErrors) {
SLOG(g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " disconnected while reading question body" << endl,
g_slogtcpin->info(Logr::Error, "TCP client disconnected while reading question body", "remote", Logging::Loggable(conn->d_remote)));
}
return;
}
- else if (bytes > std::numeric_limits<std::uint16_t>::max()) {
+ if (bytes > std::numeric_limits<std::uint16_t>::max()) {
if (g_logCommonErrors) {
SLOG(g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " sent an invalid question size while reading question body" << endl,
g_slogtcpin->info(Logr::Error, "TCP client sent an invalid question size while reading question body", "remote", Logging::Loggable(conn->d_remote)));
conn->bytesread += (uint16_t)bytes;
if (conn->bytesread == conn->qlen) {
conn->state = TCPConnection::BYTE0;
- std::unique_ptr<DNSComboWriter> dc;
+ std::unique_ptr<DNSComboWriter> comboWriter;
try {
- dc = std::make_unique<DNSComboWriter>(conn->data, g_now, t_pdl);
+ comboWriter = std::make_unique<DNSComboWriter>(conn->data, g_now, t_pdl);
}
catch (const MOADNSException& mde) {
t_Counters.at(rec::Counter::clientParseError)++;
return;
}
- dc->d_tcpConnection = conn; // carry the torch
- dc->setSocket(conn->getFD()); // this is the only time a copy is made of the actual fd
- dc->d_tcp = true;
- dc->setRemote(conn->d_remote); // the address the query was received from
- dc->setSource(conn->d_source); // the address we assume the query is coming from, might be set by proxy protocol
+ comboWriter->d_tcpConnection = conn; // carry the torch
+ comboWriter->setSocket(conn->getFD()); // this is the only time a copy is made of the actual fd
+ comboWriter->d_tcp = true;
+ comboWriter->setRemote(conn->d_remote); // the address the query was received from
+ comboWriter->setSource(conn->d_source); // the address we assume the query is coming from, might be set by proxy protocol
ComboAddress dest;
dest.reset();
dest.sin4.sin_family = conn->d_remote.sin4.sin_family;
socklen_t len = dest.getSocklen();
- getsockname(conn->getFD(), (sockaddr*)&dest, &len); // if this fails, we're ok with it
- dc->setLocal(dest); // the address we received the query on
- dc->setDestination(conn->d_destination); // the address we assume the query is received on, might be set by proxy protocol
- dc->setMappedSource(conn->d_mappedSource); // the address we assume the query is coming from after table based mapping
+ getsockname(conn->getFD(), reinterpret_cast<sockaddr*>(&dest), &len); // if this fails, we're ok with it NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ comboWriter->setLocal(dest); // the address we received the query on
+ comboWriter->setDestination(conn->d_destination); // the address we assume the query is received on, might be set by proxy protocol
+ comboWriter->setMappedSource(conn->d_mappedSource); // the address we assume the query is coming from after table based mapping
/* we can't move this if we want to be able to access the values in
all queries sent over this connection */
- dc->d_proxyProtocolValues = conn->proxyProtocolValues;
-
- struct timeval start;
- Utility::gettimeofday(&start, nullptr);
-
- DNSName qname;
- uint16_t qtype = 0;
- uint16_t qclass = 0;
- bool needECS = false;
- string requestorId;
- string deviceId;
- string deviceName;
- bool logQuery = false;
- bool qnameParsed = false;
-
- dc->d_eventTrace.setEnabled(SyncRes::s_event_trace_enabled);
- dc->d_eventTrace.add(RecEventTrace::ReqRecv);
- auto luaconfsLocal = g_luaconfs.getLocal();
- if (checkProtobufExport(luaconfsLocal)) {
- needECS = true;
- }
- logQuery = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logQueries;
- dc->d_logResponse = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logResponses;
-
- if (needECS || (t_pdl && (t_pdl->d_gettag_ffi || t_pdl->d_gettag)) || dc->d_mdp.d_header.opcode == Opcode::Notify) {
-
- try {
- EDNSOptionViewMap ednsOptions;
- dc->d_ecsParsed = true;
- dc->d_ecsFound = false;
- getQNameAndSubnet(conn->data, &qname, &qtype, &qclass,
- dc->d_ecsFound, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
- qnameParsed = true;
-
- if (t_pdl) {
- try {
- if (t_pdl->d_gettag_ffi) {
- RecursorLua4::FFIParams params(qname, qtype, dc->d_destination, dc->d_source, dc->d_ednssubnet.source, dc->d_data, dc->d_policyTags, dc->d_records, ednsOptions, dc->d_proxyProtocolValues, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_rcode, dc->d_ttlCap, dc->d_variable, true, logQuery, dc->d_logResponse, dc->d_followCNAMERecords, dc->d_extendedErrorCode, dc->d_extendedErrorExtra, dc->d_responsePaddingDisabled, dc->d_meta);
- dc->d_eventTrace.add(RecEventTrace::LuaGetTagFFI);
- dc->d_tag = t_pdl->gettag_ffi(params);
- dc->d_eventTrace.add(RecEventTrace::LuaGetTagFFI, dc->d_tag, false);
- }
- else if (t_pdl->d_gettag) {
- dc->d_eventTrace.add(RecEventTrace::LuaGetTag);
- dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_proxyProtocolValues);
- dc->d_eventTrace.add(RecEventTrace::LuaGetTag, dc->d_tag, false);
- }
- }
- catch (const std::exception& e) {
- if (g_logCommonErrors) {
- SLOG(g_log << Logger::Warning << "Error parsing a query packet qname='" << qname << "' for tag determination, setting tag=0: " << e.what() << endl,
- g_slogtcpin->info(Logr::Warning, "Error parsing a query packet for tag determination, setting tag=0", "remote", Logging::Loggable(conn->d_remote), "qname", Logging::Loggable(qname)));
- }
- }
- }
- }
- catch (const std::exception& e) {
- if (g_logCommonErrors) {
- SLOG(g_log << Logger::Warning << "Error parsing a query packet for tag determination, setting tag=0: " << e.what() << endl,
- g_slogtcpin->error(Logr::Warning, e.what(), "Error parsing a query packet for tag determination, setting tag=0", "exception", Logging::Loggable("std::exception"), "remote", Logging::Loggable(conn->d_remote)));
- }
- }
- }
-
- if (dc->d_tag == 0 && !dc->d_responsePaddingDisabled && g_paddingFrom.match(dc->d_remote)) {
- dc->d_tag = g_paddingTag;
- }
-
- const dnsheader_aligned headerdata(conn->data.data());
- const struct dnsheader* dh = headerdata.get();
-
- if (t_protobufServers.servers || t_outgoingProtobufServers.servers) {
- dc->d_requestorId = requestorId;
- dc->d_deviceId = deviceId;
- dc->d_deviceName = deviceName;
- dc->d_uuid = getUniqueID();
- }
-
- if (t_protobufServers.servers) {
- try {
-
- if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && dc->d_policyTags.empty())) {
- protobufLogQuery(luaconfsLocal, dc->d_uuid, dc->d_source, dc->d_destination, dc->d_mappedSource, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName, dc->d_meta);
- }
- }
- catch (const std::exception& e) {
- if (g_logCommonErrors) {
- SLOG(g_log << Logger::Warning << "Error parsing a TCP query packet for edns subnet: " << e.what() << endl,
- g_slogtcpin->error(Logr::Warning, e.what(), "Error parsing a TCP query packet for edns subnet", "exception", Logging::Loggable("std::exception"), "remote", Logging::Loggable(conn->d_remote)));
- }
- }
- }
-
- if (t_pdl) {
- bool ipf = t_pdl->ipfilter(dc->d_source, dc->d_destination, *dh, dc->d_eventTrace);
- if (ipf) {
- if (!g_quiet) {
- SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] DROPPED TCP question from " << dc->d_source.toStringWithPort() << (dc->d_source != dc->d_remote ? " (via " + dc->d_remote.toStringWithPort() + ")" : "") << " based on policy" << endl,
- g_slogtcpin->info(Logr::Info, "Dropped TCP question based on policy", "remote", Logging::Loggable(conn->d_remote), "source", Logging::Loggable(dc->d_source)));
- }
- t_Counters.at(rec::Counter::policyDrops)++;
- return;
- }
- }
-
- if (dc->d_mdp.d_header.qr) {
- t_Counters.at(rec::Counter::ignoredCount)++;
- if (g_logCommonErrors) {
- SLOG(g_log << Logger::Error << "Ignoring answer from TCP client " << dc->getRemote() << " on server socket!" << endl,
- g_slogtcpin->info(Logr::Error, "Ignoring answer from TCP client on server socket", "remote", Logging::Loggable(dc->getRemote())));
- }
- return;
- }
- if (dc->d_mdp.d_header.opcode != Opcode::Query && dc->d_mdp.d_header.opcode != Opcode::Notify) {
- t_Counters.at(rec::Counter::ignoredCount)++;
- if (g_logCommonErrors) {
- SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(dc->d_mdp.d_header.opcode) << " from TCP client " << dc->getRemote() << " on server socket!" << endl,
- g_slogtcpin->info(Logr::Error, "Ignoring unsupported opcode from TCP client", "remote", Logging::Loggable(dc->getRemote()), "opcode", Logging::Loggable(Opcode::to_s(dc->d_mdp.d_header.opcode))));
- }
- sendErrorOverTCP(dc, RCode::NotImp);
- tcpGuard.keep();
- return;
- }
- else if (dh->qdcount == 0) {
- t_Counters.at(rec::Counter::emptyQueriesCount)++;
- if (g_logCommonErrors) {
- SLOG(g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << dc->getRemote() << " on server socket!" << endl,
- g_slogtcpin->info(Logr::Error, "Ignoring empty (qdcount == 0) query on server socket", "remote", Logging::Loggable(dc->getRemote())));
- }
- sendErrorOverTCP(dc, RCode::NotImp);
- tcpGuard.keep();
- return;
- }
- else {
- // We have read a proper query
- //++t_Counters.at(rec::Counter::qcounter);
- ++t_Counters.at(rec::Counter::qcounter);
- ++t_Counters.at(rec::Counter::tcpqcounter);
-
- if (dc->d_mdp.d_header.opcode == Opcode::Notify) {
- if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(dc->d_mappedSource)) {
- if (!g_quiet) {
- SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_mappedSource.toString() << ", address not matched by allow-notify-from" << endl,
- g_slogtcpin->info(Logr::Error, "Dropping TCP NOTIFY, address not matched by allow-notify-from", "source", Logging::Loggable(dc->d_mappedSource)));
- }
-
- t_Counters.at(rec::Counter::sourceDisallowedNotify)++;
- return;
- }
-
- if (!isAllowNotifyForZone(qname)) {
- if (!g_quiet) {
- SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_mappedSource.toString() << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl,
- g_slogtcpin->info(Logr::Error, "Dropping TCP NOTIFY, zone not matched by allow-notify-for", "source", Logging::Loggable(dc->d_mappedSource), "zone", Logging::Loggable(qname)));
- }
-
- t_Counters.at(rec::Counter::zoneDisallowedNotify)++;
- return;
- }
- }
-
- string response;
- RecursorPacketCache::OptPBData pbData{boost::none};
-
- if (dc->d_mdp.d_header.opcode == Opcode::Query) {
- /* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable,
- but it means that the hash would not be computed. If some script decides at a later time to mark back the answer
- as cacheable we would cache it with a wrong tag, so better safe than sorry. */
- dc->d_eventTrace.add(RecEventTrace::PCacheCheck);
- bool cacheHit = checkForCacheHit(qnameParsed, dc->d_tag, conn->data, qname, qtype, qclass, g_now, response, dc->d_qhash, pbData, true, dc->d_source, dc->d_mappedSource);
- dc->d_eventTrace.add(RecEventTrace::PCacheCheck, cacheHit, false);
-
- if (cacheHit) {
- if (!g_quiet) {
- SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " TCP question answered from packet cache tag=" << dc->d_tag << " from " << dc->d_source.toStringWithPort() << (dc->d_source != dc->d_remote ? " (via " + dc->d_remote.toStringWithPort() + ")" : "") << endl,
- g_slogtcpin->info(Logr::Notice, "TCP question answered from packet cache", "tag", Logging::Loggable(dc->d_tag),
- "qname", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype)),
- "source", Logging::Loggable(dc->d_source), "remote", Logging::Loggable(dc->d_remote)));
- }
-
- bool hadError = sendResponseOverTCP(dc, response);
- finishTCPReply(dc, hadError, false);
- struct timeval now;
- Utility::gettimeofday(&now, nullptr);
- uint64_t spentUsec = uSec(now - start);
- t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
- dc->d_eventTrace.add(RecEventTrace::AnswerSent);
-
- if (t_protobufServers.servers && dc->d_logResponse && !(luaconfsLocal->protobufExportConfig.taggedOnly && pbData && !pbData->d_tagged)) {
- struct timeval tv
- {
- 0, 0
- };
- protobufLogResponse(dh, luaconfsLocal, pbData, tv, true, dc->d_source, dc->d_destination, dc->d_mappedSource, dc->d_ednssubnet, dc->d_uuid, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName, dc->d_meta, dc->d_eventTrace);
- }
-
- if (dc->d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
- SLOG(g_log << Logger::Info << dc->d_eventTrace.toString() << endl,
- g_slogtcpin->info(Logr::Info, dc->d_eventTrace.toString())); // More fancy?
- }
- tcpGuard.keep();
- t_Counters.updateSnap(g_regressionTestMode);
- return;
- } // cache hit
- } // query opcode
-
- if (dc->d_mdp.d_header.opcode == Opcode::Notify) {
- if (!g_quiet) {
- SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << dc->d_source.toStringWithPort() << (dc->d_source != dc->d_remote ? " (via " + dc->d_remote.toStringWithPort() + ")" : "") << endl,
- g_slogtcpin->info(Logr::Notice, "Got NOTIFY", "qname", Logging::Loggable(qname), "source", Logging::Loggable(dc->d_source), "remote", Logging::Loggable(dc->d_remote)));
- }
-
- requestWipeCaches(qname);
-
- // the operation will now be treated as a Query, generating
- // a normal response, as the rest of the code does not
- // check dh->opcode, but we need to ensure that the response
- // to this request does not get put into the packet cache
- dc->d_variable = true;
- }
-
- // setup for startDoResolve() in an mthread
- ++conn->d_requestsInFlight;
- if (conn->d_requestsInFlight >= TCPConnection::s_maxInFlight) {
- t_fdm->removeReadFD(fd); // should no longer awake ourselves when there is data to read
- }
- else {
- Utility::gettimeofday(&g_now, nullptr); // needed?
- struct timeval ttd = g_now;
- t_fdm->setReadTTD(fd, ttd, g_tcpTimeout);
- }
- tcpGuard.keep();
- MT->makeThread(startDoResolve, dc.release()); // deletes dc
- } // good query
- } // read full query
- } // reading query
+ comboWriter->d_proxyProtocolValues = conn->proxyProtocolValues;
+ doProcessTCPQuestion(comboWriter, conn, tcpGuard, fileDesc);
+ } // reading query
+ }
// more to come
tcpGuard.keep();
}
//! Handle new incoming TCP connection
-void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&)
+void handleNewTCPQuestion(int fileDesc, [[maybe_unused]] FDMultiplexer::funcparam_t& var)
{
ComboAddress addr;
socklen_t addrlen = sizeof(addr);
- int newsock = accept(fd, (struct sockaddr*)&addr, &addrlen);
+ int newsock = accept(fileDesc, reinterpret_cast<struct sockaddr*>(&addr), &addrlen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
if (newsock >= 0) {
- if (MT->numProcesses() > g_maxMThreads) {
+ if (g_multiTasker->numProcesses() > g_maxMThreads) {
t_Counters.at(rec::Counter::overCapacityDrops)++;
try {
closesocket(newsock);
bool fromProxyProtocolSource = expectProxyProtocol(addr);
ComboAddress mappedSource = addr;
if (!fromProxyProtocolSource && t_proxyMapping) {
- if (auto it = t_proxyMapping->lookup(addr)) {
- mappedSource = it->second.address;
- ++it->second.stats.netmaskMatches;
+ if (const auto* iter = t_proxyMapping->lookup(addr)) {
+ mappedSource = iter->second.address;
+ ++iter->second.stats.netmaskMatches;
}
}
if (!fromProxyProtocolSource && t_allowFrom && !t_allowFrom->match(&mappedSource)) {
- if (!g_quiet)
- SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << mappedSource.toString() << ", address neither matched by allow-from nor proxy-protocol-from" << endl,
+ if (!g_quiet) {
+ SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping TCP query from " << mappedSource.toString() << ", address neither matched by allow-from nor proxy-protocol-from" << endl,
g_slogtcpin->info(Logr::Error, "dropping TCP query address neither matched by allow-from nor proxy-protocol-from", "source", Logging::Loggable(mappedSource)));
-
+ }
t_Counters.at(rec::Counter::unauthorizedTCP)++;
try {
closesocket(newsock);
return;
}
- if (g_maxTCPPerClient && t_tcpClientCounts->count(addr) && (*t_tcpClientCounts)[addr] >= g_maxTCPPerClient) {
+ if (g_maxTCPPerClient > 0 && t_tcpClientCounts->count(addr) > 0 && (*t_tcpClientCounts)[addr] >= g_maxTCPPerClient) {
t_Counters.at(rec::Counter::tcpClientOverflow)++;
try {
closesocket(newsock); // don't call TCPConnection::closeAndCleanup here - did not enter it in the counts yet!
setNonBlocking(newsock);
setTCPNoDelay(newsock);
- std::shared_ptr<TCPConnection> tc = std::make_shared<TCPConnection>(newsock, addr);
- tc->d_source = addr;
- tc->d_destination.reset();
- tc->d_destination.sin4.sin_family = addr.sin4.sin_family;
- socklen_t len = tc->d_destination.getSocklen();
- getsockname(tc->getFD(), reinterpret_cast<sockaddr*>(&tc->d_destination), &len); // if this fails, we're ok with it
- tc->d_mappedSource = mappedSource;
+ std::shared_ptr<TCPConnection> tcpConn = std::make_shared<TCPConnection>(newsock, addr);
+ tcpConn->d_source = addr;
+ tcpConn->d_destination.reset();
+ tcpConn->d_destination.sin4.sin_family = addr.sin4.sin_family;
+ socklen_t len = tcpConn->d_destination.getSocklen();
+ getsockname(tcpConn->getFD(), reinterpret_cast<sockaddr*>(&tcpConn->d_destination), &len); // if this fails, we're ok with it NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+ tcpConn->d_mappedSource = mappedSource;
if (fromProxyProtocolSource) {
- tc->proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
- tc->data.resize(tc->proxyProtocolNeed);
- tc->state = TCPConnection::PROXYPROTOCOLHEADER;
+ tcpConn->proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+ tcpConn->data.resize(tcpConn->proxyProtocolNeed);
+ tcpConn->state = TCPConnection::PROXYPROTOCOLHEADER;
}
else {
- tc->state = TCPConnection::BYTE0;
+ tcpConn->state = TCPConnection::BYTE0;
}
- struct timeval ttd;
+ struct timeval ttd
+ {
+ };
Utility::gettimeofday(&ttd, nullptr);
ttd.tv_sec += g_tcpTimeout;
- t_fdm->addReadFD(tc->getFD(), handleRunningTCPQuestion, tc, &ttd);
+ t_fdm->addReadFD(tcpConn->getFD(), handleRunningTCPQuestion, tcpConn, &ttd);
}
}
-static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var);
+static void TCPIOHandlerIO(int fileDesc, FDMultiplexer::funcparam_t& var);
static void TCPIOHandlerStateChange(IOState oldstate, IOState newstate, std::shared_ptr<PacketID>& pid)
{
}
}
-static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var)
+static void TCPIOHandlerIO(int fileDesc, FDMultiplexer::funcparam_t& var)
{
- std::shared_ptr<PacketID> pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
- assert(pid->tcphandler);
- assert(fd == pid->tcphandler->getDescriptor());
+ auto pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
+ assert(pid->tcphandler); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay): def off assert triggers it
+ assert(fileDesc == pid->tcphandler->getDescriptor()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) idem
IOState newstate = IOState::Done;
TCPLOG(pid->tcpsock, "TCPIOHandlerIO: lowState " << int(pid->lowState) << endl);
pid->inMSG.resize(pid->inPos); // old content (if there) + new bytes read, only relevant for the inIncompleteOkay case
newstate = IOState::Done;
TCPIOHandlerStateChange(pid->lowState, newstate, pid);
- MT->sendEvent(pid, &pid->inMSG);
+ g_multiTasker->sendEvent(pid, &pid->inMSG);
return;
}
break;
TCPLOG(pid->tcpsock, "read exception..." << e.what() << endl);
PacketBuffer empty;
TCPIOHandlerStateChange(pid->lowState, newstate, pid);
- MT->sendEvent(pid, &empty); // this conveys error status
+ g_multiTasker->sendEvent(pid, &empty); // this conveys error status
return;
}
break;
case IOState::Done: {
TCPLOG(pid->tcpsock, "tryWrite: Done" << endl);
TCPIOHandlerStateChange(pid->lowState, newstate, pid);
- MT->sendEvent(pid, &pid->outMSG); // send back what we sent to convey everything is ok
+ g_multiTasker->sendEvent(pid, &pid->outMSG); // send back what we sent to convey everything is ok
return;
}
case IOState::NeedRead:
TCPLOG(pid->tcpsock, "write exception..." << e.what() << endl);
PacketBuffer sent;
TCPIOHandlerStateChange(pid->lowState, newstate, pid);
- MT->sendEvent(pid, &sent); // we convey error status by sending empty string
+ g_multiTasker->sendEvent(pid, &sent); // we convey error status by sending empty string
return;
}
break;
void checkTFOconnect(Logr::log_t log)
{
try {
- Socket s(AF_INET, SOCK_STREAM);
- s.setNonBlocking();
- s.setFastOpenConnect();
+ Socket socket(AF_INET, SOCK_STREAM);
+ socket.setNonBlocking();
+ socket.setFastOpenConnect();
}
catch (const NetworkError& e) {
SLOG(g_log << Logger::Error << "tcp-fast-open-connect enabled but returned error: " << e.what() << endl,
pident->outMSG = data;
pident->highState = TCPAction::DoingWrite;
- IOState state;
+ IOState state = IOState::Done;
try {
TCPLOG(pident->tcpsock, "Initial tryWrite: " << pident->outPos << '/' << pident->outMSG.size() << ' ' << " -> ");
state = handler->tryWrite(pident->outMSG, pident->outPos, pident->outMSG.size());
TCPIOHandlerStateChange(IOState::Done, state, pident);
PacketBuffer packet;
- int ret = MT->waitEvent(pident, &packet, g_networkTimeoutMsec);
+ int ret = g_multiTasker->waitEvent(pident, &packet, g_networkTimeoutMsec);
TCPLOG(pident->tcpsock, "asendtcp waitEvent returned " << ret << ' ' << packet.size() << '/' << data.size() << ' ');
if (ret == 0) {
TCPLOG(pident->tcpsock, "timeout" << endl);
TCPIOHandlerStateChange(pident->lowState, IOState::Done, pident);
return LWResult::Result::Timeout;
}
- else if (ret == -1) { // error
+ if (ret == -1) { // error
TCPLOG(pident->tcpsock, "PermanentError" << endl);
TCPIOHandlerStateChange(pident->lowState, IOState::Done, pident);
return LWResult::Result::PermanentError;
}
- else if (packet.size() != data.size()) { // main loop tells us what it sent out, or empty in case of an error
+ if (packet.size() != data.size()) { // main loop tells us what it sent out, or empty in case of an error
// fd housekeeping done by TCPIOHandlerIO
TCPLOG(pident->tcpsock, "PermanentError size mismatch" << endl);
return LWResult::Result::PermanentError;
// We might have data already available from the TLS layer, try to get that into the buffer
size_t pos = 0;
- IOState state;
+ IOState state = IOState::Done;
try {
TCPLOG(handler->getDescriptor(), "calling tryRead() " << len << endl);
state = handler->tryRead(data, pos, len);
// Will set pident->lowState
TCPIOHandlerStateChange(IOState::Done, state, pident);
- int ret = MT->waitEvent(pident, &data, g_networkTimeoutMsec);
+ int ret = g_multiTasker->waitEvent(pident, &data, g_networkTimeoutMsec);
TCPLOG(pident->tcpsock, "arecvtcp " << ret << ' ' << data.size() << ' ');
if (ret == 0) {
TCPLOG(pident->tcpsock, "timeout" << endl);
TCPIOHandlerStateChange(pident->lowState, IOState::Done, pident);
return LWResult::Result::Timeout;
}
- else if (ret == -1) {
+ if (ret == -1) {
TCPLOG(pident->tcpsock, "PermanentError" << endl);
TCPIOHandlerStateChange(pident->lowState, IOState::Done, pident);
return LWResult::Result::PermanentError;
}
- else if (data.empty()) { // error, EOF or other
+ if (data.empty()) { // error, EOF or other
// fd housekeeping done by TCPIOHandlerIO
TCPLOG(pident->tcpsock, "EOF" << endl);
return LWResult::Result::PermanentError;
int err = errno;
SLOG(g_log << Logger::Error << "Setsockopt failed for TCP listening socket" << endl,
log->error(Logr::Critical, err, "Setsockopt failed for TCP listening socket"));
- exit(1);
+ _exit(1);
}
if (address.sin6.sin6_family == AF_INET6 && setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &tmp, sizeof(tmp)) < 0) {
int err = errno;
- SLOG(g_log << Logger::Error << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << strerror(err) << endl,
- log->error(Logr::Error, err, "Failed to set IPv6 socket to IPv6 only, continuing anyhow"));
+ SLOG(g_log << Logger::Error << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << stringerror(err) << endl,
+ log->error(Logr::Warning, err, "Failed to set IPv6 socket to IPv6 only, continuing anyhow"));
}
#ifdef TCP_DEFER_ACCEPT
#ifdef TCP_FASTOPEN
if (setsockopt(socketFd, IPPROTO_TCP, TCP_FASTOPEN, &SyncRes::s_tcp_fast_open, sizeof SyncRes::s_tcp_fast_open) < 0) {
int err = errno;
- SLOG(g_log << Logger::Error << "Failed to enable TCP Fast Open for listening socket: " << strerror(err) << endl,
+ SLOG(g_log << Logger::Error << "Failed to enable TCP Fast Open for listening socket: " << stringerror(err) << endl,
log->error(Logr::Error, err, "Failed to enable TCP Fast Open for listening socket"));
}
#else
}
socklen_t socklen = address.sin4.sin_family == AF_INET ? sizeof(address.sin4) : sizeof(address.sin6);
- if (::bind(socketFd, (struct sockaddr*)&address, socklen) < 0) {
+ if (::bind(socketFd, reinterpret_cast<struct sockaddr*>(&address), socklen) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
throw PDNSException("Binding TCP server socket for " + address.toStringWithPort() + ": " + stringerror());
}
void TCPOutConnectionManager::store(const struct timeval& now, const ComboAddress& ip, Connection&& connection)
{
++connection.d_numqueries;
- if (s_maxQueries > 0 && connection.d_numqueries > s_maxQueries) {
+ if (s_maxQueries > 0 && connection.d_numqueries >= s_maxQueries) {
return;
}
struct ZoneData
{
- ZoneData(Logr::log_t log, const std::string& zone) :
+ ZoneData(const std::shared_ptr<Logr::Logger>& log, const std::string& zone) :
d_log(log),
d_zone(zone),
d_now(time(nullptr)) {}
// Maybe use a SuffixMatchTree?
std::set<DNSName> d_delegations;
- Logr::log_t d_log;
+ std::shared_ptr<Logr::Logger> d_log;
DNSName d_zone;
time_t d_now;
- bool isRRSetAuth(const DNSName& qname, QType qtype) const;
- void parseDRForCache(DNSRecord& dr);
- pdns::ZoneMD::Result getByAXFR(const RecZoneToCache::Config&, pdns::ZoneMD&);
- pdns::ZoneMD::Result processLines(const std::vector<std::string>& lines, const RecZoneToCache::Config& config, pdns::ZoneMD&);
+ [[nodiscard]] bool isRRSetAuth(const DNSName& qname, QType qtype) const;
+ void parseDRForCache(DNSRecord& resourceRecord);
+ pdns::ZoneMD::Result getByAXFR(const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd);
+ pdns::ZoneMD::Result processLines(const std::vector<std::string>& lines, const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd);
void ZoneToCache(const RecZoneToCache::Config& config);
- vState dnssecValidate(pdns::ZoneMD&, size_t& zonemdCount) const;
+ vState dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const;
};
bool ZoneData::isRRSetAuth(const DNSName& qname, QType qtype) const
break;
}
delegatedZone.chopOff();
- if (delegatedZone == g_rootdnsname || delegatedZone == d_zone)
+ if (delegatedZone == g_rootdnsname || delegatedZone == d_zone) {
break;
+ }
}
return !isDelegated;
}
-void ZoneData::parseDRForCache(DNSRecord& dr)
+void ZoneData::parseDRForCache(DNSRecord& dnsRecord)
{
- if (dr.d_class != QClass::IN) {
+ if (dnsRecord.d_class != QClass::IN) {
return;
}
- const auto key = pair(dr.d_name, dr.d_type);
+ const auto key = pair(dnsRecord.d_name, dnsRecord.d_type);
- dr.d_ttl += d_now;
+ dnsRecord.d_ttl += d_now;
- switch (dr.d_type) {
+ switch (dnsRecord.d_type) {
case QType::NSEC:
case QType::NSEC3:
break;
case QType::RRSIG: {
- const auto& rr = getRR<RRSIGRecordContent>(dr);
- const auto sigkey = pair(key.first, rr->d_type);
+ const auto rrsig = getRR<RRSIGRecordContent>(dnsRecord);
+ if (rrsig == nullptr) {
+ break;
+ }
+ const auto sigkey = pair(key.first, rrsig->d_type);
auto found = d_sigs.find(sigkey);
if (found != d_sigs.end()) {
- found->second.push_back(rr);
+ found->second.push_back(rrsig);
}
else {
vector<shared_ptr<const RRSIGRecordContent>> sigsrr;
- sigsrr.push_back(rr);
+ sigsrr.push_back(rrsig);
d_sigs.insert({sigkey, sigsrr});
}
break;
}
case QType::NS:
- if (dr.d_name != d_zone) {
- d_delegations.insert(dr.d_name);
+ if (dnsRecord.d_name != d_zone) {
+ d_delegations.insert(dnsRecord.d_name);
}
break;
default:
auto found = d_all.find(key);
if (found != d_all.end()) {
- found->second.push_back(dr);
+ found->second.push_back(dnsRecord);
}
else {
- vector<DNSRecord> v;
- v.push_back(dr);
- d_all.insert({key, v});
+ vector<DNSRecord> dnsRecords;
+ dnsRecords.push_back(dnsRecord);
+ d_all.insert({key, dnsRecords});
}
}
ComboAddress primary = ComboAddress(config.d_sources.at(0), 53);
uint16_t axfrTimeout = config.d_timeout;
size_t maxReceivedBytes = config.d_maxReceivedBytes;
- const TSIGTriplet tt = config.d_tt;
+ const TSIGTriplet tsigTriplet = config.d_tt;
ComboAddress local = config.d_local;
if (local == ComboAddress()) {
local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
}
- AXFRRetriever axfr(primary, d_zone, tt, &local, maxReceivedBytes, axfrTimeout);
+ AXFRRetriever axfr(primary, d_zone, tsigTriplet, &local, maxReceivedBytes, axfrTimeout);
Resolver::res_t nop;
vector<DNSRecord> chunk;
time_t axfrStart = time(nullptr);
time_t axfrNow = time(nullptr);
- while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
- for (auto& dr : chunk) {
+ // coverity[store_truncates_time_t]
+ while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow)) != 0) {
+ for (auto& dnsRecord : chunk) {
if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
- zonemd.readRecord(dr);
+ zonemd.readRecord(dnsRecord);
}
- parseDRForCache(dr);
+ parseDRForCache(dnsRecord);
}
axfrNow = time(nullptr);
if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
}
}
if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
- bool validationDone, validationSuccess;
+ bool validationDone = false;
+ bool validationSuccess = false;
zonemd.verify(validationDone, validationSuccess);
- d_log->info("ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
+ d_log->info(Logr::Info, "ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
"validationSuccess", Logging::Loggable(validationSuccess));
if (!validationDone) {
return pdns::ZoneMD::Result::NoValidationDone;
{
std::vector<std::string> lines;
#ifdef HAVE_LIBCURL
- MiniCurl mc;
+ MiniCurl miniCurl;
ComboAddress local = config.d_local;
- std::string reply = mc.getURL(config.d_sources.at(0), nullptr, local == ComboAddress() ? nullptr : &local, config.d_timeout, false, true);
+ std::string reply = miniCurl.getURL(config.d_sources.at(0), nullptr, local == ComboAddress() ? nullptr : &local, static_cast<int>(config.d_timeout), false, true);
if (config.d_maxReceivedBytes > 0 && reply.size() > config.d_maxReceivedBytes) {
// We should actually detect this *during* the GET
throw std::runtime_error("Retrieved data exceeds maxReceivedBytes");
zpt.setMaxIncludes(0);
while (zpt.get(drr)) {
- DNSRecord dr(drr);
+ DNSRecord dnsRecord(drr);
if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
- zonemd.readRecord(dr);
+ zonemd.readRecord(dnsRecord);
}
- parseDRForCache(dr);
+ parseDRForCache(dnsRecord);
}
if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
- bool validationDone, validationSuccess;
+ bool validationDone = false;
+ bool validationSuccess = false;
zonemd.verify(validationDone, validationSuccess);
- d_log->info("ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
+ d_log->info(Logr::Info, "ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
"validationSuccess", Logging::Loggable(validationSuccess));
if (!validationDone) {
return pdns::ZoneMD::Result::NoValidationDone;
vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
{
+ pdns::validation::ValidationContext validationContext;
+ validationContext.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(validationContext.d_nsec3IterationsRemainingQuota)>::max();
zonemdCount = 0;
- SyncRes sr({d_now, 0});
- sr.setDoDNSSEC(true);
- sr.setDNSSECValidationRequested(true);
+ SyncRes resolver({d_now, 0});
+ resolver.setDoDNSSEC(true);
+ resolver.setDNSSECValidationRequested(true);
dsmap_t dsmap; // Actually a set
- vState dsState = sr.getDSRecords(d_zone, dsmap, false, 0, "");
+ vState dsState = resolver.getDSRecords(d_zone, dsmap, false, 0, "");
if (dsState != vState::Secure) {
return dsState;
}
skeyset_t dnsKeys;
sortedRecords_t records;
- if (zonemd.getDNSKEYs().size() == 0) {
+ if (zonemd.getDNSKEYs().empty()) {
return vState::BogusUnableToGetDNSKEYs;
}
for (const auto& key : zonemd.getDNSKEYs()) {
}
skeyset_t validKeys;
- vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
+ vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(QType::DNSKEY), validKeys, std::nullopt, validationContext);
if (dnsKeyState != vState::Secure) {
return dnsKeyState;
}
- if (validKeys.size() == 0) {
+ if (validKeys.empty()) {
return vState::BogusNoValidDNSKEY;
}
const auto& nsec3s = zonemd.getNSEC3s();
cspmap_t csp;
- vState nsecValidationStatus;
+ vState nsecValidationStatus = vState::Indeterminate;
- if (nsecs.records.size() > 0 && nsecs.signatures.size() > 0) {
+ if (!nsecs.records.empty() && !nsecs.signatures.empty()) {
// Valdidate the NSEC
- nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsecs.records, nsecs.signatures, validKeys, std::nullopt);
- csp.emplace(std::make_pair(d_zone, QType::NSEC), nsecs);
+ nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsecs.records, nsecs.signatures, validKeys, std::nullopt, validationContext);
+ csp.emplace(std::pair(d_zone, QType::NSEC), nsecs);
}
- else if (nsec3s.records.size() > 0 && nsec3s.signatures.size() > 0) {
+ else if (!nsec3s.records.empty() && !nsec3s.signatures.empty()) {
// Validate NSEC3PARAMS
records.clear();
for (const auto& rec : zonemd.getNSEC3Params()) {
records.emplace(rec);
}
- nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
+ nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(QType::NSEC3PARAM), validKeys, std::nullopt, validationContext);
if (nsecValidationStatus != vState::Secure) {
- d_log->info("NSEC3PARAMS records did not validate");
+ d_log->info(Logr::Warning, "NSEC3PARAMS records did not validate");
return nsecValidationStatus;
}
// Valdidate the NSEC3
- nsecValidationStatus = validateWithKeySet(d_now, zonemd.getNSEC3Label(), nsec3s.records, nsec3s.signatures, validKeys, std::nullopt);
- csp.emplace(std::make_pair(zonemd.getNSEC3Label(), QType::NSEC3), nsec3s);
+ nsecValidationStatus = validateWithKeySet(d_now, zonemd.getNSEC3Label(), nsec3s.records, nsec3s.signatures, validKeys, std::nullopt, validationContext);
+ csp.emplace(std::pair(zonemd.getNSEC3Label(), QType::NSEC3), nsec3s);
}
else {
- d_log->info("No NSEC(3) records and/or RRSIGS found to deny ZONEMD");
+ d_log->info(Logr::Warning, "No NSEC(3) records and/or RRSIGS found to deny ZONEMD");
return vState::BogusInvalidDenial;
}
if (nsecValidationStatus != vState::Secure) {
- d_log->info("zone NSEC(3) record does not validate");
+ d_log->info(Logr::Warning, "zone NSEC(3) record does not validate");
return nsecValidationStatus;
}
- auto denial = getDenial(csp, d_zone, QType::ZONEMD, false, false, std::nullopt, true);
+ auto denial = getDenial(csp, d_zone, QType::ZONEMD, false, false, validationContext, std::nullopt, true);
if (denial == dState::NXQTYPE) {
- d_log->info("Validated denial of absence of ZONEMD record");
+ d_log->info(Logr::Info, "Validated denial of existence of ZONEMD record");
return vState::Secure;
}
- d_log->info("No ZONEMD record, but NSEC(3) record does not deny it");
+ d_log->info(Logr::Warning, "No ZONEMD record, but NSEC(3) record does not deny it");
return vState::BogusInvalidDenial;
}
for (const auto& rec : zonemdRecords) {
records.emplace(rec);
}
- return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
+ return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(QType::ZONEMD), validKeys, std::nullopt, validationContext);
}
void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
{
if (config.d_sources.size() > 1) {
- d_log->info("Multiple sources not yet supported, using first");
+ d_log->info(Logr::Warning, "Multiple sources not yet supported, using first");
}
if (config.d_dnssec == pdns::ZoneMD::Config::Require && (g_dnssecmode == DNSSECMode::Off || g_dnssecmode == DNSSECMode::ProcessNoValidate)) {
auto zonemd = pdns::ZoneMD(DNSName(config.d_zone));
pdns::ZoneMD::Result result = pdns::ZoneMD::Result::OK;
if (config.d_method == "axfr") {
- d_log->info("Getting zone by AXFR");
+ d_log->info(Logr::Info, "Getting zone by AXFR");
result = getByAXFR(config, zonemd);
}
else {
vector<string> lines;
if (config.d_method == "url") {
- d_log->info("Getting zone by URL");
+ d_log->info(Logr::Info, "Getting zone by URL");
lines = getURL(config);
}
else if (config.d_method == "file") {
- d_log->info("Getting zone from file");
+ d_log->info(Logr::Info, "Getting zone from file");
lines = getLinesFromFile(config.d_sources.at(0));
}
result = processLines(lines, config, zonemd);
// Validate DNSKEYs and ZONEMD, rest of records are validated on-demand by SyncRes
if (config.d_dnssec == pdns::ZoneMD::Config::Require || (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate && config.d_dnssec != pdns::ZoneMD::Config::Ignore)) {
- size_t zonemdCount;
+ size_t zonemdCount = 0;
auto validationStatus = dnssecValidate(zonemd, zonemdCount);
- d_log->info("ZONEMD record related DNSSEC validation", "validationStatus", Logging::Loggable(validationStatus),
+ d_log->info(Logr::Info, "ZONEMD record related DNSSEC validation", "validationStatus", Logging::Loggable(validationStatus),
"zonemdCount", Logging::Loggable(zonemdCount));
if (config.d_dnssec == pdns::ZoneMD::Config::Require && validationStatus != vState::Secure) {
throw PDNSException("ZONEMD required DNSSEC validation failed");
d_now = time(nullptr);
for (const auto& [key, v] : d_all) {
const auto& [qname, qtype] = key;
+ if (qname.isWildcard()) {
+ continue;
+ }
switch (qtype) {
case QType::NSEC:
case QType::NSEC3:
- break;
case QType::RRSIG:
break;
default: {
vector<shared_ptr<const RRSIGRecordContent>> sigsrr;
- auto it = d_sigs.find(key);
- if (it != d_sigs.end()) {
- sigsrr = it->second;
+ auto iter = d_sigs.find(key);
+ if (iter != d_sigs.end()) {
+ sigsrr = iter->second;
}
bool auth = isRRSetAuth(qname, qtype);
// Same decision as updateCacheFromRecords() (we do not test for NSEC since we skip those completely)
}
}
else {
- states.emplace(std::make_pair(config.first, State{0, 0, mygeneration}));
+ states.emplace(config.first, State{0, 0, mygeneration});
}
}
}
ZoneData data(log, config.d_zone);
data.ZoneToCache(config);
state.d_waittime = config.d_refreshPeriod;
- log->info("Loaded zone into cache", "refresh", Logging::Loggable(state.d_waittime));
+ log->info(Logr::Info, "Loaded zone into cache", "refresh", Logging::Loggable(state.d_waittime));
}
catch (const PDNSException& e) {
log->error(Logr::Error, e.reason, "Unable to load zone into cache, will retry", "exception", Logging::Loggable("PDNSException"), "refresh", Logging::Loggable(state.d_waittime));
log->error(Logr::Error, e.what(), "Unable to load zone into cache, will retry", "exception", Logging::Loggable("std::runtime_error"), "refresh", Logging::Loggable(state.d_waittime));
}
catch (...) {
- log->info("Unable to load zone into cache, will retry", "refresh", Logging::Loggable(state.d_waittime));
+ log->info(Logr::Error, "Unable to load zone into cache, will retry", "refresh", Logging::Loggable(state.d_waittime));
}
state.d_lastrun = time(nullptr);
- return;
}
TSIGTriplet d_tt; // Authentication data
size_t d_maxReceivedBytes{0}; // Maximum size
time_t d_retryOnError{60}; // Retry on error
- time_t d_refreshPeriod{24 * 3600}; // Time between refetch
+ time_t d_refreshPeriod{static_cast<time_t>(24 * 3600)}; // Time between refetch
uint32_t d_timeout{20}; // timeout in seconds
pdns::ZoneMD::Config d_zonemd{pdns::ZoneMD::Config::Validate};
pdns::ZoneMD::Config d_dnssec{pdns::ZoneMD::Config::Validate};
{
time_t d_lastrun{0};
time_t d_waittime{0};
- uint64_t d_generation;
+ uint64_t d_generation{0};
};
static void maintainStates(const map<DNSName, Config>&, map<DNSName, State>&, uint64_t mygeneration);
if (elapsed >= timeout) {
throw PDNSException("Timeout waiting for control channel data");
}
+ // coverity[store_truncates_time_t]
int ret = waitForData(fd, timeout - elapsed, 0);
if (ret == 0) {
throw PDNSException("Timeout waiting for control channel data");
str.append(buffer, recvd);
}
- return {err, str};
+ return {err, std::move(str)};
}
class RecursorControlParser
{
public:
- RecursorControlParser()
- {
- }
- static void nop(void) {}
- typedef void func_t(void);
+ RecursorControlParser() = default;
+ static void nop() {}
+ using func_t = void();
- RecursorControlChannel::Answer getAnswer(int s, const std::string& question, func_t** func);
+ static RecursorControlChannel::Answer getAnswer(int socket, const std::string& question, func_t** command);
};
enum class StatComponent
#include "rec-lua-conf.hh"
#include "aggressive_nsec.hh"
+#include "coverage.hh"
#include "validate-recursor.hh"
#include "filterpo.hh"
#include "rec-tcpout.hh"
#include "rec-main.hh"
+#include "settings/cxxsettings.hh"
+
std::pair<std::string, std::string> PrefixDashNumberCompare::prefixAndTrailingNum(const std::string& a)
{
auto i = a.length();
static void addGetStat(const string& name, std::function<uint64_t()> f)
{
- d_get64bitmembers[name] = f;
+ d_get64bitmembers[name] = std::move(f);
}
static void addGetStat(const string& name, std::function<StatsMap()> f)
{
- d_getmultimembers[name] = f;
+ d_getmultimembers[name] = std::move(f);
}
static std::string getPrometheusName(const std::string& arg)
name = getPrometheusName(name);
}
- auto ret = dynmetrics{new std::atomic<unsigned long>(), name};
+ auto ret = dynmetrics{new std::atomic<unsigned long>(), std::move(name)};
(*dm)[str] = ret;
return ret.d_ptr;
}
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
return 0;
}
- fprintf(fp.get(), "; aggressive NSEC cache dump follows\n;\n");
+ fprintf(filePtr.get(), "; aggressive NSEC cache dump follows\n;\n");
struct timeval now;
Utility::gettimeofday(&now, nullptr);
- return g_aggressiveNSECCache->dumpToFile(fp, now);
+ return g_aggressiveNSECCache->dumpToFile(filePtr, now);
}
static uint64_t* pleaseDumpEDNSMap(int fd)
}
// Does not follow the generic dump to file pattern, has a more complex lambda
-static RecursorControlChannel::Answer doDumpCache(int socket)
+template <typename T>
+static RecursorControlChannel::Answer doDumpCache(int socket, T begin, T end)
{
auto fdw = getfd(socket);
if (fdw < 0) {
return {1, "Error opening dump file for writing: " + stringerror() + "\n"};
}
+ bool dumpRecordCache = true;
+ bool dumpNegCache = true;
+ bool dumpPacketCache = true;
+ bool dumpAggrCache = true;
+ if (begin != end) {
+ dumpRecordCache = false;
+ dumpNegCache = false;
+ dumpPacketCache = false;
+ dumpAggrCache = false;
+ for (auto name = begin; name != end; ++name) {
+ if (*name == "r") {
+ dumpRecordCache = true;
+ }
+ else if (*name == "n") {
+ dumpNegCache = true;
+ }
+ else if (*name == "p") {
+ dumpPacketCache = true;
+ }
+ else if (*name == "a") {
+ dumpAggrCache = true;
+ }
+ }
+ }
uint64_t total = 0;
try {
- total += g_recCache->doDump(fdw, g_maxCacheEntries.load());
- total += g_negCache->doDump(fdw, g_maxCacheEntries.load() / 8);
- total += g_packetCache ? g_packetCache->doDump(fdw) : 0;
- total += dumpAggressiveNSECCache(fdw);
+ if (dumpRecordCache) {
+ total += g_recCache->doDump(fdw, g_maxCacheEntries.load());
+ }
+ if (dumpNegCache) {
+ total += g_negCache->doDump(fdw, g_maxCacheEntries.load() / 8);
+ }
+ if (dumpPacketCache) {
+ total += g_packetCache ? g_packetCache->doDump(fdw) : 0;
+ }
+ if (dumpAggrCache) {
+ total += dumpAggressiveNSECCache(fdw);
+ }
}
catch (...) {
}
return {1, "No RPZ zone named " + zoneName + "\n"};
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fdw, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(fdw, "w"));
+ if (!filePtr) {
int err = errno;
return {1, "converting file descriptor: " + stringerror(err) + "\n"};
}
- zone->dump(fp.get());
+ zone->dump(filePtr.get());
return {0, "done\n"};
}
}
}
+template <typename T>
+static RecursorControlChannel::Answer setAggrNSECCacheSize(T begin, T end)
+{
+ if (end - begin != 1) {
+ return {1, "Need to supply new aggressive NSEC cache size\n"};
+ }
+ if (!g_aggressiveNSECCache) {
+ return {1, "Aggressive NSEC cache is disabled by startup config\n"};
+ }
+ try {
+ auto newmax = pdns::checked_stoi<uint64_t>(*begin);
+ g_aggressiveNSECCache->setMaxEntries(newmax);
+ return {0, "New aggressive NSEC cache size: " + std::to_string(newmax) + "\n"};
+ }
+ catch (const std::exception& e) {
+ return {1, "Error parsing the new aggressive NSEC cache size: " + std::string(e.what()) + "\n"};
+ }
+}
+
static uint64_t getSysTimeMsec()
{
struct rusage ru;
auto ret = new ProxyMappingStats_t;
if (t_proxyMapping) {
for (const auto& [key, entry] : *t_proxyMapping) {
- ret->emplace(std::make_pair(key, ProxyMappingCounts{entry.stats.netmaskMatches, entry.stats.suffixMatches}));
+ ret->emplace(key, ProxyMappingCounts{entry.stats.netmaskMatches, entry.stats.suffixMatches});
}
}
return ret;
if (t_protobufServers.servers) {
for (const auto& server : *t_protobufServers.servers) {
- ret->emplace(std::make_pair(server->address(), server->getStats()));
+ ret->emplace(server->address(), server->getStats());
}
}
return ret.release();
if (t_outgoingProtobufServers.servers) {
for (const auto& server : *t_outgoingProtobufServers.servers) {
- ret->emplace(std::make_pair(server->address(), server->getStats()));
+ ret->emplace(server->address(), server->getStats());
}
}
return ret.release();
if (t_frameStreamServersInfo.servers) {
for (const auto& server : *t_frameStreamServersInfo.servers) {
- ret->emplace(std::make_pair(server->address(), server->getStats()));
+ ret->emplace(server->address(), server->getStats());
}
}
return ret.release();
if (t_nodFrameStreamServersInfo.servers) {
for (const auto& server : *t_nodFrameStreamServersInfo.servers) {
- ret->emplace(std::make_pair(server->address(), server->getStats()));
+ ret->emplace(server->address(), server->getStats());
}
}
return ret.release();
struct timeval now;
gettimeofday(&now, 0);
- ostr << getMT()->d_waiters.size() << " currently outstanding questions\n";
+ ostr << getMT()->getWaiters().size() << " currently outstanding questions\n";
boost::format fmt("%1% %|40t|%2% %|47t|%3% %|63t|%4% %|68t|%5% %|78t|%6%\n");
ostr << (fmt % "qname" % "qtype" % "remote" % "tcp" % "chained" % "spent(ms)");
unsigned int n = 0;
- for (const auto& mthread : getMT()->d_waiters) {
+ for (const auto& mthread : getMT()->getWaiters()) {
const std::shared_ptr<PacketID>& pident = mthread.key;
const double spent = g_networkTimeoutMsec - (DiffTime(now, mthread.ttd) * 1000);
ostr << (fmt
return g_recCache->bytes();
}
-static uint64_t doGetCacheHits()
-{
- return g_recCache->cacheHits;
-}
-
-static uint64_t doGetCacheMisses()
-{
- return g_recCache->cacheMisses;
-}
-
static uint64_t doGetMallocated()
{
// this turned out to be broken
for (const auto& bucket : data) {
snprintf(buf, sizeof(buf), "%g", bucket.d_boundary / 1e6);
std::string pname = pbasename + "seconds_bucket{" + "le=\"" + (bucket.d_boundary == std::numeric_limits<uint64_t>::max() ? "+Inf" : buf) + "\"}";
- entries.emplace(bucket.d_name, StatsMapEntry{pname, std::to_string(bucket.d_count)});
+ entries.emplace(bucket.d_name, StatsMapEntry{std::move(pname), std::to_string(bucket.d_count)});
}
snprintf(buf, sizeof(buf), "%g", histogram.getSum() / 1e6);
for (const auto& entry : rcodes) {
const auto key = RCode::to_short_s(n);
std::string pname = pbasename + "{rcode=\"" + key + "\"}";
- entries.emplace("auth-" + key + "-answers", StatsMapEntry{pname, std::to_string(entry)});
+ entries.emplace("auth-" + key + "-answers", StatsMapEntry{std::move(pname), std::to_string(entry)});
n++;
}
return entries;
{
const string pbasename = getPrometheusName(name);
StatsMap entries;
- // Only distr and worker threads, I think we should revisit this as we now not only have the handler thread but also
- // taskThread(s).
- for (unsigned int n = 0; n < RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers(); ++n) {
+
+ // Handler is not reported
+ for (unsigned int n = 0; n < RecThreadInfo::numRecursorThreads() - 1; ++n) {
uint64_t tm = doGetThreadCPUMsec(n);
std::string pname = pbasename + "{thread=\"" + std::to_string(n) + "\"}";
- entries.emplace(name + "-thread-" + std::to_string(n), StatsMapEntry{pname, std::to_string(tm)});
+ entries.emplace(name + "-thread-" + std::to_string(n), StatsMapEntry{std::move(pname), std::to_string(tm)});
}
return entries;
}
sname = name + "-rpz-" + key;
pname = pbasename + "{type=\"rpz\",policyname=\"" + key + "\"}";
}
- entries.emplace(sname, StatsMapEntry{pname, std::to_string(count)});
+ entries.emplace(sname, StatsMapEntry{std::move(pname), std::to_string(count)});
total += count;
}
entries.emplace(name, StatsMapEntry{pbasename, std::to_string(total)});
auto keyname = pbasename + "{netmask=\"" + key.toString() + "\",count=\"";
auto sname1 = name + "-n-" + std::to_string(count);
auto pname1 = keyname + "netmaskmatches\"}";
- entries.emplace(sname1, StatsMapEntry{pname1, std::to_string(entry.netmaskMatches)});
+ entries.emplace(sname1, StatsMapEntry{std::move(pname1), std::to_string(entry.netmaskMatches)});
auto sname2 = name + "-s-" + std::to_string(count);
auto pname2 = keyname + "suffixmatches\"}";
- entries.emplace(sname2, StatsMapEntry{pname2, std::to_string(entry.suffixMatches)});
+ entries.emplace(sname2, StatsMapEntry{std::move(pname2), std::to_string(entry.suffixMatches)});
count++;
}
return entries;
auto keyname = pbasename + "{address=\"" + key + "\",type=\"" + type + "\",count=\"";
auto sname1 = name + "-q-" + std::to_string(count);
auto pname1 = keyname + "queued\"}";
- entries.emplace(sname1, StatsMapEntry{pname1, std::to_string(entry.d_queued)});
+ entries.emplace(sname1, StatsMapEntry{std::move(pname1), std::to_string(entry.d_queued)});
auto sname2 = name + "-p-" + std::to_string(count);
auto pname2 = keyname + "pipeFull\"}";
- entries.emplace(sname2, StatsMapEntry{pname2, std::to_string(entry.d_pipeFull)});
+ entries.emplace(sname2, StatsMapEntry{std::move(pname2), std::to_string(entry.d_pipeFull)});
auto sname3 = name + "-t-" + std::to_string(count);
auto pname3 = keyname + "tooLarge\"}";
- entries.emplace(sname3, StatsMapEntry{pname3, std::to_string(entry.d_tooLarge)});
+ entries.emplace(sname3, StatsMapEntry{std::move(pname3), std::to_string(entry.d_tooLarge)});
auto sname4 = name + "-o-" + std::to_string(count);
auto pname4 = keyname + "otherError\"}";
- entries.emplace(sname4, StatsMapEntry{pname4, std::to_string(entry.d_otherError)});
+ entries.emplace(sname4, StatsMapEntry{std::move(pname4), std::to_string(entry.d_otherError)});
++count;
}
}
addGetStat("ipv6-questions", [] { return g_Counters.sum(rec::Counter::ipv6qcounter); });
addGetStat("tcp-questions", [] { return g_Counters.sum(rec::Counter::tcpqcounter); });
- addGetStat("cache-hits", doGetCacheHits);
- addGetStat("cache-misses", doGetCacheMisses);
+ addGetStat("cache-hits", []() { return g_recCache->getCacheHits(); });
+ addGetStat("cache-misses", []() { return g_recCache->getCacheMisses(); });
addGetStat("cache-entries", doGetCacheSize);
addGetStat("max-cache-entries", []() { return g_maxCacheEntries.load(); });
addGetStat("max-packetcache-entries", []() { return g_maxPacketCacheEntries.load(); });
addGetStat("maintenance-usec", [] { return g_Counters.sum(rec::Counter::maintenanceUsec); });
addGetStat("maintenance-calls", [] { return g_Counters.sum(rec::Counter::maintenanceCalls); });
+ addGetStat("nod-events", [] { return g_Counters.sum(rec::Counter::nodCount); });
+ addGetStat("udr-events", [] { return g_Counters.sum(rec::Counter::udrCount); });
+
/* make sure that the ECS stats are properly initialized */
SyncRes::clearECSStats();
for (size_t idx = 0; idx < SyncRes::s_ecsResponsesBySubnetSize4.size(); idx++) {
g_log << Logger::Error << "Exiting on user request" << endl;
g_rcc.~RecursorControlChannel();
- if (!g_pidfname.empty())
+ if (!g_pidfname.empty()) {
unlink(g_pidfname.c_str()); // we can at least try..
+ }
+
if (nicely) {
RecursorControlChannel::stop = true;
}
else {
+ pdns::coverage::dumpCoverageData();
_exit(1);
}
}
return nullptr;
}
-RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const string& question, RecursorControlParser::func_t** command)
+static RecursorControlChannel::Answer help()
+{
+ return {0,
+ "add-dont-throttle-names [N...] add names that are not allowed to be throttled\n"
+ "add-dont-throttle-netmasks [N...]\n"
+ " add netmasks that are not allowed to be throttled\n"
+ "add-nta DOMAIN [REASON] add a Negative Trust Anchor for DOMAIN with the comment REASON\n"
+ "add-ta DOMAIN DSRECORD add a Trust Anchor for DOMAIN with data DSRECORD\n"
+ "current-queries show currently active queries\n"
+ "clear-dont-throttle-names [N...] remove names that are not allowed to be throttled. If N is '*', remove all\n"
+ "clear-dont-throttle-netmasks [N...]\n"
+ " remove netmasks that are not allowed to be throttled. If N is '*', remove all\n"
+ "clear-nta [DOMAIN]... Clear the Negative Trust Anchor for DOMAINs, if no DOMAIN is specified, remove all\n"
+ "clear-ta [DOMAIN]... Clear the Trust Anchor for DOMAINs\n"
+ "dump-cache <filename> dump cache contents to the named file\n"
+ "dump-dot-probe-map <filename> dump the contents of the DoT probe map to the named file\n"
+ "dump-edns [status] <filename> dump EDNS status to the named file\n"
+ "dump-failedservers <filename> dump the failed servers to the named file\n"
+ "dump-non-resolving <filename> dump non-resolving nameservers addresses to the named file\n"
+ "dump-nsspeeds <filename> dump nsspeeds statistics to the named file\n"
+ "dump-saved-parent-ns-sets <filename>\n"
+ " dump saved parent ns sets that were successfully used as fallback\n"
+ "dump-rpz <zone name> <filename> dump the content of a RPZ zone to the named file\n"
+ "dump-throttlemap <filename> dump the contents of the throttle map to the named file\n"
+ "get [key1] [key2] .. get specific statistics\n"
+ "get-all get all statistics\n"
+ "get-dont-throttle-names get the list of names that are not allowed to be throttled\n"
+ "get-dont-throttle-netmasks get the list of netmasks that are not allowed to be throttled\n"
+ "get-ntas get all configured Negative Trust Anchors\n"
+ "get-tas get all configured Trust Anchors\n"
+ "get-parameter [key1] [key2] .. get configuration parameters\n"
+ "get-proxymapping-stats get proxy mapping statistics\n"
+ "get-qtypelist get QType statistics\n"
+ " notice: queries from cache aren't being counted yet\n"
+ "get-remotelogger-stats get remote logger statistics\n"
+ "hash-password [work-factor] ask for a password then return the hashed version\n"
+ "help get this list\n"
+ "list-dnssec-algos list supported DNSSEC algorithms\n"
+ "ping check that all threads are alive\n"
+ "quit stop the recursor daemon\n"
+ "quit-nicely stop the recursor daemon nicely\n"
+ "reload-acls reload ACLS\n"
+ "reload-lua-script [filename] (re)load Lua script\n"
+ "reload-lua-config [filename] (re)load Lua configuration file\n"
+ "reload-zones reload all auth and forward zones\n"
+ "set-ecs-minimum-ttl value set ecs-minimum-ttl-override\n"
+ "set-max-aggr-nsec-cache-size value set new maximum aggressive NSEC cache size\n"
+ "set-max-cache-entries value set new maximum record cache size\n"
+ "set-max-packetcache-entries val set new maximum packet cache size\n"
+ "set-minimum-ttl value set minimum-ttl-override\n"
+ "set-carbon-server set a carbon server for telemetry\n"
+ "set-dnssec-log-bogus SETTING enable (SETTING=yes) or disable (SETTING=no) logging of DNSSEC validation failures\n"
+ "set-event-trace-enabled SETTING set logging of event trace messages, 0 = disabled, 1 = protobuf, 2 = log file, 3 = both\n"
+ "show-yaml [file] show yaml config derived from old-style config\n"
+ "trace-regex [regex file] emit resolution trace for matching queries (no arguments clears tracing)\n"
+ "top-largeanswer-remotes show top remotes receiving large answers\n"
+ "top-queries show top queries\n"
+ "top-pub-queries show top queries grouped by public suffix list\n"
+ "top-remotes show top remotes\n"
+ "top-timeouts show top downstream timeouts\n"
+ "top-servfail-queries show top queries receiving servfail answers\n"
+ "top-bogus-queries show top queries validating as bogus\n"
+ "top-pub-servfail-queries show top queries receiving servfail answers grouped by public suffix list\n"
+ "top-pub-bogus-queries show top queries validating as bogus grouped by public suffix list\n"
+ "top-servfail-remotes show top remotes receiving servfail answers\n"
+ "top-bogus-remotes show top remotes receiving bogus answers\n"
+ "unload-lua-script unload Lua script\n"
+ "version return Recursor version number\n"
+ "wipe-cache domain0 [domain1] .. wipe domain data from cache\n"
+ "wipe-cache-typed type domain0 [domain1] .. wipe domain data with qtype from cache\n"};
+}
+
+template <typename T>
+static RecursorControlChannel::Answer luaconfig(T begin, T end)
+{
+ if (begin != end) {
+ ::arg().set("lua-config-file") = *begin;
+ }
+ try {
+ luaConfigDelayedThreads delayedLuaThreads;
+ ProxyMapping proxyMapping;
+ loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
+ startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
+ broadcastFunction([=] { return pleaseSupplantProxyMapping(proxyMapping); });
+ g_log << Logger::Warning << "Reloaded Lua configuration file '" << ::arg()["lua-config-file"] << "', requested via control channel" << endl;
+ return {0, "Reloaded Lua configuration file '" + ::arg()["lua-config-file"] + "'\n"};
+ }
+ catch (std::exception& e) {
+ return {1, "Unable to load Lua script from '" + ::arg()["lua-config-file"] + "': " + e.what() + "\n"};
+ }
+ catch (const PDNSException& e) {
+ return {1, "Unable to load Lua script from '" + ::arg()["lua-config-file"] + "': " + e.reason + "\n"};
+ }
+}
+
+static RecursorControlChannel::Answer reloadACLs()
+{
+ if (!::arg()["chroot"].empty()) {
+ g_log << Logger::Error << "Unable to reload ACL when chroot()'ed, requested via control channel" << endl;
+ return {1, "Unable to reload ACL when chroot()'ed, please restart\n"};
+ }
+
+ try {
+ parseACLs();
+ }
+ catch (std::exception& e) {
+ g_log << Logger::Error << "Reloading ACLs failed (Exception: " << e.what() << ")" << endl;
+ return {1, e.what() + string("\n")};
+ }
+ catch (PDNSException& ae) {
+ g_log << Logger::Error << "Reloading ACLs failed (PDNSException: " << ae.reason << ")" << endl;
+ return {1, ae.reason + string("\n")};
+ }
+ return {0, "ok\n"};
+}
+
+RecursorControlChannel::Answer RecursorControlParser::getAnswer(int socket, const string& question, RecursorControlParser::func_t** command)
{
*command = nop;
vector<string> words;
stringtok(words, question);
- if (words.empty())
+ if (words.empty()) {
return {1, "invalid command\n"};
+ }
string cmd = toLower(words[0]);
- vector<string>::const_iterator begin = words.begin() + 1, end = words.end();
+ auto begin = words.begin() + 1;
+ auto end = words.end();
// should probably have a smart dispatcher here, like auth has
- if (cmd == "help")
- return {0,
- "add-dont-throttle-names [N...] add names that are not allowed to be throttled\n"
- "add-dont-throttle-netmasks [N...]\n"
- " add netmasks that are not allowed to be throttled\n"
- "add-nta DOMAIN [REASON] add a Negative Trust Anchor for DOMAIN with the comment REASON\n"
- "add-ta DOMAIN DSRECORD add a Trust Anchor for DOMAIN with data DSRECORD\n"
- "current-queries show currently active queries\n"
- "clear-dont-throttle-names [N...] remove names that are not allowed to be throttled. If N is '*', remove all\n"
- "clear-dont-throttle-netmasks [N...]\n"
- " remove netmasks that are not allowed to be throttled. If N is '*', remove all\n"
- "clear-nta [DOMAIN]... Clear the Negative Trust Anchor for DOMAINs, if no DOMAIN is specified, remove all\n"
- "clear-ta [DOMAIN]... Clear the Trust Anchor for DOMAINs\n"
- "dump-cache <filename> dump cache contents to the named file\n"
- "dump-dot-probe-map <filename> dump the contents of the DoT probe map to the named file\n"
- "dump-edns [status] <filename> dump EDNS status to the named file\n"
- "dump-failedservers <filename> dump the failed servers to the named file\n"
- "dump-non-resolving <filename> dump non-resolving nameservers addresses to the named file\n"
- "dump-nsspeeds <filename> dump nsspeeds statistics to the named file\n"
- "dump-saved-parent-ns-sets <filename>\n"
- " dump saved parent ns sets that were successfully used as fallback\n"
- "dump-rpz <zone name> <filename> dump the content of a RPZ zone to the named file\n"
- "dump-throttlemap <filename> dump the contents of the throttle map to the named file\n"
- "get [key1] [key2] .. get specific statistics\n"
- "get-all get all statistics\n"
- "get-dont-throttle-names get the list of names that are not allowed to be throttled\n"
- "get-dont-throttle-netmasks get the list of netmasks that are not allowed to be throttled\n"
- "get-ntas get all configured Negative Trust Anchors\n"
- "get-tas get all configured Trust Anchors\n"
- "get-parameter [key1] [key2] .. get configuration parameters\n"
- "get-proxymapping-stats get proxy mapping statistics\n"
- "get-qtypelist get QType statistics\n"
- " notice: queries from cache aren't being counted yet\n"
- "get-remotelogger-stats get remote logger statistics\n"
- "hash-password [work-factor] ask for a password then return the hashed version\n"
- "help get this list\n"
- "ping check that all threads are alive\n"
- "quit stop the recursor daemon\n"
- "quit-nicely stop the recursor daemon nicely\n"
- "reload-acls reload ACLS\n"
- "reload-lua-script [filename] (re)load Lua script\n"
- "reload-lua-config [filename] (re)load Lua configuration file\n"
- "reload-zones reload all auth and forward zones\n"
- "set-ecs-minimum-ttl value set ecs-minimum-ttl-override\n"
- "set-max-cache-entries value set new maximum cache size\n"
- "set-max-packetcache-entries val set new maximum packet cache size\n"
- "set-minimum-ttl value set minimum-ttl-override\n"
- "set-carbon-server set a carbon server for telemetry\n"
- "set-dnssec-log-bogus SETTING enable (SETTING=yes) or disable (SETTING=no) logging of DNSSEC validation failures\n"
- "set-event-trace-enabled SETTING set logging of event trace messages, 0 = disabled, 1 = protobuf, 2 = log file, 3 = both\n"
- "trace-regex [regex file] emit resolution trace for matching queries (no arguments clears tracing)\n"
- "top-largeanswer-remotes show top remotes receiving large answers\n"
- "top-queries show top queries\n"
- "top-pub-queries show top queries grouped by public suffix list\n"
- "top-remotes show top remotes\n"
- "top-timeouts show top downstream timeouts\n"
- "top-servfail-queries show top queries receiving servfail answers\n"
- "top-bogus-queries show top queries validating as bogus\n"
- "top-pub-servfail-queries show top queries receiving servfail answers grouped by public suffix list\n"
- "top-pub-bogus-queries show top queries validating as bogus grouped by public suffix list\n"
- "top-servfail-remotes show top remotes receiving servfail answers\n"
- "top-bogus-remotes show top remotes receiving bogus answers\n"
- "unload-lua-script unload Lua script\n"
- "version return Recursor version number\n"
- "wipe-cache domain0 [domain1] .. wipe domain data from cache\n"
- "wipe-cache-typed type domain0 [domain1] .. wipe domain data with qtype from cache\n"};
-
+ if (cmd == "help") {
+ return help();
+ }
if (cmd == "get-all") {
return {0, getAllStats()};
}
return {0, "bye nicely\n"};
}
if (cmd == "dump-cache") {
- return doDumpCache(s);
+ return doDumpCache(socket, begin, end);
}
if (cmd == "dump-dot-probe-map") {
- return doDumpToFile(s, pleaseDumpDoTProbeMap, cmd, false);
+ return doDumpToFile(socket, pleaseDumpDoTProbeMap, cmd, false);
}
if (cmd == "dump-ednsstatus" || cmd == "dump-edns") {
- return doDumpToFile(s, pleaseDumpEDNSMap, cmd, false);
+ return doDumpToFile(socket, pleaseDumpEDNSMap, cmd, false);
}
if (cmd == "dump-nsspeeds") {
- return doDumpToFile(s, pleaseDumpNSSpeeds, cmd, false);
+ return doDumpToFile(socket, pleaseDumpNSSpeeds, cmd, false);
}
if (cmd == "dump-failedservers") {
- return doDumpToFile(s, pleaseDumpFailedServers, cmd, false);
+ return doDumpToFile(socket, pleaseDumpFailedServers, cmd, false);
}
if (cmd == "dump-saved-parent-ns-sets") {
- return doDumpToFile(s, pleaseDumpSavedParentNSSets, cmd, false);
+ return doDumpToFile(socket, pleaseDumpSavedParentNSSets, cmd, false);
}
if (cmd == "dump-rpz") {
- return doDumpRPZ(s, begin, end);
+ return doDumpRPZ(socket, begin, end);
}
if (cmd == "dump-throttlemap") {
- return doDumpToFile(s, pleaseDumpThrottleMap, cmd, false);
+ return doDumpToFile(socket, pleaseDumpThrottleMap, cmd, false);
}
if (cmd == "dump-non-resolving") {
- return doDumpToFile(s, pleaseDumpNonResolvingNS, cmd, false);
+ return doDumpToFile(socket, pleaseDumpNonResolvingNS, cmd, false);
}
if (cmd == "wipe-cache" || cmd == "flushname") {
return {0, doWipeCache(begin, end, 0xffff)};
return doQueueReloadLuaScript(begin, end);
}
if (cmd == "reload-lua-config") {
- if (begin != end)
- ::arg().set("lua-config-file") = *begin;
-
- try {
- luaConfigDelayedThreads delayedLuaThreads;
- ProxyMapping proxyMapping;
- loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
- startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
- broadcastFunction([=] { return pleaseSupplantProxyMapping(proxyMapping); });
- g_log << Logger::Warning << "Reloaded Lua configuration file '" << ::arg()["lua-config-file"] << "', requested via control channel" << endl;
- return {0, "Reloaded Lua configuration file '" + ::arg()["lua-config-file"] + "'\n"};
- }
- catch (std::exception& e) {
- return {1, "Unable to load Lua script from '" + ::arg()["lua-config-file"] + "': " + e.what() + "\n"};
- }
- catch (const PDNSException& e) {
- return {1, "Unable to load Lua script from '" + ::arg()["lua-config-file"] + "': " + e.reason + "\n"};
- }
+ return luaconfig(begin, end);
}
if (cmd == "set-carbon-server") {
return {0, doSetCarbonServer(begin, end)};
}
if (cmd == "trace-regex") {
- return {0, doTraceRegex(begin == end ? FDWrapper(-1) : getfd(s), begin, end)};
+ return {0, doTraceRegex(begin == end ? FDWrapper(-1) : getfd(socket), begin, end)};
}
if (cmd == "unload-lua-script") {
vector<string> empty;
- empty.push_back(string());
+ empty.emplace_back();
return doQueueReloadLuaScript(empty.begin(), empty.end());
}
if (cmd == "reload-acls") {
- if (!::arg()["chroot"].empty()) {
- g_log << Logger::Error << "Unable to reload ACL when chroot()'ed, requested via control channel" << endl;
- return {1, "Unable to reload ACL when chroot()'ed, please restart\n"};
- }
-
- try {
- parseACLs();
- }
- catch (std::exception& e) {
- g_log << Logger::Error << "Reloading ACLs failed (Exception: " << e.what() << ")" << endl;
- return {1, e.what() + string("\n")};
- }
- catch (PDNSException& ae) {
- g_log << Logger::Error << "Reloading ACLs failed (PDNSException: " << ae.reason << ")" << endl;
- return {1, ae.reason + string("\n")};
- }
- return {0, "ok\n"};
+ return reloadACLs();
}
if (cmd == "top-remotes") {
return {0, doGenericTopRemotes(pleaseGetRemotes)};
g_log << Logger::Error << "Unable to reload zones and forwards when chroot()'ed, requested via control channel" << endl;
return {1, "Unable to reload zones and forwards when chroot()'ed, please restart\n"};
}
- return {0, reloadZoneConfiguration()};
+ return {0, reloadZoneConfiguration(g_yamlSettings)};
}
if (cmd == "set-ecs-minimum-ttl") {
return {0, setMinimumECSTTL(begin, end)};
if (cmd == "get-remotelogger-stats") {
return {0, getRemoteLoggerStats()};
}
+ if (cmd == "list-dnssec-algos") {
+ return {0, DNSCryptoKeyEngine::listSupportedAlgoNames()};
+ }
+ if (cmd == "set-aggr-nsec-cache-size") {
+ return setAggrNSECCacheSize(begin, end);
+ }
return {1, "Unknown command '" + cmd + "', try 'help'\n"};
}
#include "credentials.hh"
#include "namespaces.hh"
#include "rec_channel.hh"
+#include "settings/cxxsettings.hh"
ArgvMap& arg()
{
exit(arg().mustDo("help") ? 0 : 99);
}
- string configname = ::arg()["config-dir"] + "/recursor.conf";
- if (::arg()["config-name"] != "")
- configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+ string configname = ::arg()["config-dir"] + "/recursor";
+ if (!::arg()["config-name"].empty()) {
+ configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
+ }
cleanSlashes(configname);
- arg().laxFile(configname.c_str());
+ const string yamlconfigname = configname + ".yml";
+ string msg;
+ pdns::rust::settings::rec::Recursorsettings settings;
- arg().laxParse(argc, argv); // make sure the commandline wins
+ auto yamlstatus = pdns::settings::rec::readYamlSettings(yamlconfigname, "", settings, msg, g_slog);
+
+ switch (yamlstatus) {
+ case pdns::settings::rec::YamlSettingsStatus::CannotOpen:
+ break;
+ case pdns::settings::rec::YamlSettingsStatus::PresentButFailed:
+ cerr << "YAML config found for configname '" << yamlconfigname << "' but error ocurred processing it" << endl;
+ exit(1); // NOLINT(concurrency-mt-unsafe)
+ break;
+ case pdns::settings::rec::YamlSettingsStatus::OK:
+ cout << "YAML config found and processed for configname '" << yamlconfigname << "'" << endl;
+ pdns::settings::rec::bridgeStructToOldStyleSettings(settings);
+ break;
+ }
+ if (yamlstatus == pdns::settings::rec::YamlSettingsStatus::CannotOpen) {
+ configname += ".conf";
+ arg().laxFile(configname);
+ }
+ arg().laxParse(argc, argv); // make sure the commandline wins
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") = ::arg()["chroot"] + "/";
+ }
}
else if (!::arg()["chroot"].empty()) {
::arg().set("socket-dir") = ::arg()["chroot"] + "/" + ::arg()["socket-dir"];
}
}
+static std::string showIncludeYAML(::rust::String& rdirname)
+{
+ std::string msg;
+ if (rdirname.empty()) {
+ return msg;
+ }
+ const auto dirname = string(rdirname);
+
+ std::vector<std::string> confFiles;
+ ::arg().gatherIncludes(dirname, ".conf", confFiles);
+ msg += "# Found " + std::to_string(confFiles.size()) + " .conf file" + addS(confFiles.size()) + " in " + dirname + "\n";
+ for (const auto& confFile : confFiles) {
+ auto converted = pdns::settings::rec::oldStyleSettingsFileToYaml(confFile, false);
+ msg += "# Converted include-dir " + confFile + " to YAML format:\n";
+ msg += converted;
+ msg += "# Validation result: ";
+ try {
+ // Parse back and validate
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(converted);
+ settings.validate();
+ msg += "OK";
+ }
+ catch (const rust::Error& err) {
+ msg += err.what();
+ }
+ msg += "\n# End of converted " + confFile + "\n#\n";
+ }
+ return msg;
+}
+
+static std::string showForwardFileYAML(const ::rust::string& rfilename)
+{
+ std::string msg;
+ if (rfilename.empty() || boost::ends_with(rfilename, ".yml")) {
+ return msg;
+ }
+ const std::string filename = string(rfilename);
+
+ msg += "# Converted " + filename + " to YAML format for recursor.forward_zones_file: \n";
+ rust::Vec<pdns::rust::settings::rec::ForwardZone> forwards;
+ pdns::settings::rec::oldStyleForwardsFileToBridgeStruct(filename, forwards);
+ auto yaml = pdns::rust::settings::rec::forward_zones_to_yaml_string(forwards);
+ msg += std::string(yaml);
+ msg += "# Validation result: ";
+ try {
+ pdns::rust::settings::rec::validate_forward_zones("forward_zones", forwards);
+ msg += "OK";
+ }
+ catch (const rust::Error& err) {
+ msg += err.what();
+ }
+ msg += "\n# End of converted " + filename + "\n#\n";
+ return msg;
+}
+
+static std::string showAllowYAML(const ::rust::String& rfilename, const string& section, const string& key, const std::function<void(const ::rust::String&, const ::rust::Vec<::rust::String>&)>& func)
+{
+ std::string msg;
+ if (rfilename.empty() || boost::ends_with(rfilename, ".yml")) {
+ return msg;
+ }
+ const std::string filename = string(rfilename);
+
+ msg += "# Converted " + filename + " to YAML format for " + section + "." + key + ": \n";
+ rust::Vec<::rust::String> allows;
+ pdns::settings::rec::oldStyleAllowFileToBridgeStruct(filename, allows);
+ auto yaml = pdns::rust::settings::rec::allow_from_to_yaml_string(allows);
+ msg += std::string(yaml);
+ msg += "# Validation result: ";
+ try {
+ func(key, allows);
+ msg += "OK";
+ }
+ catch (const rust::Error& err) {
+ msg += err.what();
+ }
+ msg += "\n# End of converted " + filename + "\n#\n";
+ return msg;
+}
+
+static RecursorControlChannel::Answer showYAML(const std::string& path)
+{
+ string configName = ::arg()["config-dir"] + "/recursor.conf";
+ if (!::arg()["config-name"].empty()) {
+ configName = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+ }
+ if (!path.empty()) {
+ configName = path;
+ }
+ cleanSlashes(configName);
+
+ try {
+ std::string msg;
+ auto converted = pdns::settings::rec::oldStyleSettingsFileToYaml(configName, true);
+ msg += "# Start of converted recursor.yml based on " + configName + "\n";
+ msg += converted;
+ msg += "# Validation result: ";
+ pdns::rust::settings::rec::Recursorsettings mainsettings;
+ try {
+ // Parse back and validate
+ mainsettings = pdns::rust::settings::rec::parse_yaml_string(converted);
+ mainsettings.validate();
+ msg += "OK";
+ }
+ catch (const rust::Error& err) {
+ msg += err.what();
+ }
+ msg += "\n# End of converted " + configName + "\n#\n";
+
+ msg += showIncludeYAML(mainsettings.recursor.include_dir);
+ msg += showForwardFileYAML(mainsettings.recursor.forward_zones_file);
+ msg += showAllowYAML(mainsettings.incoming.allow_from_file, "incoming", "allow_from_file", pdns::rust::settings::rec::validate_allow_from);
+ msg += showAllowYAML(mainsettings.incoming.allow_notify_from_file, "incoming", "allow_notify_from_file", pdns::rust::settings::rec::validate_allow_from);
+ msg += showAllowYAML(mainsettings.incoming.allow_notify_for_file, "incoming", "allow_notify_for_file", pdns::rust::settings::rec::validate_allow_for);
+ return {0, std::move(msg)};
+ }
+ catch (const rust::Error& err) {
+ return {1, std::string(err.what())};
+ }
+ catch (const PDNSException& err) {
+ return {1, std::string(err.reason)};
+ }
+ catch (const std::exception& err) {
+ return {1, std::string(err.what())};
+ }
+}
+
int main(int argc, char** argv)
{
g_slogStructured = false;
const vector<string>& commands = arg().getCommands();
- if (commands.size() >= 1 && commands.at(0) == "hash-password") {
+ if (!commands.empty() && commands.at(0) == "show-yaml") {
+ auto [ret, str] = showYAML(commands.size() > 1 ? commands.at(1) : "");
+ cout << str << endl;
+ return ret;
+ }
+
+ if (!commands.empty() && commands.at(0) == "hash-password") {
uint64_t workFactor = CredentialsHolder::s_defaultWorkFactor;
if (commands.size() > 1) {
try {
auto timeout = arg().asNum("timeout");
RecursorControlChannel rccS;
rccS.connect(arg()["socket-dir"], sockname);
- rccS.send(rccS.d_fd, {0, command}, timeout, fd);
+ rccS.send(rccS.d_fd, {0, std::move(command)}, timeout, fd);
auto receive = rccS.recv(rccS.d_fd, timeout);
if (receive.d_ret != 0) {
{
uint64_t count = 0;
for (const auto& map : d_maps) {
- count += map.d_entriesCount;
+ count += map.getEntriesCount();
}
return count;
}
}
if (qtype == 0xffff || iter->d_type == qtype) {
iter = idx.erase(iter);
- map.d_entriesCount--;
+ map.decEntriesCount();
count++;
}
else {
}
if (now < iter->d_ttd) { // it is right, it is fresh!
+ // coverity[store_truncates_time_t]
*age = static_cast<uint32_t>(now - iter->d_creation);
// we know ttl is > 0
auto ttl = static_cast<uint32_t>(iter->d_ttd - now);
- if (s_refresh_ttlperc > 0 && !iter->d_submitted) {
- const uint32_t deadline = iter->getOrigTTL() * s_refresh_ttlperc / 100;
- const bool almostExpired = ttl <= deadline;
- if (almostExpired) {
- iter->d_submitted = true;
- pushAlmostExpiredTask(qname, qtype, iter->d_ttd, Netmask());
+ if (s_refresh_ttlperc > 0 && !iter->d_submitted && taskQTypeIsSupported(qtype)) {
+ const dnsheader_aligned header(iter->d_packet.data());
+ const auto* headerPtr = header.get();
+ if (headerPtr->rcode == RCode::NoError) {
+ const uint32_t deadline = iter->getOrigTTL() * s_refresh_ttlperc / 100;
+ const bool almostExpired = ttl <= deadline;
+ if (almostExpired) {
+ iter->d_submitted = true;
+ pushAlmostExpiredTask(qname, qtype, iter->d_ttd, Netmask());
+ }
}
}
*responsePacket = iter->d_packet;
return;
}
- struct Entry entry(qname, qtype, qclass, std::move(responsePacket), std::move(query), tcp, qhash, now + ttl, now, tag, valState);
+ struct Entry entry(DNSName(qname), qtype, qclass, std::move(responsePacket), std::move(query), tcp, qhash, now + ttl, now, tag, valState);
if (pbdata) {
entry.d_pbdata = std::move(*pbdata);
}
shard->d_map.insert(entry);
- map.d_entriesCount++;
+ map.incEntriesCount();
if (shard->d_map.size() > shard->d_shardSize) {
auto& seq_idx = shard->d_map.get<SequencedTag>();
seq_idx.erase(seq_idx.begin());
- map.d_entriesCount--;
+ map.decEntriesCount();
}
- assert(map.d_entriesCount == shard->d_map.size()); // XXX
+ assert(map.getEntriesCount() == shard->d_map.size()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clib implementation
}
-void RecursorPacketCache::doPruneTo(size_t maxSize)
+void RecursorPacketCache::doPruneTo(time_t now, size_t maxSize)
{
size_t cacheSize = size();
- pruneMutexCollectionsVector<SequencedTag>(*this, d_maps, maxSize, cacheSize);
+ pruneMutexCollectionsVector<SequencedTag>(now, d_maps, maxSize, cacheSize);
}
uint64_t RecursorPacketCache::doDump(int file)
if (fdupped == -1) {
return 0;
}
- auto filePtr = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(fdupped, "w"), fclose);
+ auto filePtr = pdns::UniqueFilePtr(fdopen(fdupped, "w"));
if (!filePtr) {
close(fdupped);
return 0;
*/
#pragma once
#include <string>
-#include <inttypes.h>
+#include <cinttypes>
#include "dns.hh"
#include "namespaces.hh"
#include <iostream>
bool getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp);
void insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, OptPBData&& pbdata, bool tcp);
- void doPruneTo(size_t maxSize);
+ void doPruneTo(time_t now, size_t maxSize);
uint64_t doDump(int file);
uint64_t doWipePacketCache(const DNSName& name, uint16_t qtype = 0xffff, bool subtree = false);
private:
struct Entry
{
- Entry(const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& packet, std::string&& query, bool tcp,
+ Entry(DNSName&& qname, uint16_t qtype, uint16_t qclass, std::string&& packet, std::string&& query, bool tcp,
+ // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
uint32_t qhash, time_t ttd, time_t now, uint32_t tag, vState vstate) :
- d_name(qname), d_packet(std::move(packet)), d_query(std::move(query)), d_ttd(ttd), d_creation(now), d_qhash(qhash), d_tag(tag), d_type(qtype), d_class(qclass), d_vstate(vstate), d_tcp(tcp)
+ d_name(std::move(qname)),
+ d_packet(std::move(packet)),
+ d_query(std::move(query)),
+ d_ttd(ttd),
+ d_creation(now),
+ d_qhash(qhash),
+ d_tag(tag),
+ d_type(qtype),
+ d_class(qclass),
+ d_vstate(vstate),
+ d_tcp(tcp)
{
}
struct MapCombo
{
MapCombo() = default;
+ ~MapCombo() = default;
MapCombo(const MapCombo&) = delete;
+ MapCombo(MapCombo&&) = delete;
MapCombo& operator=(const MapCombo&) = delete;
+ MapCombo& operator=(MapCombo&&) = delete;
+
struct LockedContent
{
packetCache_t d_map;
uint64_t d_contended_count{0};
uint64_t d_acquired_count{0};
void invalidate() {}
+ void preRemoval(const Entry& /* entry */) {}
};
- pdns::stat_t d_entriesCount{0};
LockGuardedTryHolder<MapCombo::LockedContent> lock()
{
return locked;
}
+ [[nodiscard]] auto getEntriesCount() const
+ {
+ return d_entriesCount.load();
+ }
+
+ void incEntriesCount()
+ {
+ ++d_entriesCount;
+ }
+
+ void decEntriesCount()
+ {
+ --d_entriesCount;
+ }
+
private:
LockGuarded<LockedContent> d_content;
+ pdns::stat_t d_entriesCount{0};
};
vector<MapCombo> d_maps;
}
static bool qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass);
- bool checkResponseMatches(MapCombo::LockedContent& shard, std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, OptPBData* pbdata);
+ static bool checkResponseMatches(MapCombo::LockedContent& shard, std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, OptPBData* pbdata);
void setShardSizes(size_t shardSize);
-
-public:
- void preRemoval(MapCombo::LockedContent& /* map */, const Entry& /* entry */)
- {
- }
};
#include "dnsrecords.hh"
#include "arguments.hh"
#include "syncres.hh"
-#include "recursor_cache.hh"
#include "namespaces.hh"
#include "cachecleaner.hh"
#include "rec-taskqueue.hh"
uint16_t MemRecursorCache::s_maxServedStaleExtensions;
+void MemRecursorCache::resetStaticsForTests()
+{
+ s_maxServedStaleExtensions = 0;
+ SyncRes::s_refresh_ttlperc = 0;
+ SyncRes::s_locked_ttlperc = 0;
+ SyncRes::s_minimumTTL = 0;
+}
+
MemRecursorCache::MemRecursorCache(size_t mapsCount) :
d_maps(mapsCount == 0 ? 1 : mapsCount)
{
{
size_t count = 0;
for (const auto& shard : d_maps) {
- count += shard.d_entriesCount;
+ count += shard.getEntriesCount();
}
return count;
}
else if (stateUpdate == vState::NTA) {
state = vState::Insecure;
}
- else if (vStateIsBogus(stateUpdate)) {
- state = stateUpdate;
- }
- else if (stateUpdate == vState::Indeterminate) {
+ else if (vStateIsBogus(stateUpdate) || stateUpdate == vState::Indeterminate) {
state = stateUpdate;
}
- else if (stateUpdate == vState::Insecure) {
+ else if (stateUpdate == vState::Insecure || stateUpdate == vState::Secure) {
if (!vStateIsBogus(*state) && *state != vState::Indeterminate) {
state = stateUpdate;
}
}
- else if (stateUpdate == vState::Secure) {
- if (!vStateIsBogus(*state) && *state != vState::Indeterminate) {
- state = stateUpdate;
- }
+}
+
+template <typename T>
+static void ptrAssign(T* ptr, const T& value)
+{
+ if (ptr != nullptr) {
+ *ptr = value;
}
}
-time_t MemRecursorCache::handleHit(MapCombo::LockedContent& content, MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
+time_t MemRecursorCache::handleHit(time_t now, MapCombo::LockedContent& content, MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
{
// MUTEX SHOULD BE ACQUIRED (as indicated by the reference to the content which is protected by a lock)
time_t ttd = entry->d_ttd;
+ if (ttd <= now) {
+ // Expired, don't bother returning contents. Callers *MUST* check return value of get(), and only look at the entry
+ // if it returned > 0
+ return ttd;
+ }
origTTL = entry->d_orig_ttl;
- if (variable != nullptr && (!entry->d_netmask.empty() || entry->d_rtag)) {
- *variable = true;
+ if (!entry->d_netmask.empty() || entry->d_rtag) {
+ ptrAssign(variable, true);
}
if (res != nullptr) {
if (wasAuth != nullptr) {
*wasAuth = *wasAuth && entry->d_auth;
}
-
- if (fromAuthZone != nullptr) {
- *fromAuthZone = entry->d_authZone;
- }
-
- if (fromAuthIP != nullptr) {
- *fromAuthIP = entry->d_from;
- }
+ ptrAssign(fromAuthZone, entry->d_authZone);
+ ptrAssign(fromAuthIP, entry->d_from);
moveCacheItemToBack<SequencedTag>(content.d_map, entry);
/* we have nothing more specific for you */
break;
}
- auto key = std::make_tuple(qname, qtype, boost::none, best);
+ auto key = std::tuple(qname, qtype, boost::none, best);
auto entry = map.d_map.find(key);
if (entry == map.d_map.end()) {
/* ecsIndex is not up-to-date */
/* we need auth data and the best match is not authoritative */
return map.d_map.end();
}
- else {
- /* this netmask-specific entry has expired */
- moveCacheItemToFront<SequencedTag>(map.d_map, entry);
- // XXX when serving stale, it should be kept, but we don't want a match wth lookupBestMatch()...
- ecsIndex->removeNetmask(best);
- if (ecsIndex->isEmpty()) {
- map.d_ecsIndex.erase(ecsIndex);
- break;
- }
+ /* this netmask-specific entry has expired */
+ moveCacheItemToFront<SequencedTag>(map.d_map, entry);
+ // XXX when serving stale, it should be kept, but we don't want a match wth lookupBestMatch()...
+ ecsIndex->removeNetmask(best);
+ if (ecsIndex->isEmpty()) {
+ map.d_ecsIndex.erase(ecsIndex);
+ break;
}
}
}
/* we have nothing specific, let's see if we have a generic one */
- auto key = std::make_tuple(qname, qtype, boost::none, Netmask());
+ auto key = std::tuple(qname, qtype, boost::none, Netmask());
auto entry = map.d_map.find(key);
if (entry != map.d_map.end()) {
handleServeStaleBookkeeping(now, serveStale, entry);
return map.d_map.end();
}
-MemRecursorCache::Entries MemRecursorCache::getEntries(MapCombo::LockedContent& map, const DNSName& qname, const QType /* qt */, const OptTag& rtag)
+MemRecursorCache::Entries MemRecursorCache::getEntries(MapCombo::LockedContent& map, const DNSName& qname, const QType /* qtype */, const OptTag& rtag)
{
// MUTEX SHOULD BE ACQUIRED
if (!map.d_cachecachevalid || map.d_cachedqname != qname || map.d_cachedrtag != rtag) {
return map.d_cachecache;
}
-bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entry, const QType qt, bool requireAuth, const ComboAddress& who)
+bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entry, const QType qtype, bool requireAuth, const ComboAddress& who)
{
// This code assumes that if a routing tag is present, it matches
// MUTEX SHOULD BE ACQUIRED
- if (requireAuth && !entry->d_auth)
+ if (requireAuth && !entry->d_auth) {
return false;
+ }
- bool match = (entry->d_qtype == qt || qt == QType::ANY || (qt == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA)))
+ bool match = (entry->d_qtype == qtype || qtype == QType::ANY || (qtype == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA)))
&& (entry->d_netmask.empty() || entry->d_netmask.match(who));
return match;
}
if (refresh) {
return -1;
}
- else {
- if (!entry->d_submitted) {
- pushRefreshTask(qname, qtype, entry->d_ttd, entry->d_netmask);
- entry->d_submitted = true;
- }
+ if (!entry->d_submitted) {
+ pushRefreshTask(qname, qtype, entry->d_ttd, entry->d_netmask);
+ entry->d_submitted = true;
}
}
}
return ttl;
}
+
// returns -1 for no hits
-time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
+time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qtype, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
{
- bool requireAuth = flags & RequireAuth;
- bool refresh = flags & Refresh;
- bool serveStale = flags & ServeStale;
+ bool requireAuth = (flags & RequireAuth) != 0;
+ bool refresh = (flags & Refresh) != 0;
+ bool serveStale = (flags & ServeStale) != 0;
boost::optional<vState> cachedState{boost::none};
- uint32_t origTTL;
+ uint32_t origTTL = 0;
if (res != nullptr) {
res->clear();
}
- const uint16_t qtype = qt.getCode();
- if (wasAuth != nullptr) {
- // we might retrieve more than one entry, we need to set that to true
- // so it will be set to false if at least one entry is not auth
- *wasAuth = true;
- }
+
+ // we might retrieve more than one entry, we need to set that to true
+ // so it will be set to false if at least one entry is not auth
+ ptrAssign(wasAuth, true);
auto& shard = getMap(qname);
auto lockedShard = shard.lock();
auto entryA = getEntryUsingECSIndex(*lockedShard, now, qname, QType::A, requireAuth, who, serveStale);
if (entryA != lockedShard->d_map.end()) {
- ret = handleHit(*lockedShard, entryA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+ ret = handleHit(now, *lockedShard, entryA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
}
auto entryAAAA = getEntryUsingECSIndex(*lockedShard, now, qname, QType::AAAA, requireAuth, who, serveStale);
if (entryAAAA != lockedShard->d_map.end()) {
- time_t ttdAAAA = handleHit(*lockedShard, entryAAAA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+ time_t ttdAAAA = handleHit(now, *lockedShard, entryAAAA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
if (ret > 0) {
ret = std::min(ret, ttdAAAA);
}
}
}
- if (state && cachedState) {
- *state = *cachedState;
+ if (cachedState && ret > 0) {
+ ptrAssign(state, *cachedState);
}
return ret > 0 ? (ret - now) : ret;
}
- else {
- auto entry = getEntryUsingECSIndex(*lockedShard, now, qname, qtype, requireAuth, who, serveStale);
- if (entry != lockedShard->d_map.end()) {
- time_t ret = handleHit(*lockedShard, entry, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
- if (state && cachedState) {
- *state = *cachedState;
- }
- return fakeTTD(entry, qname, qtype, ret, now, origTTL, refresh);
+ auto entry = getEntryUsingECSIndex(*lockedShard, now, qname, qtype, requireAuth, who, serveStale);
+ if (entry != lockedShard->d_map.end()) {
+ time_t ret = handleHit(now, *lockedShard, entry, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+ if (cachedState && ret > now) {
+ ptrAssign(state, *cachedState);
}
- return -1;
+ return fakeTTD(entry, qname, qtype, ret, now, origTTL, refresh);
}
+ return -1;
}
if (routingTag) {
- auto entries = getEntries(*lockedShard, qname, qt, routingTag);
+ auto entries = getEntries(*lockedShard, qname, qtype, routingTag);
bool found = false;
- time_t ttd;
+ time_t ttd{};
if (entries.first != entries.second) {
OrderedTagIterator_t firstIndexIterator;
handleServeStaleBookkeeping(now, serveStale, firstIndexIterator);
- ttd = handleHit(*lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+ ttd = handleHit(now, *lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
- if (qt != QType::ANY && qt != QType::ADDR) { // normally if we have a hit, we are done
+ if (qtype != QType::ANY && qtype != QType::ADDR) { // normally if we have a hit, we are done
break;
}
}
if (found) {
- if (state && cachedState) {
- *state = *cachedState;
+ if (cachedState && ttd > now) {
+ ptrAssign(state, *cachedState);
}
return fakeTTD(firstIndexIterator, qname, qtype, ttd, now, origTTL, refresh);
}
- else {
- return -1;
- }
+ return -1;
}
}
// Try (again) without tag
- auto entries = getEntries(*lockedShard, qname, qt, boost::none);
+ auto entries = getEntries(*lockedShard, qname, qtype, boost::none);
if (entries.first != entries.second) {
OrderedTagIterator_t firstIndexIterator;
bool found = false;
- time_t ttd;
+ time_t ttd{};
for (auto i = entries.first; i != entries.second; ++i) {
firstIndexIterator = lockedShard->d_map.project<OrderedTag>(i);
handleServeStaleBookkeeping(now, serveStale, firstIndexIterator);
- ttd = handleHit(*lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+ ttd = handleHit(now, *lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
- if (qt != QType::ANY && qt != QType::ADDR) { // normally if we have a hit, we are done
+ if (qtype != QType::ANY && qtype != QType::ADDR) { // normally if we have a hit, we are done
break;
}
}
if (found) {
- if (state && cachedState) {
- *state = *cachedState;
+ if (cachedState && ttd > now) {
+ ptrAssign(state, *cachedState);
}
return fakeTTD(firstIndexIterator, qname, qtype, ttd, now, origTTL, refresh);
}
return true;
}
-void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qt, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask, const OptTag& routingTag, vState state, boost::optional<ComboAddress> from, bool refresh)
+void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qtype, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask, const OptTag& routingTag, vState state, boost::optional<ComboAddress> from, bool refresh, time_t ttl_time)
{
auto& shard = getMap(qname);
auto lockedShard = shard.lock();
// We only store with a tag if we have an ednsmask and the tag is available
// We only store an ednsmask if we do not have a tag and we do have a mask.
- auto key = std::make_tuple(qname, qt.getCode(), ednsmask ? routingTag : boost::none, (ednsmask && !routingTag) ? *ednsmask : Netmask());
+ auto key = std::tuple(qname, qtype.getCode(), ednsmask ? routingTag : boost::none, (ednsmask && !routingTag) ? *ednsmask : Netmask());
bool isNew = false;
cache_t::iterator stored = lockedShard->d_map.find(key);
if (stored == lockedShard->d_map.end()) {
stored = lockedShard->d_map.insert(CacheEntry(key, auth)).first;
- ++shard.d_entriesCount;
+ shard.incEntriesCount();
isNew = true;
}
if (isNew || stored->d_ttd <= now) {
/* don't bother building an ecsIndex if we don't have any netmask-specific entries */
if (!routingTag && ednsmask && !ednsmask->empty()) {
- auto ecsIndexKey = std::make_tuple(qname, qt.getCode());
+ auto ecsIndexKey = std::tuple(qname, qtype.getCode());
auto ecsIndex = lockedShard->d_ecsIndex.find(ecsIndexKey);
if (ecsIndex == lockedShard->d_ecsIndex.end()) {
- ecsIndex = lockedShard->d_ecsIndex.insert(ECSIndexEntry(qname, qt.getCode())).first;
+ ecsIndex = lockedShard->d_ecsIndex.insert(ECSIndexEntry(qname, qtype.getCode())).first;
}
ecsIndex->addMask(*ednsmask);
}
}
time_t maxTTD = std::numeric_limits<time_t>::max();
- CacheEntry ce = *stored; // this is a COPY
- ce.d_qtype = qt.getCode();
+ CacheEntry cacheEntry = *stored; // this is a COPY
+ cacheEntry.d_qtype = qtype.getCode();
- if (!isNew && !ce.shouldReplace(now, auth, state, refresh)) {
+ if (!isNew && !cacheEntry.shouldReplace(now, auth, state, refresh)) {
return;
}
- ce.d_state = state;
+ cacheEntry.d_state = state;
// refuse any attempt to *raise* the TTL of auth NS records, as it would make it possible
// for an auth to keep a "ghost" zone alive forever, even after the delegation is gone from
// the parent
// BUT make sure that we CAN refresh the root
- if (ce.d_auth && auth && qt == QType::NS && !isNew && !qname.isRoot()) {
- maxTTD = ce.d_ttd;
+ if (cacheEntry.d_auth && auth && qtype == QType::NS && !isNew && !qname.isRoot()) {
+ maxTTD = cacheEntry.d_ttd;
}
if (auth) {
- ce.d_auth = true;
+ cacheEntry.d_auth = true;
}
- ce.d_signatures = signatures;
- ce.d_authorityRecs = authorityRecs;
- ce.d_records.clear();
- ce.d_records.reserve(content.size());
- ce.d_authZone = authZone;
+ cacheEntry.d_signatures = signatures;
+ cacheEntry.d_authorityRecs = authorityRecs;
+ cacheEntry.d_records.clear();
+ cacheEntry.d_records.reserve(content.size());
+ cacheEntry.d_authZone = authZone;
if (from) {
- ce.d_from = *from;
+ cacheEntry.d_from = *from;
}
else {
- ce.d_from = ComboAddress();
+ cacheEntry.d_from = ComboAddress();
}
- for (const auto& i : content) {
+ for (const auto& record : content) {
/* Yes, we have altered the d_ttl value by adding time(nullptr) to it
prior to calling this function, so the TTL actually holds a TTD. */
- ce.d_ttd = min(maxTTD, static_cast<time_t>(i.d_ttl)); // XXX this does weird things if TTLs differ in the set
- ce.d_orig_ttl = ce.d_ttd - now;
- ce.d_records.push_back(i.getContent());
+ cacheEntry.d_ttd = min(maxTTD, static_cast<time_t>(record.d_ttl)); // XXX this does weird things if TTLs differ in the set
+
+ // coverity[store_truncates_time_t]
+ cacheEntry.d_orig_ttl = cacheEntry.d_ttd - ttl_time;
+ // Even though we record the time the ttd was computed, there still seems to be a case where the computed
+ // d_orig_ttl can wrap.
+ // So santize the computed ce.d_orig_ttl to be on the safe side
+ if (cacheEntry.d_orig_ttl < SyncRes::s_minimumTTL || cacheEntry.d_orig_ttl > SyncRes::s_maxcachettl) {
+ cacheEntry.d_orig_ttl = SyncRes::s_minimumTTL;
+ }
+ cacheEntry.d_records.push_back(record.getContent());
}
if (!isNew) {
moveCacheItemToBack<SequencedTag>(lockedShard->d_map, stored);
}
- ce.d_submitted = false;
- ce.d_servedStale = 0;
- lockedShard->d_map.replace(stored, ce);
+ cacheEntry.d_submitted = false;
+ cacheEntry.d_servedStale = 0;
+ lockedShard->d_map.replace(stored, cacheEntry);
}
size_t MemRecursorCache::doWipeCache(const DNSName& name, bool sub, const QType qtype)
lockedShard->d_cachecachevalid = false;
auto& idx = lockedShard->d_map.get<OrderedTag>();
auto range = idx.equal_range(name);
- auto i = range.first;
- while (i != range.second) {
- if (i->d_qtype == qtype || qtype == 0xffff) {
- i = idx.erase(i);
+ auto iter = range.first;
+ while (iter != range.second) {
+ if (iter->d_qtype == qtype || qtype == 0xffff) {
+ iter = idx.erase(iter);
count++;
- --shard.d_entriesCount;
+ shard.decEntriesCount();
}
else {
- ++i;
+ ++iter;
}
}
}
}
else {
- for (auto& mc : d_maps) {
- auto map = mc.lock();
+ for (auto& content : d_maps) {
+ auto map = content.lock();
map->d_cachecachevalid = false;
auto& idx = map->d_map.get<OrderedTag>();
for (auto i = idx.lower_bound(name); i != idx.end();) {
- if (!i->d_qname.isPartOf(name))
+ if (!i->d_qname.isPartOf(name)) {
break;
+ }
if (i->d_qtype == qtype || qtype == 0xffff) {
count++;
i = idx.erase(i);
- --mc.d_entriesCount;
+ content.decEntriesCount();
}
else {
++i;
}
auto& ecsIdx = map->d_ecsIndex.get<OrderedTag>();
for (auto i = ecsIdx.lower_bound(name); i != ecsIdx.end();) {
- if (!i->d_qname.isPartOf(name))
+ if (!i->d_qname.isPartOf(name)) {
break;
+ }
if (i->d_qtype == qtype || qtype == 0xffff) {
i = ecsIdx.erase(i);
}
return false;
}
- CacheEntry ce = *iter;
- if (ce.d_ttd < now) {
+ CacheEntry cacheEntry = *iter;
+ if (cacheEntry.d_ttd < now) {
return false; // would be dead anyhow
}
- uint32_t maxTTL = static_cast<uint32_t>(ce.d_ttd - now);
+ // coverity[store_truncates_time_t]
+ auto maxTTL = static_cast<uint32_t>(cacheEntry.d_ttd - now);
if (maxTTL > newTTL) {
lockedShard->d_cachecachevalid = false;
time_t newTTD = now + newTTL;
- if (ce.d_ttd > newTTD) {
- ce.d_ttd = newTTD;
- lockedShard->d_map.replace(iter, ce);
+ if (cacheEntry.d_ttd > newTTD) {
+ cacheEntry.d_ttd = newTTD;
+ lockedShard->d_map.replace(iter, cacheEntry);
}
return true;
}
return false;
}
-bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName& qname, const QType qt, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD)
+bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName& qname, const QType qtype, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD)
{
- uint16_t qtype = qt.getCode();
if (qtype == QType::ANY) {
throw std::runtime_error("Trying to update the DNSSEC validation status of all (via ANY) records for " + qname.toLogString());
}
throw std::runtime_error("Trying to update the DNSSEC validation status of several (via ADDR) records for " + qname.toLogString());
}
- auto& mc = getMap(qname);
- auto map = mc.lock();
+ auto& content = getMap(qname);
+ auto map = content.lock();
bool updated = false;
if (!map->d_ecsIndex.empty() && !routingTag) {
return true;
}
- auto entries = getEntries(*map, qname, qt, routingTag);
+ auto entries = getEntries(*map, qname, qtype, routingTag);
for (auto i = entries.first; i != entries.second; ++i) {
auto firstIndexIterator = map->d_map.project<OrderedTag>(i);
return updated;
}
-uint64_t MemRecursorCache::doDump(int fd, size_t maxCacheEntries)
+uint64_t MemRecursorCache::doDump(int fileDesc, size_t maxCacheEntries)
{
- int newfd = dup(fd);
+ int newfd = dup(fileDesc);
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) { // dup probably failed
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) { // dup probably failed
close(newfd);
return 0;
}
- fprintf(fp.get(), "; main record cache dump follows\n;\n");
+ fprintf(filePtr.get(), "; main record cache dump follows\n;\n");
uint64_t count = 0;
size_t shardNumber = 0;
size_t min = std::numeric_limits<size_t>::max();
for (auto& shard : d_maps) {
auto lockedShard = shard.lock();
const auto shardSize = lockedShard->d_map.size();
- fprintf(fp.get(), "; record cache shard %zu; size %zu\n", shardNumber, shardSize);
+ fprintf(filePtr.get(), "; record cache shard %zu; size %zu\n", shardNumber, shardSize);
min = std::min(min, shardSize);
max = std::max(max, shardSize);
shardNumber++;
const auto& sidx = lockedShard->d_map.get<SequencedTag>();
time_t now = time(nullptr);
- for (const auto& i : sidx) {
- for (const auto& j : i.d_records) {
+ for (const auto& recordSet : sidx) {
+ for (const auto& record : recordSet.d_records) {
count++;
try {
- fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s nm=%s rtag=%s ss=%hd\n", i.d_qname.toString().c_str(), i.d_orig_ttl, static_cast<int64_t>(i.d_ttd - now), i.d_qtype.toString().c_str(), j->getZoneRepresentation().c_str(), vStateToString(i.d_state).c_str(), i.d_auth, i.d_authZone.toLogString().c_str(), i.d_from.toString().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str(), !i.d_rtag ? "" : i.d_rtag.get().c_str(), i.d_servedStale);
+ fprintf(filePtr.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s nm=%s rtag=%s ss=%hd\n", recordSet.d_qname.toString().c_str(), recordSet.d_orig_ttl, static_cast<int64_t>(recordSet.d_ttd - now), recordSet.d_qtype.toString().c_str(), record->getZoneRepresentation().c_str(), vStateToString(recordSet.d_state).c_str(), static_cast<int>(recordSet.d_auth), recordSet.d_authZone.toLogString().c_str(), recordSet.d_from.toString().c_str(), recordSet.d_netmask.empty() ? "" : recordSet.d_netmask.toString().c_str(), !recordSet.d_rtag ? "" : recordSet.d_rtag.get().c_str(), recordSet.d_servedStale);
}
catch (...) {
- fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
+ fprintf(filePtr.get(), "; error printing '%s'\n", recordSet.d_qname.empty() ? "EMPTY" : recordSet.d_qname.toString().c_str());
}
}
- for (const auto& sig : i.d_signatures) {
+ for (const auto& sig : recordSet.d_signatures) {
count++;
try {
- fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN RRSIG %s ; %s\n", i.d_qname.toString().c_str(), i.d_orig_ttl, static_cast<int64_t>(i.d_ttd - now), sig->getZoneRepresentation().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
+ fprintf(filePtr.get(), "%s %" PRIu32 " %" PRId64 " IN RRSIG %s ; %s\n", recordSet.d_qname.toString().c_str(), recordSet.d_orig_ttl, static_cast<int64_t>(recordSet.d_ttd - now), sig->getZoneRepresentation().c_str(), recordSet.d_netmask.empty() ? "" : recordSet.d_netmask.toString().c_str());
}
catch (...) {
- fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
+ fprintf(filePtr.get(), "; error printing '%s'\n", recordSet.d_qname.empty() ? "EMPTY" : recordSet.d_qname.toString().c_str());
}
}
}
}
- fprintf(fp.get(), "; main record cache size: %zu/%zu shards: %zu min/max shard size: %zu/%zu\n", size(), maxCacheEntries, d_maps.size(), min, max);
+ fprintf(filePtr.get(), "; main record cache size: %zu/%zu shards: %zu min/max shard size: %zu/%zu\n", size(), maxCacheEntries, d_maps.size(), min, max);
return count;
}
-void MemRecursorCache::doPrune(size_t keep)
+void MemRecursorCache::doPrune(time_t now, size_t keep)
{
size_t cacheSize = size();
- pruneMutexCollectionsVector<SequencedTag>(*this, d_maps, keep, cacheSize);
+ pruneMutexCollectionsVector<SequencedTag>(now, d_maps, keep, cacheSize);
}
namespace boost
// The time a stale cache entry is extended
static constexpr uint32_t s_serveStaleExtensionPeriod = 30;
- size_t size() const;
- size_t bytes();
- pair<uint64_t, uint64_t> stats();
- size_t ecsIndexSize();
+ [[nodiscard]] size_t size() const;
+ [[nodiscard]] size_t bytes();
+ [[nodiscard]] pair<uint64_t, uint64_t> stats();
+ [[nodiscard]] size_t ecsIndexSize();
- typedef boost::optional<std::string> OptTag;
+ using OptTag = boost::optional<std::string>;
- typedef uint8_t Flags;
+ using Flags = uint8_t;
static constexpr Flags None = 0;
static constexpr Flags RequireAuth = 1 << 0;
static constexpr Flags Refresh = 1 << 1;
static constexpr Flags ServeStale = 1 << 2;
- time_t get(time_t, const DNSName& qname, const QType qt, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag = boost::none, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures = nullptr, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs = nullptr, bool* variable = nullptr, vState* state = nullptr, bool* wasAuth = nullptr, DNSName* fromAuthZone = nullptr, ComboAddress* fromAuthIP = nullptr);
+ [[nodiscard]] time_t get(time_t, const DNSName& qname, QType qtype, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag = boost::none, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures = nullptr, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs = nullptr, bool* variable = nullptr, vState* state = nullptr, bool* wasAuth = nullptr, DNSName* fromAuthZone = nullptr, ComboAddress* fromAuthIP = nullptr);
- void replace(time_t, const DNSName& qname, const QType qt, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask = boost::none, const OptTag& routingTag = boost::none, vState state = vState::Indeterminate, boost::optional<ComboAddress> from = boost::none, bool refresh = false);
+ void replace(time_t, const DNSName& qname, QType qtype, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask = boost::none, const OptTag& routingTag = boost::none, vState state = vState::Indeterminate, boost::optional<ComboAddress> from = boost::none, bool refresh = false, time_t ttl_time = time(nullptr));
- void doPrune(size_t keep);
- uint64_t doDump(int fd, size_t maxCacheEntries);
+ void doPrune(time_t now, size_t keep);
+ uint64_t doDump(int fileDesc, size_t maxCacheEntries);
size_t doWipeCache(const DNSName& name, bool sub, QType qtype = 0xffff);
bool doAgeCache(time_t now, const DNSName& name, QType qtype, uint32_t newTTL);
- bool updateValidationStatus(time_t now, const DNSName& qname, QType qt, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
+ bool updateValidationStatus(time_t now, const DNSName& qname, QType qtype, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
- pdns::stat_t cacheHits{0}, cacheMisses{0};
+ static void resetStaticsForTests();
+
+ [[nodiscard]] auto getCacheHits() const
+ {
+ return cacheHits.load();
+ }
+ [[nodiscard]] auto getCacheMisses() const
+ {
+ return cacheMisses.load();
+ }
+
+ void incCacheHits()
+ {
+ ++cacheHits;
+ }
+ void incCacheMisses()
+ {
+ ++cacheMisses;
+ }
private:
+ pdns::stat_t cacheHits{0}, cacheMisses{0};
+
struct CacheEntry
{
CacheEntry(const std::tuple<DNSName, QType, OptTag, Netmask>& key, bool auth) :
- d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_state(vState::Indeterminate), d_ttd(0), d_orig_ttl{0}, d_servedStale(0), d_qtype(std::get<1>(key)), d_auth(auth), d_submitted(false)
+ d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_qtype(std::get<1>(key)), d_auth(auth)
{
}
- typedef vector<std::shared_ptr<const DNSRecordContent>> records_t;
+ using records_t = vector<std::shared_ptr<const DNSRecordContent>>;
bool isStale(time_t now) const
{
if (s_maxServedStaleExtensions > 0) {
return d_ttd + static_cast<time_t>(s_maxServedStaleExtensions) * std::min(s_serveStaleExtensionPeriod, d_orig_ttl) < now;
}
- else {
- return d_ttd < now;
- }
+ return d_ttd < now;
}
bool isEntryUsable(time_t now, bool serveStale) const
ComboAddress d_from;
Netmask d_netmask;
OptTag d_rtag;
- mutable vState d_state;
- mutable time_t d_ttd;
- uint32_t d_orig_ttl;
- mutable uint16_t d_servedStale;
+ mutable vState d_state{vState::Indeterminate};
+ mutable time_t d_ttd{0};
+ uint32_t d_orig_ttl{0};
+ mutable uint16_t d_servedStale{0};
QType d_qtype;
bool d_auth;
- mutable bool d_submitted; // whether this entry has been queued for refetch
+ mutable bool d_submitted{false}; // whether this entry has been queued for refetch
};
/* The ECS Index (d_ecsIndex) keeps track of whether there is any ECS-specific
class ECSIndexEntry
{
public:
- ECSIndexEntry(const DNSName& qname, QType qtype) :
- d_nmt(), d_qname(qname), d_qtype(qtype)
+ ECSIndexEntry(DNSName qname, QType qtype) :
+ d_qname(std::move(qname)), d_qtype(qtype)
{
}
- Netmask lookupBestMatch(const ComboAddress& addr) const
+ [[nodiscard]] Netmask lookupBestMatch(const ComboAddress& addr) const
{
- const auto best = d_nmt.lookup(addr);
+ const auto* best = d_nmt.lookup(addr);
if (best != nullptr) {
return best->first;
}
- return Netmask();
+ return {};
}
- void addMask(const Netmask& nm) const
+ void addMask(const Netmask& netmask) const
{
- d_nmt.insert(nm).second = true;
+ d_nmt.insert(netmask).second = true;
}
- void removeNetmask(const Netmask& nm) const
+ void removeNetmask(const Netmask& netmask) const
{
- d_nmt.erase(nm);
+ d_nmt.erase(netmask);
}
- bool isEmpty() const
+ [[nodiscard]] bool isEmpty() const
{
return d_nmt.empty();
}
{
};
- typedef multi_index_container<
+ using cache_t = multi_index_container<
CacheEntry,
indexed_by<
ordered_unique<tag<OrderedTag>,
member<CacheEntry, QType, &CacheEntry::d_qtype>,
member<CacheEntry, OptTag, &CacheEntry::d_rtag>,
member<CacheEntry, Netmask, &CacheEntry::d_netmask>>,
- composite_key_compare<CanonDNSNameCompare, std::less<QType>, std::less<OptTag>, std::less<Netmask>>>,
+ composite_key_compare<CanonDNSNameCompare, std::less<>, std::less<>, std::less<>>>,
sequenced<tag<SequencedTag>>,
hashed_non_unique<tag<NameAndRTagOnlyHashedTag>,
composite_key<
CacheEntry,
member<CacheEntry, DNSName, &CacheEntry::d_qname>,
- member<CacheEntry, OptTag, &CacheEntry::d_rtag>>>>>
- cache_t;
+ member<CacheEntry, OptTag, &CacheEntry::d_rtag>>>>>;
- typedef MemRecursorCache::cache_t::index<MemRecursorCache::OrderedTag>::type::iterator OrderedTagIterator_t;
- typedef MemRecursorCache::cache_t::index<MemRecursorCache::NameAndRTagOnlyHashedTag>::type::iterator NameAndRTagOnlyHashedTagIterator_t;
+ using OrderedTagIterator_t = MemRecursorCache::cache_t::index<MemRecursorCache::OrderedTag>::type::iterator;
+ using NameAndRTagOnlyHashedTagIterator_t = MemRecursorCache::cache_t::index<MemRecursorCache::NameAndRTagOnlyHashedTag>::type::iterator;
- typedef multi_index_container<
+ using ecsIndex_t = multi_index_container<
ECSIndexEntry,
indexed_by<
hashed_unique<tag<HashedTag>,
ECSIndexEntry,
member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>,
- composite_key_compare<CanonDNSNameCompare, std::less<QType>>>>>
- ecsIndex_t;
+ composite_key_compare<CanonDNSNameCompare, std::less<>>>>>;
- typedef std::pair<NameAndRTagOnlyHashedTagIterator_t, NameAndRTagOnlyHashedTagIterator_t> Entries;
+ using Entries = std::pair<NameAndRTagOnlyHashedTagIterator_t, NameAndRTagOnlyHashedTagIterator_t>;
struct MapCombo
{
- MapCombo() {}
+ MapCombo() = default;
+ ~MapCombo() = default;
MapCombo(const MapCombo&) = delete;
MapCombo& operator=(const MapCombo&) = delete;
+ MapCombo(MapCombo&&) = delete;
+ MapCombo& operator=(MapCombo&&) = delete;
+
struct LockedContent
{
cache_t d_map;
{
d_cachecachevalid = false;
}
- };
- pdns::stat_t d_entriesCount{0};
+ void preRemoval(const CacheEntry& entry)
+ {
+ if (entry.d_netmask.empty()) {
+ return;
+ }
+
+ auto key = std::tie(entry.d_qname, entry.d_qtype);
+ auto ecsIndexEntry = d_ecsIndex.find(key);
+ if (ecsIndexEntry != d_ecsIndex.end()) {
+ ecsIndexEntry->removeNetmask(entry.d_netmask);
+ if (ecsIndexEntry->isEmpty()) {
+ d_ecsIndex.erase(ecsIndexEntry);
+ }
+ }
+ }
+ };
LockGuardedTryHolder<LockedContent> lock()
{
return locked;
}
+ [[nodiscard]] auto getEntriesCount() const
+ {
+ return d_entriesCount.load();
+ }
+
+ void incEntriesCount()
+ {
+ ++d_entriesCount;
+ }
+
+ void decEntriesCount()
+ {
+ --d_entriesCount;
+ }
+
+ void clearEntriesCount()
+ {
+ d_entriesCount = 0;
+ }
+
private:
LockGuarded<LockedContent> d_content;
+ pdns::stat_t d_entriesCount{0};
};
vector<MapCombo> d_maps;
static time_t fakeTTD(OrderedTagIterator_t& entry, const DNSName& qname, QType qtype, time_t ret, time_t now, uint32_t origTTL, bool refresh);
- bool entryMatches(OrderedTagIterator_t& entry, QType qt, bool requireAuth, const ComboAddress& who);
- Entries getEntries(MapCombo::LockedContent& content, const DNSName& qname, const QType qt, const OptTag& rtag);
- cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& content, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale);
-
- time_t handleHit(MapCombo::LockedContent& content, OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* authZone, ComboAddress* fromAuthIP);
- void updateStaleEntry(time_t now, OrderedTagIterator_t& entry);
- void handleServeStaleBookkeeping(time_t, bool, OrderedTagIterator_t&);
-
-public:
- void preRemoval(MapCombo::LockedContent& map, const CacheEntry& entry)
- {
- if (entry.d_netmask.empty()) {
- return;
- }
+ static bool entryMatches(OrderedTagIterator_t& entry, QType qtype, bool requireAuth, const ComboAddress& who);
+ static Entries getEntries(MapCombo::LockedContent& map, const DNSName& qname, QType qtype, const OptTag& rtag);
+ static cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale);
- auto key = std::tie(entry.d_qname, entry.d_qtype);
- auto ecsIndexEntry = map.d_ecsIndex.find(key);
- if (ecsIndexEntry != map.d_ecsIndex.end()) {
- ecsIndexEntry->removeNetmask(entry.d_netmask);
- if (ecsIndexEntry->isEmpty()) {
- map.d_ecsIndex.erase(ecsIndexEntry);
- }
- }
- }
+ static time_t handleHit(time_t now, MapCombo::LockedContent& content, OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* authZone, ComboAddress* fromAuthIP);
+ static void updateStaleEntry(time_t now, OrderedTagIterator_t& entry);
+ static void handleServeStaleBookkeeping(time_t, bool, OrderedTagIterator_t&);
};
namespace boost
arr.d_type = QType::A;
aaaarr.d_type = QType::AAAA;
nsrr.d_type = QType::NS;
+ // coverity[store_truncates_time_t]
arr.d_ttl = aaaarr.d_ttl = nsrr.d_ttl = now + 3600000;
string templ = "a.root-servers.net.";
dr.d_place = DNSResourceRecord::ANSWER;
dr.d_ttl = 86400;
dr.d_type = QType::SOA;
- dr.setContent(DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"));
+ dr.setContent(DNSRecordContent::make(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"));
SyncRes::AuthDomain ad;
ad.d_rdForward = false;
static void addToDomainMap(SyncRes::domainmap_t& newMap,
SyncRes::AuthDomain ad,
- DNSName& name,
+ const DNSName& name,
Logr::log_t log,
const bool partial = false,
const bool reverse = false)
auto recType = address.isIPv6() ? QType::AAAA : QType::A;
dr.d_type = recType;
dr.d_ttl = 86400;
- dr.setContent(DNSRecordContent::mastermake(recType, QClass::IN, address.toStringNoInterface()));
+ dr.setContent(DNSRecordContent::make(recType, QClass::IN, address.toStringNoInterface()));
entry->second.d_records.insert(dr);
}
// Add a PTR entry for the primary name for reverse lookups.
dr.d_type = QType::PTR;
- dr.setContent(DNSRecordContent::mastermake(QType::PTR, 1, DNSName(canonicalHostname).toString()));
+ dr.setContent(DNSRecordContent::make(QType::PTR, 1, DNSName(canonicalHostname).toString()));
ad.d_records.insert(dr);
- addToDomainMap(newMap, ad, dr.d_name, log, false, true);
+ addToDomainMap(newMap, std::move(ad), dr.d_name, log, false, true);
}
void makePartialIPZone(SyncRes::domainmap_t& newMap,
SyncRes::AuthDomain ad = makeSOAAndNSNodes(dr, DNSName("localhost."));
- addToDomainMap(newMap, ad, dr.d_name, log, true, true);
+ addToDomainMap(newMap, std::move(ad), dr.d_name, log, true, true);
}
void addForwardAndReverseLookupEntries(SyncRes::domainmap_t& newMap,
#include "config.h"
#endif
+#include <sys/stat.h>
+
#include "reczones-helpers.hh"
#include "arguments.hh"
#include "dnsrecords.hh"
#include "logger.hh"
#include "syncres.hh"
#include "zoneparser-tng.hh"
+#include "settings/cxxsettings.hh"
extern int g_argc;
extern char** g_argv;
vector<DNSRecord> nsvec;
bool ret = true;
- if (hintfile == "no") {
+ if (hintfile == "no" || hintfile == "no-refresh") {
auto log = g_slog->withName("config");
- SLOG(g_log << Logger::Debug << "Priming root disabled by hint-file=no" << endl,
- log->info(Logr::Debug, "Priming root disabled by hint-file=no"));
+ SLOG(g_log << Logger::Debug << "Priming root disabled by hint-file setting" << endl,
+ log->info(Logr::Debug, "Priming root disabled by hint-file setting"));
return ret;
}
return ret;
}
-static void convertServersForAD(const std::string& zone, const std::string& input, SyncRes::AuthDomain& ad, const char* sepa, Logr::log_t log, bool verbose = true)
+static void convertServersForAD(const std::string& zone, const std::string& input, SyncRes::AuthDomain& authDomain, const char* sepa, Logr::log_t log, bool verbose = true)
{
vector<string> servers;
stringtok(servers, input, sepa);
- ad.d_servers.clear();
+ authDomain.d_servers.clear();
vector<string> addresses;
- for (auto server = servers.begin(); server != servers.end(); ++server) {
- ComboAddress addr = parseIPAndPort(*server, 53);
- ad.d_servers.push_back(addr);
+ for (auto& server : servers) {
+ ComboAddress addr = parseIPAndPort(server, 53);
+ authDomain.d_servers.push_back(addr);
if (verbose) {
addresses.push_back(addr.toStringWithPort());
}
if (verbose) {
if (!g_slogStructured) {
g_log << Logger::Info << "Redirecting queries for zone '" << zone << "' ";
- if (ad.d_rdForward) {
+ if (authDomain.d_rdForward) {
g_log << "with recursion ";
}
g_log << "to: ";
bool first = true;
- for (const auto& a : addresses) {
+ for (const auto& address : addresses) {
if (!first) {
g_log << ", ";
}
else {
first = false;
}
- g_log << a;
+ g_log << address;
}
g_log << endl;
}
else {
- log->info(Logr::Info, "Redirecting queries", "zone", Logging::Loggable(zone), "recursion", Logging::Loggable(ad.d_rdForward), "addresses", Logging::IterLoggable(addresses.begin(), addresses.end()));
+ log->info(Logr::Info, "Redirecting queries", "zone", Logging::Loggable(zone), "recursion", Logging::Loggable(authDomain.d_rdForward), "addresses", Logging::IterLoggable(addresses.begin(), addresses.end()));
}
}
}
static void* pleaseUseNewSDomainsMap(std::shared_ptr<SyncRes::domainmap_t> newmap)
{
- SyncRes::setDomainMap(newmap);
- return 0;
+ SyncRes::setDomainMap(std::move(newmap));
+ return nullptr;
}
-string reloadZoneConfiguration()
+string reloadZoneConfiguration(bool yaml)
{
std::shared_ptr<SyncRes::domainmap_t> original = SyncRes::getDomainMap();
auto log = g_slog->withName("config");
+ string configname = ::arg()["config-dir"] + "/recursor";
+ if (!::arg()["config-name"].empty()) {
+ configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
+ }
+ cleanSlashes(configname);
+
try {
SLOG(g_log << Logger::Warning << "Reloading zones, purging data from cache" << endl,
log->info(Logr::Notice, "Reloading zones, purging data from cache"));
- string configname = ::arg()["config-dir"] + "/recursor.conf";
- if (::arg()["config-name"] != "") {
- configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
- }
- cleanSlashes(configname);
-
- if (!::arg().preParseFile(configname.c_str(), "forward-zones"))
- throw runtime_error("Unable to re-parse configuration file '" + configname + "'");
- ::arg().preParseFile(configname.c_str(), "forward-zones-file");
- ::arg().preParseFile(configname.c_str(), "forward-zones-recurse");
- ::arg().preParseFile(configname.c_str(), "auth-zones");
- ::arg().preParseFile(configname.c_str(), "allow-notify-for");
- ::arg().preParseFile(configname.c_str(), "allow-notify-for-file");
- ::arg().preParseFile(configname.c_str(), "export-etc-hosts", "off");
- ::arg().preParseFile(configname.c_str(), "serve-rfc1918");
- ::arg().preParseFile(configname.c_str(), "include-dir");
- ::arg().preParse(g_argc, g_argv, "include-dir");
-
- // then process includes
- std::vector<std::string> extraConfigs;
- ::arg().gatherIncludes(extraConfigs);
-
- for (const std::string& fn : extraConfigs) {
- if (!::arg().preParseFile(fn.c_str(), "forward-zones", ::arg()["forward-zones"]))
- throw runtime_error("Unable to re-parse configuration file include '" + fn + "'");
- ::arg().preParseFile(fn.c_str(), "forward-zones-file", ::arg()["forward-zones-file"]);
- ::arg().preParseFile(fn.c_str(), "forward-zones-recurse", ::arg()["forward-zones-recurse"]);
- ::arg().preParseFile(fn.c_str(), "auth-zones", ::arg()["auth-zones"]);
- ::arg().preParseFile(fn.c_str(), "allow-notify-for", ::arg()["allow-notify-for"]);
- ::arg().preParseFile(fn.c_str(), "allow-notify-for-file", ::arg()["allow-notify-for-file"]);
- ::arg().preParseFile(fn.c_str(), "export-etc-hosts", ::arg()["export-etc-hosts"]);
- ::arg().preParseFile(fn.c_str(), "serve-rfc1918", ::arg()["serve-rfc1918"]);
+ if (yaml) {
+ configname += ".yml";
+ string msg;
+ pdns::rust::settings::rec::Recursorsettings settings;
+ // XXX Does ::arg()["include-dir"] have the right value, i.e. potentially overriden by command line?
+ auto yamlstatus = pdns::settings::rec::readYamlSettings(configname, ::arg()["include-dir"], settings, msg, log);
+
+ switch (yamlstatus) {
+ case pdns::settings::rec::YamlSettingsStatus::CannotOpen:
+ throw runtime_error("Unable to open '" + configname + "': " + msg);
+ break;
+ case pdns::settings::rec::YamlSettingsStatus::PresentButFailed:
+ throw runtime_error("Error processing '" + configname + "': " + msg);
+ break;
+ case pdns::settings::rec::YamlSettingsStatus::OK:
+ // Does *not* set include-dir
+ pdns::settings::rec::setArgsForZoneRelatedSettings(settings);
+ break;
+ }
}
+ else {
+ configname += ".conf";
+ if (!::arg().preParseFile(configname, "forward-zones")) {
+ throw runtime_error("Unable to re-parse configuration file '" + configname + "'");
+ }
+ ::arg().preParseFile(configname, "forward-zones-file");
+ ::arg().preParseFile(configname, "forward-zones-recurse");
+ ::arg().preParseFile(configname, "auth-zones");
+ ::arg().preParseFile(configname, "allow-notify-for");
+ ::arg().preParseFile(configname, "allow-notify-for-file");
+ ::arg().preParseFile(configname, "export-etc-hosts", "off");
+ ::arg().preParseFile(configname, "serve-rfc1918");
+ ::arg().preParseFile(configname, "include-dir");
+ ::arg().preParse(g_argc, g_argv, "include-dir");
+
+ // then process includes
+ std::vector<std::string> extraConfigs;
+ ::arg().gatherIncludes(::arg()["include-dir"], ".conf", extraConfigs);
+
+ for (const std::string& filename : extraConfigs) {
+ if (!::arg().preParseFile(filename, "forward-zones", ::arg()["forward-zones"])) {
+ throw runtime_error("Unable to re-parse configuration file include '" + filename + "'");
+ }
+ ::arg().preParseFile(filename, "forward-zones-file", ::arg()["forward-zones-file"]);
+ ::arg().preParseFile(filename, "forward-zones-recurse", ::arg()["forward-zones-recurse"]);
+ ::arg().preParseFile(filename, "auth-zones", ::arg()["auth-zones"]);
+ ::arg().preParseFile(filename, "allow-notify-for", ::arg()["allow-notify-for"]);
+ ::arg().preParseFile(filename, "allow-notify-for-file", ::arg()["allow-notify-for-file"]);
+ ::arg().preParseFile(filename, "export-etc-hosts", ::arg()["export-etc-hosts"]);
+ ::arg().preParseFile(filename, "serve-rfc1918", ::arg()["serve-rfc1918"]);
+ }
+ }
+ // Process command line args potentially overriding what we read from config files
::arg().preParse(g_argc, g_argv, "forward-zones");
::arg().preParse(g_argc, g_argv, "forward-zones-file");
::arg().preParse(g_argc, g_argv, "forward-zones-recurse");
::arg().preParse(g_argc, g_argv, "export-etc-hosts");
::arg().preParse(g_argc, g_argv, "serve-rfc1918");
- auto [newDomainMap, newNotifySet] = parseZoneConfiguration();
+ auto [newDomainMap, newNotifySet] = parseZoneConfiguration(yaml);
// purge both original and new names
std::set<DNSName> oldAndNewDomains;
- for (const auto& i : *newDomainMap) {
- oldAndNewDomains.insert(i.first);
+ for (const auto& entry : *newDomainMap) {
+ oldAndNewDomains.insert(entry.first);
}
if (original) {
- for (const auto& i : *original) {
- oldAndNewDomains.insert(i.first);
+ for (const auto& entry : *original) {
+ oldAndNewDomains.insert(entry.first);
}
}
// these explicitly-named captures should not be necessary, as lambda
// capture of tuple-like structured bindings is permitted, but some
// compilers still don't allow it
- broadcastFunction([dm = newDomainMap] { return pleaseUseNewSDomainsMap(dm); });
- broadcastFunction([ns = newNotifySet] { return pleaseSupplantAllowNotifyFor(ns); });
+ broadcastFunction([dmap = newDomainMap] { return pleaseUseNewSDomainsMap(dmap); });
+ broadcastFunction([nsset = newNotifySet] { return pleaseSupplantAllowNotifyFor(nsset); });
// Wipe the caches *after* the new auth domain info has been set
// up, as a query during setting up might fill the caches
// again. Old code did the clear before, exposing a race.
- for (const auto& i : oldAndNewDomains) {
- wipeCaches(i, true, 0xffff);
+ for (const auto& entry : oldAndNewDomains) {
+ wipeCaches(entry, true, 0xffff);
}
return "ok\n";
}
return "reloading failed, see log\n";
}
-std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration()
+static void readAuthZoneData(SyncRes::AuthDomain& authDomain, const pair<string, string>& headers, Logr::log_t log)
{
- auto log = g_slog->withName("config");
-
- TXTRecordContent::report();
- OPTRecordContent::report();
+ SLOG(g_log << Logger::Notice << "Parsing authoritative data for zone '" << headers.first << "' from file '" << headers.second << "'" << endl,
+ log->info(Logr::Notice, "Parsing authoritative data from file", "zone", Logging::Loggable(headers.first), "file", Logging::Loggable(headers.second)));
+ ZoneParserTNG zpt(headers.second, DNSName(headers.first));
+ zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+ zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
+ DNSResourceRecord resourceRecord;
+ DNSRecord dnsrecord;
+ while (zpt.get(resourceRecord)) {
+ try {
+ dnsrecord = DNSRecord(resourceRecord);
+ dnsrecord.d_place = DNSResourceRecord::ANSWER;
+ }
+ catch (std::exception& e) {
+ throw PDNSException("Error parsing record '" + resourceRecord.qname.toLogString() + "' of type " + resourceRecord.qtype.toString() + " in zone '" + headers.first + "' from file '" + headers.second + "': " + e.what());
+ }
+ catch (...) {
+ throw PDNSException("Error parsing record '" + resourceRecord.qname.toLogString() + "' of type " + resourceRecord.qtype.toString() + " in zone '" + headers.first + "' from file '" + headers.second + "'");
+ }
- auto newMap = std::make_shared<SyncRes::domainmap_t>();
- auto newSet = std::make_shared<notifyset_t>();
+ authDomain.d_records.insert(dnsrecord);
+ }
+}
- typedef vector<string> parts_t;
- parts_t parts;
- const char* option_names[3] = {"auth-zones", "forward-zones", "forward-zones-recurse"};
- for (int n = 0; n < 3; ++n) {
- parts.clear();
- stringtok(parts, ::arg()[option_names[n]], " ,\t\n\r");
- for (parts_t::const_iterator iter = parts.begin(); iter != parts.end(); ++iter) {
- SyncRes::AuthDomain ad;
- if ((*iter).find('=') == string::npos)
- throw PDNSException("Error parsing '" + *iter + "', missing =");
- pair<string, string> headers = splitField(*iter, '=');
+static void processForwardZones(shared_ptr<SyncRes::domainmap_t>& newMap, Logr::log_t log)
+{
+ const std::array<string, 3> option_names = {"auth-zones", "forward-zones", "forward-zones-recurse"};
+
+ for (size_t option = 0; option < option_names.size(); ++option) {
+ vector<string> parts;
+ stringtok(parts, ::arg()[option_names.at(option)], " ,\t\n\r");
+ for (const auto& part : parts) {
+ SyncRes::AuthDomain authDomain;
+ if (part.find('=') == string::npos) {
+ throw PDNSException("Error parsing '" + part + "', missing =");
+ }
+ pair<string, string> headers = splitField(part, '=');
boost::trim(headers.first);
boost::trim(headers.second);
- // headers.first=toCanonic("", headers.first);
- if (n == 0) {
- ad.d_rdForward = false;
- SLOG(g_log << Logger::Notice << "Parsing authoritative data for zone '" << headers.first << "' from file '" << headers.second << "'" << endl,
- log->info(Logr::Notice, "Parsing authoritative data from file", "zone", Logging::Loggable(headers.first), "file", Logging::Loggable(headers.second)));
- ZoneParserTNG zpt(headers.second, DNSName(headers.first));
- zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
- zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
- DNSResourceRecord rr;
- DNSRecord dr;
- while (zpt.get(rr)) {
- try {
- dr = DNSRecord(rr);
- dr.d_place = DNSResourceRecord::ANSWER;
- }
- catch (std::exception& e) {
- throw PDNSException("Error parsing record '" + rr.qname.toLogString() + "' of type " + rr.qtype.toString() + " in zone '" + headers.first + "' from file '" + headers.second + "': " + e.what());
- }
- catch (...) {
- throw PDNSException("Error parsing record '" + rr.qname.toLogString() + "' of type " + rr.qtype.toString() + " in zone '" + headers.first + "' from file '" + headers.second + "'");
- }
-
- ad.d_records.insert(dr);
- }
+
+ if (option == 0) {
+ authDomain.d_rdForward = false;
+ readAuthZoneData(authDomain, headers, log);
}
else {
- ad.d_rdForward = (n == 2);
- convertServersForAD(headers.first, headers.second, ad, ";", log);
+ authDomain.d_rdForward = (option == 2);
+ convertServersForAD(headers.first, headers.second, authDomain, ";", log);
}
- ad.d_name = DNSName(headers.first);
- (*newMap)[ad.d_name] = ad;
+ authDomain.d_name = DNSName(headers.first);
+ (*newMap)[authDomain.d_name] = authDomain;
}
}
+}
+
+static void processApiZonesFile(shared_ptr<SyncRes::domainmap_t>& newMap, shared_ptr<notifyset_t>& newSet, Logr::log_t log)
+{
+ if (::arg()["api-config-dir"].empty()) {
+ return;
+ }
+ const auto filename = ::arg()["api-config-dir"] + "/apizones";
+ struct stat statStruct
+ {
+ };
+ // It's a TOCTU, but a harmless one
+ if (stat(filename.c_str(), &statStruct) != 0) {
+ return;
+ }
+
+ SLOG(g_log << Logger::Notice << "Processing ApiZones YAML settings from " << filename << endl,
+ log->info(Logr::Notice, "Processing ApiZones YAML settings", "path", Logging::Loggable(filename)));
+
+ const uint64_t before = newMap->size();
- if (!::arg()["forward-zones-file"].empty()) {
- SLOG(g_log << Logger::Warning << "Reading zone forwarding information from '" << ::arg()["forward-zones-file"] << "'" << endl,
- log->info(Logr::Notice, "Reading zone forwarding information", "file", Logging::Loggable(::arg()["forward-zones-file"])));
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fopen(::arg()["forward-zones-file"].c_str(), "r"), fclose);
- if (!fp) {
- throw PDNSException("Error opening forward-zones-file '" + ::arg()["forward-zones-file"] + "': " + stringerror());
+ std::unique_ptr<pdns::rust::settings::rec::ApiZones> zones = pdns::rust::settings::rec::api_read_zones(filename);
+ zones->validate("apizones");
+
+ for (const auto& forward : zones->forward_zones) {
+ SyncRes::AuthDomain authDomain;
+ authDomain.d_name = DNSName(string(forward.zone));
+ authDomain.d_rdForward = forward.recurse;
+ for (const auto& forwarder : forward.forwarders) {
+ ComboAddress addr = parseIPAndPort(string(forwarder), 53);
+ authDomain.d_servers.emplace_back(addr);
+ }
+ (*newMap)[authDomain.d_name] = authDomain;
+ if (forward.notify_allowed) {
+ newSet->insert(authDomain.d_name);
+ }
+ }
+ for (const auto& auth : zones->auth_zones) {
+ SyncRes::AuthDomain authDomain;
+ authDomain.d_name = DNSName(string(auth.zone));
+ readAuthZoneData(authDomain, {string(auth.zone), string(auth.file)}, log);
+ (*newMap)[authDomain.d_name] = authDomain;
+ }
+ SLOG(g_log << Logger::Warning << "Done parsing " << newMap->size() - before
+ << " ApiZones YAML settings from file '"
+ << filename << "'" << endl,
+ log->info(Logr::Notice, "Done parsing ApiZones YAML from file", "file",
+ Logging::Loggable(filename), "count",
+ Logging::Loggable(newMap->size() - before)));
+}
+
+static void processForwardZonesFile(shared_ptr<SyncRes::domainmap_t>& newMap, shared_ptr<notifyset_t>& newSet, Logr::log_t log)
+{
+ const auto& filename = ::arg()["forward-zones-file"];
+ if (filename.empty()) {
+ return;
+ }
+ const uint64_t before = newMap->size();
+
+ if (boost::ends_with(filename, ".yml")) {
+ ::rust::Vec<pdns::rust::settings::rec::ForwardZone> vec;
+ pdns::settings::rec::readYamlForwardZonesFile(filename, vec, log);
+ for (const auto& forward : vec) {
+ SyncRes::AuthDomain authDomain;
+ authDomain.d_name = DNSName(string(forward.zone));
+ authDomain.d_rdForward = forward.recurse;
+ for (const auto& forwarder : forward.forwarders) {
+ ComboAddress addr = parseIPAndPort(string(forwarder), 53);
+ authDomain.d_servers.emplace_back(addr);
+ }
+ (*newMap)[authDomain.d_name] = authDomain;
+ if (forward.notify_allowed) {
+ newSet->insert(authDomain.d_name);
+ }
+ }
+ }
+ else {
+ SLOG(g_log << Logger::Warning << "Reading zone forwarding information from '" << filename << "'" << endl,
+ log->info(Logr::Notice, "Reading zone forwarding information", "file", Logging::Loggable(filename)));
+ auto filePtr = pdns::UniqueFilePtr(fopen(filename.c_str(), "r"));
+ if (!filePtr) {
+ int err = errno;
+ throw PDNSException("Error opening forward-zones-file '" + filename + "': " + stringerror(err));
}
string line;
int linenum = 0;
- uint64_t before = newMap->size();
- while (linenum++, stringfgets(fp.get(), line)) {
- SyncRes::AuthDomain ad;
+ while (linenum++, stringfgets(filePtr.get(), line)) {
+ SyncRes::AuthDomain authDomain;
boost::trim(line);
if (line[0] == '#') { // Comment line, skip to the next line
continue;
if (instructions.empty()) { // empty line
continue;
}
- throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
+ throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + filename);
}
bool allowNotifyFor = false;
for (; !domain.empty(); domain.erase(0, 1)) {
switch (domain[0]) {
case '+':
- ad.d_rdForward = true;
+ authDomain.d_rdForward = true;
continue;
case '^':
allowNotifyFor = true;
}
if (domain.empty()) {
- throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
+ throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + filename);
}
try {
- convertServersForAD(domain, instructions, ad, ",; ", log, false);
+ convertServersForAD(domain, instructions, authDomain, ",; ", log, false);
}
catch (...) {
- throw PDNSException("Conversion error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
+ throw PDNSException("Conversion error parsing line " + std::to_string(linenum) + " of " + filename);
}
- ad.d_name = DNSName(domain);
- (*newMap)[ad.d_name] = ad;
+ authDomain.d_name = DNSName(domain);
+ (*newMap)[authDomain.d_name] = authDomain;
if (allowNotifyFor) {
- newSet->insert(ad.d_name);
+ newSet->insert(authDomain.d_name);
}
}
- SLOG(g_log << Logger::Warning << "Done parsing " << newMap->size() - before
- << " forwarding instructions from file '"
- << ::arg()["forward-zones-file"] << "'" << endl,
- log->info(Logr::Notice, "Done parsing forwarding instructions from file", "file",
- Logging::Loggable(::arg()["forward-zones-file"]), "count",
- Logging::Loggable(newMap->size() - before)));
}
+ SLOG(g_log << Logger::Warning << "Done parsing " << newMap->size() - before
+ << " forwarding instructions from file '"
+ << filename << "'" << endl,
+ log->info(Logr::Notice, "Done parsing forwarding instructions from file", "file",
+ Logging::Loggable(filename), "count",
+ Logging::Loggable(newMap->size() - before)));
+}
- if (::arg().mustDo("export-etc-hosts")) {
- string fname = ::arg()["etc-hosts-file"];
- ifstream ifs(fname.c_str());
- if (!ifs) {
- SLOG(g_log << Logger::Warning << "Could not open " << fname << " for reading" << endl,
- log->error(Logr::Warning, "Could not open file for reading", "file", Logging::Loggable(fname)));
+static void processExportEtcHosts(std::shared_ptr<SyncRes::domainmap_t>& newMap, Logr::log_t log)
+{
+ if (!::arg().mustDo("export-etc-hosts")) {
+ return;
+ }
+ string fname = ::arg()["etc-hosts-file"];
+ ifstream ifs(fname);
+ if (!ifs) {
+ SLOG(g_log << Logger::Warning << "Could not open " << fname << " for reading" << endl,
+ log->error(Logr::Warning, "Could not open file for reading", "file", Logging::Loggable(fname)));
+ return;
+ }
+ vector<string> parts;
+ std::string line{};
+ while (getline(ifs, line)) {
+ if (!parseEtcHostsLine(parts, line)) {
+ continue;
}
- else {
- std::string line{};
- while (getline(ifs, line)) {
- if (!parseEtcHostsLine(parts, line)) {
- continue;
- }
- try {
- string searchSuffix = ::arg()["export-etc-hosts-search-suffix"];
- addForwardAndReverseLookupEntries(*newMap, searchSuffix, parts, log);
- }
- catch (const PDNSException& ex) {
- SLOG(g_log << Logger::Warning
- << "The line `" << line << "` "
- << "in the provided etc-hosts file `" << fname << "` "
- << "could not be added: " << ex.reason << ". Going to skip it."
- << endl,
- log->info(Logr::Notice, "Skipping line in etc-hosts file",
- "line", Logging::Loggable(line),
- "hosts-file", Logging::Loggable(fname),
- "reason", Logging::Loggable(ex.reason)));
- }
- }
+ try {
+ string searchSuffix = ::arg()["export-etc-hosts-search-suffix"];
+ addForwardAndReverseLookupEntries(*newMap, searchSuffix, parts, log);
+ }
+ catch (const PDNSException& ex) {
+ SLOG(g_log << Logger::Warning
+ << "The line `" << line << "` "
+ << "in the provided etc-hosts file `" << fname << "` "
+ << "could not be added: " << ex.reason << ". Going to skip it."
+ << endl,
+ log->info(Logr::Notice, "Skipping line in etc-hosts file",
+ "line", Logging::Loggable(line),
+ "hosts-file", Logging::Loggable(fname),
+ "reason", Logging::Loggable(ex.reason)));
}
}
+}
- if (::arg().mustDo("serve-rfc1918")) {
- SLOG(g_log << Logger::Warning << "Inserting rfc 1918 private space zones" << endl,
- log->info(Logr::Notice, "Inserting rfc 1918 private space zones"));
+static void processServeRFC1918(std::shared_ptr<SyncRes::domainmap_t>& newMap, Logr::log_t log)
+{
+ if (!::arg().mustDo("serve-rfc1918")) {
+ return;
+ }
+ SLOG(g_log << Logger::Warning << "Inserting rfc 1918 private space zones" << endl,
+ log->info(Logr::Notice, "Inserting rfc 1918 private space zones"));
- makePartialIPZone(*newMap, {"127"}, log);
- makePartialIPZone(*newMap, {"10"}, log);
- makePartialIPZone(*newMap, {"192", "168"}, log);
+ makePartialIPZone(*newMap, {"127"}, log);
+ makePartialIPZone(*newMap, {"10"}, log);
+ makePartialIPZone(*newMap, {"192", "168"}, log);
- for (int n = 16; n < 32; n++) {
- makePartialIPZone(*newMap, {"172", std::to_string(n).c_str()}, log);
- }
+ for (int count = 16; count < 32; count++) {
+ makePartialIPZone(*newMap, {"172", std::to_string(count).c_str()}, log);
}
+}
- parts.clear();
+static void processAllowNotifyFor(shared_ptr<notifyset_t>& newSet)
+{
+ vector<string> parts;
stringtok(parts, ::arg()["allow-notify-for"], " ,\t\n\r");
for (auto& part : parts) {
newSet->insert(DNSName(part));
}
+}
- if (auto anff = ::arg()["allow-notify-for-file"]; !anff.empty()) {
- SLOG(g_log << Logger::Warning << "Reading NOTIFY-allowed zones from '" << anff << "'" << endl,
- log->info(Logr::Notice, "Reading NOTIFY-allowed zones from file", "file", Logging::Loggable(anff)));
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fopen(anff.c_str(), "r"), fclose);
- if (!fp) {
- throw PDNSException("Error opening allow-notify-for-file '" + anff + "': " + stringerror());
+static void processAllowNotifyForFile(shared_ptr<notifyset_t>& newSet, Logr::log_t log)
+{
+ const auto& filename = ::arg()["allow-notify-for-file"];
+ if (filename.empty()) {
+ return;
+ }
+ const uint64_t before = newSet->size();
+ if (boost::ends_with(filename, ".yml")) {
+ ::rust::Vec<::rust::String> vec;
+ pdns::settings::rec::readYamlAllowNotifyForFile(filename, vec, log);
+ for (const auto& name : vec) {
+ newSet->insert(DNSName(string(name)));
+ }
+ }
+ else {
+ SLOG(g_log << Logger::Warning << "Reading NOTIFY-allowed zones from '" << filename << "'" << endl,
+ log->info(Logr::Notice, "Reading NOTIFY-allowed zones from file", "file", Logging::Loggable(filename)));
+ auto filePtr = pdns::UniqueFilePtr(fopen(filename.c_str(), "r"));
+ if (!filePtr) {
+ throw PDNSException("Error opening allow-notify-for-file '" + filename + "': " + stringerror());
}
string line;
- uint64_t before = newSet->size();
- while (stringfgets(fp.get(), line)) {
+ while (stringfgets(filePtr.get(), line)) {
boost::trim(line);
- if (line[0] == '#') // Comment line, skip to the next line
+ if (line[0] == '#') { // Comment line, skip to the next line
continue;
+ }
newSet->insert(DNSName(line));
}
- SLOG(g_log << Logger::Warning << "Done parsing " << newSet->size() - before << " NOTIFY-allowed zones from file '" << anff << "'" << endl,
- log->info(Logr::Notice, "Done parsing NOTIFY-allowed zones from file", "file", Logging::Loggable(anff), "count", Logging::Loggable(newSet->size() - before)));
}
+ SLOG(g_log << Logger::Warning << "Done parsing " << newSet->size() - before << " NOTIFY-allowed zones from file '" << filename << "'" << endl,
+ log->info(Logr::Notice, "Done parsing NOTIFY-allowed zones from file", "file", Logging::Loggable(filename), "count", Logging::Loggable(newSet->size() - before)));
+}
+
+std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration(bool yaml)
+{
+ auto log = g_slog->withName("config");
+
+ TXTRecordContent::report();
+ OPTRecordContent::report();
+
+ auto newMap = std::make_shared<SyncRes::domainmap_t>();
+ auto newSet = std::make_shared<notifyset_t>();
+
+ processForwardZones(newMap, log);
+ processForwardZonesFile(newMap, newSet, log);
+ if (yaml) {
+ processApiZonesFile(newMap, newSet, log);
+ }
+ processExportEtcHosts(newMap, log);
+ processServeRFC1918(newMap, log);
+ processAllowNotifyFor(newSet);
+ processAllowNotifyForFile(newSet, log);
return {newMap, newSet};
}
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
#pragma once
#include "config.h"
#include <boost/uuid/uuid.hpp>
#include <boost/optional.hpp>
+#include <functional>
+
+#include "dnsname.hh"
struct ResolveContext
{
- ResolveContext()
- {
- }
+ ResolveContext(const boost::optional<const boost::uuids::uuid&>& uuid, DNSName name) :
+ d_initialRequestId(uuid), d_nsName(std::move(name))
+ {}
+ ~ResolveContext() = default;
- ResolveContext(const ResolveContext& ctx) = delete;
+ ResolveContext(const ResolveContext&) = delete;
ResolveContext& operator=(const ResolveContext&) = delete;
+ ResolveContext(ResolveContext&&) = delete;
+ ResolveContext& operator=(ResolveContext&&) = delete;
boost::optional<const boost::uuids::uuid&> d_initialRequestId;
DNSName d_nsName;
const std::array<const std::string, 13> rootIps4 = {
"198.41.0.4", // a.root-servers.net.
- "199.9.14.201", // b.root-servers.net.
+ "170.247.170.2", // b.root-servers.net.
"192.33.4.12", // c.root-servers.net.
"199.7.91.13", // d.root-servers.net.
"192.203.230.10", // e.root-servers.net.
const std::array<const std::string, 13> rootIps6 = {
"2001:503:ba3e::2:30", // a.root-servers.net.
- "2001:500:200::b", // b.root-servers.net.
+ "2801:1b8:10::b", // b.root-servers.net.
"2001:500:2::c", // c.root-servers.net.
"2001:500:2d::d", // d.root-servers.net.
"2001:500:a8::e", // e.root-servers.net.
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <condition_variable>
#include "arguments.hh"
#include "dnsparser.hh"
#include "dnsrecords.hh"
* $NETMASK.zz (::/$NETMASK)
* Terrible right?
*/
- if (parts.size() < 2 || parts.size() > 9)
+ if (parts.size() < 2 || parts.size() > 9) {
throw PDNSException("Invalid IP address in RPZ: " + name.toLogString());
+ }
bool isV6 = (stoi(parts[0]) > 32);
bool hadZZ = false;
for (auto& part : parts) {
// Check if we have an IPv4 octet
- for (auto c : part)
- if (!isdigit(c))
+ for (auto labelLetter : part) {
+ if (isdigit(labelLetter) == 0) {
isV6 = true;
-
+ }
+ }
if (pdns_iequals(part, "zz")) {
- if (hadZZ)
+ if (hadZZ) {
throw PDNSException("more than one 'zz' label found in RPZ name" + name.toLogString());
+ }
part = "";
isV6 = true;
hadZZ = true;
}
}
- if (isV6 && parts.size() < 9 && !hadZZ)
+ if (isV6 && parts.size() < 9 && !hadZZ) {
throw PDNSException("No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: " + name.toLogString());
+ }
- if (parts.size() == 5 && !isV6)
- return Netmask(parts[4] + "." + parts[3] + "." + parts[2] + "." + parts[1] + "/" + parts[0]);
-
- string v6;
+ if (parts.size() == 5 && !isV6) {
+ return parts[4] + "." + parts[3] + "." + parts[2] + "." + parts[1] + "/" + parts[0];
+ }
+ string v6Address;
- if (parts[parts.size() - 1] == "") {
- v6 += ":";
+ if (parts[parts.size() - 1].empty()) {
+ v6Address += ":";
}
for (uint8_t i = parts.size() - 1; i > 0; i--) {
- v6 += parts[i];
- if (i > 1 || (i == 1 && parts[i] == "")) {
- v6 += ":";
+ v6Address += parts[i];
+ if (i > 1 || (i == 1 && parts[i].empty())) {
+ v6Address += ":";
}
}
- v6 += "/" + parts[0];
+ v6Address += "/" + parts[0];
- return Netmask(v6);
+ return v6Address;
}
-static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, Logr::log_t log)
+static void RPZRecordToPolicy(const DNSRecord& dnsRecord, const std::shared_ptr<DNSFilterEngine::Zone>& zone, bool addOrRemove, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, Logr::log_t log)
{
- static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
- static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
- rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
+ static const DNSName drop("rpz-drop.");
+ static const DNSName truncate("rpz-tcp-only.");
+ static const DNSName noaction("rpz-passthru.");
+ static const DNSName rpzClientIP("rpz-client-ip");
+ static const DNSName rpzIP("rpz-ip");
+ static const DNSName rpzNSDname("rpz-nsdname");
+ static const DNSName rpzNSIP("rpz-nsip.");
static const std::string rpzPrefix("rpz-");
DNSFilterEngine::Policy pol;
bool defpolApplied = false;
- if (dr.d_class != QClass::IN) {
+ if (dnsRecord.d_class != QClass::IN) {
return;
}
- if (dr.d_type == QType::CNAME) {
- auto crc = getRR<CNAMERecordContent>(dr);
+ if (dnsRecord.d_type == QType::CNAME) {
+ auto crc = getRR<CNAMERecordContent>(dnsRecord);
if (!crc) {
return;
}
else if (!crcTarget.empty() && !crcTarget.isRoot() && crcTarget.getRawLabel(crcTarget.countLabels() - 1).compare(0, rpzPrefix.length(), rpzPrefix) == 0) {
/* this is very likely an higher format number or a configuration error,
let's just ignore it. */
- SLOG(g_log << Logger::Info << "Discarding unsupported RPZ entry " << crcTarget << " for " << dr.d_name << endl,
- log->info(Logr::Info, "Discarding unsupported RPZ entry", "target", Logging::Loggable(crcTarget), "name", Logging::Loggable(dr.d_name)));
+ SLOG(g_log << Logger::Info << "Discarding unsupported RPZ entry " << crcTarget << " for " << dnsRecord.d_name << endl,
+ log->info(Logr::Info, "Discarding unsupported RPZ entry", "target", Logging::Loggable(crcTarget), "name", Logging::Loggable(dnsRecord.d_name)));
return;
}
else {
pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
- pol.d_custom.emplace_back(dr.getContent());
+ if (!pol.d_custom) {
+ pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+ }
+ pol.d_custom->emplace_back(dnsRecord.getContent());
// cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
}
}
}
else {
pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
- pol.d_custom.emplace_back(dr.getContent());
+ if (!pol.d_custom) {
+ pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+ }
+ pol.d_custom->emplace_back(dnsRecord.getContent());
// cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": ";
}
}
if (!defpolApplied || defpol->d_ttl < 0) {
- pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dr.d_ttl));
+ pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dnsRecord.d_ttl));
}
else {
pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl)));
// now to DO something with that
- if (dr.d_name.isPartOf(rpzNSDname)) {
- DNSName filt = dr.d_name.makeRelative(rpzNSDname);
- if (addOrRemove)
+ if (dnsRecord.d_name.isPartOf(rpzNSDname)) {
+ DNSName filt = dnsRecord.d_name.makeRelative(rpzNSDname);
+ if (addOrRemove) {
zone->addNSTrigger(filt, std::move(pol), defpolApplied);
- else
- zone->rmNSTrigger(filt, std::move(pol));
- }
- else if (dr.d_name.isPartOf(rpzClientIP)) {
- DNSName filt = dr.d_name.makeRelative(rpzClientIP);
- auto nm = makeNetmaskFromRPZ(filt);
- if (addOrRemove)
- zone->addClientTrigger(nm, std::move(pol), defpolApplied);
- else
- zone->rmClientTrigger(nm, std::move(pol));
- }
- else if (dr.d_name.isPartOf(rpzIP)) {
+ }
+ else {
+ zone->rmNSTrigger(filt, pol);
+ }
+ }
+ else if (dnsRecord.d_name.isPartOf(rpzClientIP)) {
+ DNSName filt = dnsRecord.d_name.makeRelative(rpzClientIP);
+ auto netmask = makeNetmaskFromRPZ(filt);
+ if (addOrRemove) {
+ zone->addClientTrigger(netmask, std::move(pol), defpolApplied);
+ }
+ else {
+ zone->rmClientTrigger(netmask, pol);
+ }
+ }
+ else if (dnsRecord.d_name.isPartOf(rpzIP)) {
// cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
- DNSName filt = dr.d_name.makeRelative(rpzIP);
- auto nm = makeNetmaskFromRPZ(filt);
- if (addOrRemove)
- zone->addResponseTrigger(nm, std::move(pol), defpolApplied);
- else
- zone->rmResponseTrigger(nm, std::move(pol));
- }
- else if (dr.d_name.isPartOf(rpzNSIP)) {
- DNSName filt = dr.d_name.makeRelative(rpzNSIP);
- auto nm = makeNetmaskFromRPZ(filt);
- if (addOrRemove)
- zone->addNSIPTrigger(nm, std::move(pol), defpolApplied);
- else
- zone->rmNSIPTrigger(nm, std::move(pol));
+ DNSName filt = dnsRecord.d_name.makeRelative(rpzIP);
+ auto netmask = makeNetmaskFromRPZ(filt);
+ if (addOrRemove) {
+ zone->addResponseTrigger(netmask, std::move(pol), defpolApplied);
+ }
+ else {
+ zone->rmResponseTrigger(netmask, pol);
+ }
+ }
+ else if (dnsRecord.d_name.isPartOf(rpzNSIP)) {
+ DNSName filt = dnsRecord.d_name.makeRelative(rpzNSIP);
+ auto netmask = makeNetmaskFromRPZ(filt);
+ if (addOrRemove) {
+ zone->addNSIPTrigger(netmask, std::move(pol), defpolApplied);
+ }
+ else {
+ zone->rmNSIPTrigger(netmask, pol);
+ }
}
else {
if (addOrRemove) {
/* if we did override the existing policy with the default policy,
we might turn two A or AAAA into a CNAME, which would trigger
an exception. Let's just ignore it. */
- zone->addQNameTrigger(dr.d_name, std::move(pol), defpolApplied);
+ zone->addQNameTrigger(dnsRecord.d_name, std::move(pol), defpolApplied);
}
else {
- zone->rmQNameTrigger(dr.d_name, std::move(pol));
+ zone->rmQNameTrigger(dnsRecord.d_name, pol);
}
}
}
-static shared_ptr<const SOARecordContent> loadRPZFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
+static shared_ptr<const SOARecordContent> loadRPZFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tsigTriplet, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
{
auto logger = plogger->withValues("primary", Logging::Loggable(primary));
SLOG(g_log << Logger::Warning << "Loading RPZ zone '" << zoneName << "' from " << primary.toStringWithPort() << endl,
logger->info(Logr::Info, "Loading RPZ from nameserver"));
- if (!tt.name.empty()) {
- SLOG(g_log << Logger::Warning << "With TSIG key '" << tt.name << "' of algorithm '" << tt.algo << "'" << endl,
- logger->info(Logr::Info, "Using TSIG key for authentication", "tsig_key_name", Logging::Loggable(tt.name), "tsig_key_algorithm", Logging::Loggable(tt.algo)));
+ if (!tsigTriplet.name.empty()) {
+ SLOG(g_log << Logger::Warning << "With TSIG key '" << tsigTriplet.name << "' of algorithm '" << tsigTriplet.algo << "'" << endl,
+ logger->info(Logr::Info, "Using TSIG key for authentication", "tsig_key_name", Logging::Loggable(tsigTriplet.name), "tsig_key_algorithm", Logging::Loggable(tsigTriplet.algo)));
}
ComboAddress local(localAddress);
- if (local == ComboAddress())
+ if (local == ComboAddress()) {
local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
+ }
- AXFRRetriever axfr(primary, zoneName, tt, &local, maxReceivedBytes, axfrTimeout);
+ AXFRRetriever axfr(primary, zoneName, tsigTriplet, &local, maxReceivedBytes, axfrTimeout);
unsigned int nrecords = 0;
Resolver::res_t nop;
vector<DNSRecord> chunk;
time_t last = 0;
time_t axfrStart = time(nullptr);
time_t axfrNow = time(nullptr);
- shared_ptr<const SOARecordContent> sr;
- while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
- for (auto& dr : chunk) {
- if (dr.d_type == QType::NS || dr.d_type == QType::TSIG) {
+ shared_ptr<const SOARecordContent> soaRecordContent;
+ // coverity[store_truncates_time_t]
+ while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow)) != 0) {
+ for (auto& dnsRecord : chunk) {
+ if (dnsRecord.d_type == QType::NS || dnsRecord.d_type == QType::TSIG) {
continue;
}
- dr.d_name.makeUsRelative(zoneName);
- if (dr.d_type == QType::SOA) {
- sr = getRR<SOARecordContent>(dr);
+ dnsRecord.d_name.makeUsRelative(zoneName);
+ if (dnsRecord.d_type == QType::SOA) {
+ soaRecordContent = getRR<SOARecordContent>(dnsRecord);
+ zone->setSOA(dnsRecord);
continue;
}
- RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
+ RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
nrecords++;
}
axfrNow = time(nullptr);
if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
throw PDNSException("Total AXFR time exceeded!");
}
- if (last != time(0)) {
+ if (last != time(nullptr)) {
SLOG(g_log << Logger::Info << "Loaded & indexed " << nrecords << " policy records so far for RPZ zone '" << zoneName << "'" << endl,
logger->info(Logr::Info, "RPZ load in progress", "nrecords", Logging::Loggable(nrecords)));
- last = time(0);
+ last = time(nullptr);
}
}
- SLOG(g_log << Logger::Info << "Done: " << nrecords << " policy records active, SOA: " << sr->getZoneRepresentation() << endl,
- logger->info(Logr::Info, "RPZ load completed", "nrecords", Logging::Loggable(nrecords), "soa", Logging::Loggable(sr->getZoneRepresentation())));
- return sr;
+ SLOG(g_log << Logger::Info << "Done: " << nrecords << " policy records active, SOA: " << soaRecordContent->getZoneRepresentation() << endl,
+ logger->info(Logr::Info, "RPZ load completed", "nrecords", Logging::Loggable(nrecords), "soa", Logging::Loggable(soaRecordContent->getZoneRepresentation())));
+ return soaRecordContent;
}
static LockGuarded<std::unordered_map<std::string, shared_ptr<rpzStats>>> s_rpzStats;
shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone)
{
auto stats = s_rpzStats.lock();
- auto it = stats->find(zone);
- if (it == stats->end()) {
+ auto statsIt = stats->find(zone);
+ if (statsIt == stats->end()) {
auto stat = std::make_shared<rpzStats>();
(*stats)[zone] = stat;
return stat;
}
- return it->second;
+ return statsIt->second;
}
static void incRPZFailedTransfers(const std::string& zone)
{
auto stats = getRPZZoneStats(zone);
- if (stats != nullptr)
+ if (stats != nullptr) {
stats->d_failedTransfers++;
+ }
}
static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool fromFile, bool wasAXFR)
}
// this function is silent - you do the logging
-std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL)
+std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL)
{
- shared_ptr<const SOARecordContent> sr = nullptr;
+ shared_ptr<const SOARecordContent> soaRecordContent = nullptr;
ZoneParserTNG zpt(fname);
zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
DNSResourceRecord drr;
+ DNSRecord soaRecord;
DNSName domain;
auto log = g_slog->withName("rpz")->withValues("file", Logging::Loggable(fname), "zone", Logging::Loggable(zone->getName()));
while (zpt.get(drr)) {
try {
- if (drr.qtype.getCode() == QType::CNAME && drr.content.empty())
+ if (drr.qtype.getCode() == QType::CNAME && drr.content.empty()) {
drr.content = ".";
- DNSRecord dr(drr);
- if (dr.d_type == QType::SOA) {
- sr = getRR<SOARecordContent>(dr);
- domain = dr.d_name;
+ }
+ DNSRecord dnsRecord(drr);
+ if (dnsRecord.d_type == QType::SOA) {
+ soaRecordContent = getRR<SOARecordContent>(dnsRecord);
+ domain = dnsRecord.d_name;
zone->setDomain(domain);
+ soaRecord = std::move(dnsRecord);
}
- else if (dr.d_type == QType::NS) {
+ else if (dnsRecord.d_type == QType::NS) {
continue;
}
else {
- dr.d_name = dr.d_name.makeRelative(domain);
- RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, log);
+ dnsRecord.d_name = dnsRecord.d_name.makeRelative(domain);
+ RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, log);
}
}
catch (const PDNSException& pe) {
}
}
- if (sr != nullptr) {
- zone->setRefresh(sr->d_st.refresh);
- setRPZZoneNewState(zone->getName(), sr->d_st.serial, zone->size(), true, false);
+ if (soaRecordContent != nullptr) {
+ zone->setRefresh(soaRecordContent->d_st.refresh);
+ zone->setSOA(std::move(soaRecord));
+ setRPZZoneNewState(zone->getName(), soaRecordContent->d_st.serial, zone->size(), true, false);
}
- return sr;
+ return soaRecordContent;
}
static bool dumpZoneToDisk(Logr::log_t logger, const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& newZone, const std::string& dumpZoneFileName)
{
logger->info(Logr::Debug, "Dumping zone to disk", "destination_file", Logging::Loggable(dumpZoneFileName));
std::string temp = dumpZoneFileName + "XXXXXX";
- int fd = mkstemp(&temp.at(0));
- if (fd < 0) {
+ int fileDesc = mkstemp(&temp.at(0));
+ if (fileDesc < 0) {
SLOG(g_log << Logger::Warning << "Unable to open a file to dump the content of the RPZ zone " << zoneName << endl,
logger->error(Logr::Error, errno, "Unable to create temporary file"));
return false;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fd, "w+"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(fileDesc, "w+"));
+ if (!filePtr) {
int err = errno;
- close(fd);
+ close(fileDesc);
SLOG(g_log << Logger::Warning << "Unable to open a file pointer to dump the content of the RPZ zone " << zoneName << endl,
logger->error(Logr::Error, err, "Unable to open file pointer"));
return false;
}
try {
- newZone->dump(fp.get());
+ newZone->dump(filePtr.get());
}
catch (const std::exception& e) {
SLOG(g_log << Logger::Warning << "Error while dumping the content of the RPZ zone " << zoneName << ": " << e.what() << endl,
return false;
}
- if (fflush(fp.get()) != 0) {
+ if (fflush(filePtr.get()) != 0) {
SLOG(g_log << Logger::Warning << "Error while flushing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
logger->error(Logr::Warning, errno, "Error while flushing the content of the RPZ"));
return false;
}
- if (fsync(fileno(fp.get())) != 0) {
+ if (fsync(fileno(filePtr.get())) != 0) {
SLOG(g_log << Logger::Warning << "Error while syncing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
logger->error(Logr::Error, errno, "Error while syncing the content of the RPZ"));
return false;
}
- if (fclose(fp.release()) != 0) {
+ if (fclose(filePtr.release()) != 0) {
SLOG(g_log << Logger::Warning << "Error while writing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
logger->error(Logr::Error, errno, "Error while writing the content of the RPZ"));
return false;
return true;
}
-void RPZIXFRTracker(const std::vector<ComboAddress>& primaries, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t xfrTimeout, const uint32_t refreshFromConf, std::shared_ptr<const SOARecordContent> sr, const std::string& dumpZoneFileName, uint64_t configGeneration)
+// A struct that holds the condition var and related stuff to allow notifies to be sent to the tread owning
+// the struct.
+struct RPZWaiter
{
- setThreadName("rec/rpzixfr");
- bool isPreloaded = sr != nullptr;
- auto luaconfsLocal = g_luaconfs.getLocal();
-
- auto logger = g_slog->withName("rpz");
-
- /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
- std::shared_ptr<DNSFilterEngine::Zone> oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
- if (!oldZone) {
- SLOG(g_log << Logger::Error << "Unable to retrieve RPZ zone with index " << zoneIdx << " from the configuration, exiting" << endl,
- logger->error(Logr::Error, "Unable to retrieve RPZ zone from configuration", "index", Logging::Loggable(zoneIdx)));
- return;
- }
-
- // If oldZone failed to load its getRefresh() returns 0, protect against that
- uint32_t refresh = std::max(refreshFromConf ? refreshFromConf : oldZone->getRefresh(), 10U);
- DNSName zoneName = oldZone->getDomain();
- std::string polName = !oldZone->getName().empty() ? oldZone->getName() : zoneName.toStringNoDot();
-
- // Now that we know the name, set it in the logger
- logger = logger->withValues("zone", Logging::Loggable(zoneName));
-
- while (!sr) {
+ RPZWaiter(std::thread::id arg) :
+ id(arg) {}
+ std::thread::id id;
+ std::mutex mutex;
+ std::condition_variable condVar;
+ std::atomic<bool> stop{false};
+};
+
+static void preloadRPZFIle(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, RPZWaiter& rpzwaiter, Logr::log_t logger)
+{
+ while (!params.soaRecordContent) {
/* if we received an empty sr, the zone was not really preloaded */
/* full copy, as promised */
std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
- for (const auto& primary : primaries) {
+ for (const auto& primary : params.primaries) {
try {
- sr = loadRPZFromServer(logger, primary, zoneName, newZone, defpol, defpolOverrideLocal, maxTTL, tt, maxReceivedBytes, localAddress, xfrTimeout);
- newZone->setSerial(sr->d_st.serial);
- newZone->setRefresh(sr->d_st.refresh);
- refresh = std::max(refreshFromConf ? refreshFromConf : newZone->getRefresh(), 1U);
- setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), false, true);
+ params.soaRecordContent = loadRPZFromServer(logger, primary, zoneName, newZone, params.defpol, params.defpolOverrideLocal, params.maxTTL, params.tsigtriplet, params.maxReceivedBytes, params.localAddress, params.xfrTimeout);
+ newZone->setSerial(params.soaRecordContent->d_st.serial);
+ newZone->setRefresh(params.soaRecordContent->d_st.refresh);
+ refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : newZone->getRefresh(), 1U);
+ setRPZZoneNewState(polName, params.soaRecordContent->d_st.serial, newZone->size(), false, true);
- g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
+ g_luaconfs.modify([zoneIdx = params.zoneIdx, &newZone](LuaConfigItems& lci) {
lci.dfe.setZone(zoneIdx, newZone);
});
- if (!dumpZoneFileName.empty()) {
- dumpZoneToDisk(logger, zoneName, newZone, dumpZoneFileName);
+ if (!params.dumpZoneFileName.empty()) {
+ dumpZoneToDisk(logger, zoneName, newZone, params.dumpZoneFileName);
}
/* no need to try another primary */
incRPZFailedTransfers(polName);
}
}
-
- if (!sr) {
- sleep(refresh);
+ // Release newZone before (long) sleep to reduce memory usage
+ newZone = nullptr;
+ if (!params.soaRecordContent) {
+ std::unique_lock lock(rpzwaiter.mutex);
+ rpzwaiter.condVar.wait_for(lock, std::chrono::seconds(refresh),
+ [&stop = rpzwaiter.stop] { return stop.load(); });
}
+ rpzwaiter.stop = false;
}
+}
- bool skipRefreshDelay = isPreloaded;
-
- for (;;) {
- DNSRecord dr;
- dr.setContent(sr);
+static bool RPZTrackerIteration(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, bool& skipRefreshDelay, uint64_t configGeneration, RPZWaiter& rpzwaiter, Logr::log_t logger)
+{
+ // Don't hold on to oldZone, it well be re-assigned after sleep in the try block
+ oldZone = nullptr;
+ DNSRecord dnsRecord;
+ dnsRecord.setContent(params.soaRecordContent);
- if (skipRefreshDelay) {
- skipRefreshDelay = false;
- }
- else {
- sleep(refresh);
+ if (skipRefreshDelay) {
+ skipRefreshDelay = false;
+ }
+ else {
+ const time_t minimumTimeBetweenRefreshes = std::min(refresh, 5U);
+ const time_t startTime = time(nullptr);
+ time_t wakeTime = startTime;
+ while (wakeTime - startTime < minimumTimeBetweenRefreshes) {
+ std::unique_lock lock(rpzwaiter.mutex);
+ time_t remaining = refresh - (wakeTime - startTime);
+ if (remaining <= 0) {
+ break;
+ }
+ rpzwaiter.condVar.wait_for(lock, std::chrono::seconds(remaining),
+ [&stop = rpzwaiter.stop] { return stop.load(); });
+ rpzwaiter.stop = false;
+ wakeTime = time(nullptr);
}
+ }
+ auto luaconfsLocal = g_luaconfs.getLocal();
- if (luaconfsLocal->generation != configGeneration) {
- /* the configuration has been reloaded, meaning that a new thread
- has been started to handle that zone and we are now obsolete.
- */
- SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
- logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
- return;
- }
+ if (luaconfsLocal->generation != configGeneration) {
+ /* the configuration has been reloaded, meaning that a new thread
+ has been started to handle that zone and we are now obsolete.
+ */
+ SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
+ logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
+ return false;
+ }
- vector<pair<vector<DNSRecord>, vector<DNSRecord>>> deltas;
- for (const auto& primary : primaries) {
- auto soa = getRR<SOARecordContent>(dr);
- auto serial = soa ? soa->d_st.serial : 0;
- SLOG(g_log << Logger::Info << "Getting IXFR deltas for " << zoneName << " from " << primary.toStringWithPort() << ", our serial: " << serial << endl,
- logger->info(Logr::Info, "Getting IXFR deltas", "address", Logging::Loggable(primary), "ourserial", Logging::Loggable(serial)));
+ vector<pair<vector<DNSRecord>, vector<DNSRecord>>> deltas;
+ for (const auto& primary : params.primaries) {
+ auto soa = getRR<SOARecordContent>(dnsRecord);
+ auto serial = soa ? soa->d_st.serial : 0;
+ SLOG(g_log << Logger::Info << "Getting IXFR deltas for " << zoneName << " from " << primary.toStringWithPort() << ", our serial: " << serial << endl,
+ logger->info(Logr::Info, "Getting IXFR deltas", "address", Logging::Loggable(primary), "ourserial", Logging::Loggable(serial)));
- ComboAddress local(localAddress);
- if (local == ComboAddress()) {
- local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
- }
+ ComboAddress local(params.localAddress);
+ if (local == ComboAddress()) {
+ local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
+ }
- try {
- deltas = getIXFRDeltas(primary, zoneName, dr, xfrTimeout, true, tt, &local, maxReceivedBytes);
+ try {
+ deltas = getIXFRDeltas(primary, zoneName, dnsRecord, params.xfrTimeout, true, params.tsigtriplet, &local, params.maxReceivedBytes);
- /* no need to try another primary */
- break;
- }
- catch (const std::runtime_error& e) {
- SLOG(g_log << Logger::Warning << e.what() << endl,
- logger->error(Logr::Warning, e.what(), "Exception during retrieval of delta", "exception", Logging::Loggable("std::runtime_error")));
- incRPZFailedTransfers(polName);
- continue;
- }
+ /* no need to try another primary */
+ break;
}
-
- if (deltas.empty()) {
+ catch (const std::runtime_error& e) {
+ SLOG(g_log << Logger::Warning << e.what() << endl,
+ logger->error(Logr::Warning, e.what(), "Exception during retrieval of delta", "exception", Logging::Loggable("std::runtime_error")));
+ incRPZFailedTransfers(polName);
continue;
}
+ }
- try {
- SLOG(g_log << Logger::Info << "Processing " << deltas.size() << " delta" << addS(deltas) << " for RPZ " << zoneName << endl,
- logger->info(Logr::Info, "Processing deltas", "size", Logging::Loggable(deltas.size())));
+ if (deltas.empty()) {
+ return true;
+ }
- if (luaconfsLocal->generation != configGeneration) {
- SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
- logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"))
- return;
- }
- oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
- if (!oldZone || oldZone->getDomain() != zoneName) {
- SLOG(g_log << Logger::Info << "This policy is no more, stopping the existing RPZ update thread for " << zoneName << endl,
- logger->info(Logr::Info, "This policy is no more, stopping the existing RPZ update thread"));
- return;
+ try {
+ SLOG(g_log << Logger::Info << "Processing " << deltas.size() << " delta" << addS(deltas) << " for RPZ " << zoneName << endl,
+ logger->info(Logr::Info, "Processing deltas", "size", Logging::Loggable(deltas.size())));
+
+ if (luaconfsLocal->generation != configGeneration) {
+ SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
+ logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
+ return false;
+ }
+ oldZone = luaconfsLocal->dfe.getZone(params.zoneIdx);
+ if (!oldZone || oldZone->getDomain() != zoneName) {
+ SLOG(g_log << Logger::Info << "This policy is no more, stopping the existing RPZ update thread for " << zoneName << endl,
+ logger->info(Logr::Info, "This policy is no more, stopping the existing RPZ update thread"));
+ return false;
+ }
+ /* we need to make a _full copy_ of the zone we are going to work on */
+ std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
+ /* initialize the current serial to the last one */
+ std::shared_ptr<const SOARecordContent> currentSR = params.soaRecordContent;
+
+ int totremove = 0;
+ int totadd = 0;
+ bool fullUpdate = false;
+ for (const auto& delta : deltas) {
+ const auto& remove = delta.first;
+ const auto& add = delta.second;
+ if (remove.empty()) {
+ SLOG(g_log << Logger::Warning << "IXFR update is a whole new zone" << endl,
+ logger->info(Logr::Warning, "IXFR update is a whole new zone"));
+ newZone->clear();
+ fullUpdate = true;
}
- /* we need to make a _full copy_ of the zone we are going to work on */
- std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
- /* initialize the current serial to the last one */
- std::shared_ptr<const SOARecordContent> currentSR = sr;
-
- int totremove = 0, totadd = 0;
- bool fullUpdate = false;
- for (const auto& delta : deltas) {
- const auto& remove = delta.first;
- const auto& add = delta.second;
- if (remove.empty()) {
- SLOG(g_log << Logger::Warning << "IXFR update is a whole new zone" << endl,
- logger->info(Logr::Warning, "IXFR update is a whole new zone"));
- newZone->clear();
- fullUpdate = true;
+ for (const auto& resourceRecord : remove) { // should always contain the SOA
+ if (resourceRecord.d_type == QType::NS) {
+ continue;
}
- for (const auto& rr : remove) { // should always contain the SOA
- if (rr.d_type == QType::NS)
- continue;
- if (rr.d_type == QType::SOA) {
- auto oldsr = getRR<SOARecordContent>(rr);
- if (oldsr && oldsr->d_st.serial == currentSR->d_st.serial) {
- // cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl;
- }
- else {
- if (!oldsr) {
- throw std::runtime_error("Unable to extract serial from SOA record while processing the removal part of an update");
- }
- else {
- throw std::runtime_error("Received an unexpected serial (" + std::to_string(oldsr->d_st.serial) + ", expecting " + std::to_string(currentSR->d_st.serial) + ") from SOA record while processing the removal part of an update");
- }
- }
+ if (resourceRecord.d_type == QType::SOA) {
+ auto oldsr = getRR<SOARecordContent>(resourceRecord);
+ if (oldsr && oldsr->d_st.serial == currentSR->d_st.serial) {
+ // Got good removal of SOA serial, no work to be done
}
else {
- totremove++;
- SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had removal of " << rr.d_name << " from RPZ zone " << zoneName << endl,
- logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Remove from RPZ zone", "name", Logging::Loggable(rr.d_name)));
- RPZRecordToPolicy(rr, newZone, false, defpol, defpolOverrideLocal, maxTTL, logger);
- }
- }
-
- for (const auto& rr : add) { // should always contain the new SOA
- if (rr.d_type == QType::NS)
- continue;
- if (rr.d_type == QType::SOA) {
- auto tempSR = getRR<SOARecordContent>(rr);
- // g_log<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<currentSR->d_st.serial<<endl;
- if (tempSR) {
- currentSR = tempSR;
+ if (!oldsr) {
+ throw std::runtime_error("Unable to extract serial from SOA record while processing the removal part of an update");
}
- }
- else {
- totadd++;
- SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had addition of " << rr.d_name << " to RPZ zone " << zoneName << endl,
- logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Addition to RPZ zone", "name", Logging::Loggable(rr.d_name)));
- RPZRecordToPolicy(rr, newZone, true, defpol, defpolOverrideLocal, maxTTL, logger);
+ throw std::runtime_error("Received an unexpected serial (" + std::to_string(oldsr->d_st.serial) + ", expecting " + std::to_string(currentSR->d_st.serial) + ") from SOA record while processing the removal part of an update");
}
}
+ else {
+ totremove++;
+ SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had removal of " << resourceRecord.d_name << " from RPZ zone " << zoneName << endl,
+ logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Remove from RPZ zone", "name", Logging::Loggable(resourceRecord.d_name)));
+ RPZRecordToPolicy(resourceRecord, newZone, false, params.defpol, params.defpolOverrideLocal, params.maxTTL, logger);
+ }
}
- /* only update sr now that all changes have been converted */
- if (currentSR) {
- sr = currentSR;
- }
- SLOG(g_log << Logger::Info << "Had " << totremove << " RPZ removal" << addS(totremove) << ", " << totadd << " addition" << addS(totadd) << " for " << zoneName << " New serial: " << sr->d_st.serial << endl,
- logger->info(Logr::Info, "RPZ mutations", "removals", Logging::Loggable(totremove), "additions", Logging::Loggable(totadd), "newserial", Logging::Loggable(sr->d_st.serial)));
- newZone->setSerial(sr->d_st.serial);
- newZone->setRefresh(sr->d_st.refresh);
- setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), false, fullUpdate);
-
- /* we need to replace the existing zone with the new one,
- but we don't want to touch anything else, especially other zones,
- since they might have been updated by another RPZ IXFR tracker thread.
- */
- if (luaconfsLocal->generation != configGeneration) {
- SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
- logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
- return;
+ for (const auto& resourceRecord : add) { // should always contain the new SOA
+ if (resourceRecord.d_type == QType::NS) {
+ continue;
+ }
+ if (resourceRecord.d_type == QType::SOA) {
+ auto tempSR = getRR<SOARecordContent>(resourceRecord);
+ if (tempSR) {
+ currentSR = std::move(tempSR);
+ }
+ }
+ else {
+ totadd++;
+ SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had addition of " << resourceRecord.d_name << " to RPZ zone " << zoneName << endl,
+ logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Addition to RPZ zone", "name", Logging::Loggable(resourceRecord.d_name)));
+ RPZRecordToPolicy(resourceRecord, newZone, true, params.defpol, params.defpolOverrideLocal, params.maxTTL, logger);
+ }
}
- g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
- lci.dfe.setZone(zoneIdx, newZone);
- });
+ }
- if (!dumpZoneFileName.empty()) {
- dumpZoneToDisk(logger, zoneName, newZone, dumpZoneFileName);
- }
- refresh = std::max(refreshFromConf ? refreshFromConf : newZone->getRefresh(), 1U);
+ /* only update sr now that all changes have been converted */
+ if (currentSR) {
+ params.soaRecordContent = std::move(currentSR);
}
- catch (const std::exception& e) {
- SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.what() << endl,
- logger->error(Logr::Error, e.what(), "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("std::exception")));
+ SLOG(g_log << Logger::Info << "Had " << totremove << " RPZ removal" << addS(totremove) << ", " << totadd << " addition" << addS(totadd) << " for " << zoneName << " New serial: " << params.soaRecordContent->d_st.serial << endl,
+ logger->info(Logr::Info, "RPZ mutations", "removals", Logging::Loggable(totremove), "additions", Logging::Loggable(totadd), "newserial", Logging::Loggable(params.soaRecordContent->d_st.serial)));
+ newZone->setSerial(params.soaRecordContent->d_st.serial);
+ newZone->setRefresh(params.soaRecordContent->d_st.refresh);
+ setRPZZoneNewState(polName, params.soaRecordContent->d_st.serial, newZone->size(), false, fullUpdate);
+
+ /* we need to replace the existing zone with the new one,
+ but we don't want to touch anything else, especially other zones,
+ since they might have been updated by another RPZ IXFR tracker thread.
+ */
+ if (luaconfsLocal->generation != configGeneration) {
+ SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
+ logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
+ return false;
+ }
+ g_luaconfs.modify([zoneIdx = params.zoneIdx, &newZone](LuaConfigItems& lci) {
+ lci.dfe.setZone(zoneIdx, newZone);
+ });
+
+ if (!params.dumpZoneFileName.empty()) {
+ dumpZoneToDisk(logger, zoneName, newZone, params.dumpZoneFileName);
}
- catch (const PDNSException& e) {
- SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.reason << endl,
- logger->error(Logr::Error, e.reason, "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("PDNSException")));
+ refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : newZone->getRefresh(), 1U);
+ }
+ catch (const std::exception& e) {
+ SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.what() << endl,
+ logger->error(Logr::Error, e.what(), "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("std::exception")));
+ }
+ catch (const PDNSException& e) {
+ SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.reason << endl,
+ logger->error(Logr::Error, e.reason, "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("PDNSException")));
+ }
+ return true;
+}
+
+// As there can be multiple threads doing updates (due to config reloads), we use a multimap.
+// The value contains the actual thread id that owns the struct.
+
+static LockGuarded<std::multimap<DNSName, RPZWaiter&>> condVars;
+
+// Notify all threads tracking the RPZ name
+bool notifyRPZTracker(const DNSName& name)
+{
+ auto lock = condVars.lock();
+ auto [start, end] = lock->equal_range(name);
+ if (start == end) {
+ // Did not find any thread tracking that RPZ name
+ return false;
+ }
+ while (start != end) {
+ start->second.stop = true;
+ start->second.condVar.notify_one();
+ ++start;
+ }
+ return true;
+}
+
+// coverity[pass_by_value] params is intended to be a copy, as this is the main function of a thread
+void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration)
+{
+ setThreadName("rec/rpzixfr");
+ bool isPreloaded = params.soaRecordContent != nullptr;
+ auto logger = g_slog->withName("rpz");
+ RPZWaiter waiter(std::this_thread::get_id());
+
+ /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
+ std::shared_ptr<DNSFilterEngine::Zone> oldZone = g_luaconfs.getLocal()->dfe.getZone(params.zoneIdx);
+ if (!oldZone) {
+ SLOG(g_log << Logger::Error << "Unable to retrieve RPZ zone with index " << params.zoneIdx << " from the configuration, exiting" << endl,
+ logger->error(Logr::Error, "Unable to retrieve RPZ zone from configuration", "index", Logging::Loggable(params.zoneIdx)));
+ return;
+ }
+
+ // If oldZone failed to load its getRefresh() returns 0, protect against that
+ uint32_t refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : oldZone->getRefresh(), 10U);
+ DNSName zoneName = oldZone->getDomain();
+ std::string polName = !oldZone->getName().empty() ? oldZone->getName() : zoneName.toStringNoDot();
+
+ // Now that we know the name, set it in the logger
+ logger = logger->withValues("zone", Logging::Loggable(zoneName));
+
+ {
+ auto lock = condVars.lock();
+ lock->emplace(zoneName, waiter);
+ }
+ preloadRPZFIle(params, zoneName, oldZone, refresh, polName, waiter, logger);
+
+ bool skipRefreshDelay = isPreloaded;
+
+ while (RPZTrackerIteration(params, zoneName, oldZone, refresh, polName, skipRefreshDelay, configGeneration, waiter, logger)) {
+ // empty
+ }
+
+ // Zap our (and only our) RPZWaiter struct out of the multimap
+ auto lock = condVars.lock();
+ auto [start, end] = lock->equal_range(zoneName);
+ while (start != end) {
+ if (start->second.id == std::this_thread::get_id()) {
+ lock->erase(start);
+ break;
}
+ ++start;
}
}
extern bool g_logRPZChanges;
-std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL);
-void RPZIXFRTracker(const std::vector<ComboAddress>& primaries, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t xfrTimeout, const uint32_t reloadFromConf, shared_ptr<const SOARecordContent> sr, const std::string& dumpZoneFileName, uint64_t configGeneration);
+// Please make sure that the struct below only contains value types since they are used as parameters in a thread ct
+struct RPZTrackerParams
+{
+ std::vector<ComboAddress> primaries;
+ boost::optional<DNSFilterEngine::Policy> defpol;
+ bool defpolOverrideLocal;
+ uint32_t maxTTL;
+ size_t zoneIdx;
+ TSIGTriplet tsigtriplet;
+ size_t maxReceivedBytes;
+ ComboAddress localAddress;
+ uint16_t xfrTimeout;
+ uint32_t refreshFromConf;
+ std::shared_ptr<const SOARecordContent> soaRecordContent;
+ std::string dumpZoneFileName;
+};
+
+std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL);
+void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration);
+bool notifyRPZTracker(const DNSName& name);
struct rpzStats
{
#include "validate-recursor.hh"
#include "secpoll.hh"
-#include <stdint.h>
+#include <cstdint>
#ifndef PACKAGEVERSION
#define PACKAGEVERSION getPDNSVersion()
#endif
void doSecPoll(time_t* last_secpoll, Logr::log_t log)
{
- if (::arg()["security-poll-suffix"].empty())
+ if (::arg()["security-poll-suffix"].empty()) {
return;
+ }
string pkgv(PACKAGEVERSION);
- struct timeval now;
- gettimeofday(&now, 0);
+ struct timeval now
+ {
+ };
+ Utility::gettimeofday(&now);
/* update last_secpoll right now, even if it fails
we don't want to retry right away and hammer the server */
*last_secpoll = now.tv_sec;
- SyncRes sr(now);
+ SyncRes resolver(now);
if (g_dnssecmode != DNSSECMode::Off) {
- sr.setDoDNSSEC(true);
- sr.setDNSSECValidationRequested(true);
+ resolver.setDoDNSSEC(true);
+ resolver.setDNSSECValidationRequested(true);
}
- sr.setId("SecPoll");
+ resolver.setId("SecPoll");
vector<DNSRecord> ret;
string version = "recursor-" + pkgv;
string qstring(version.substr(0, 63) + ".security-status." + ::arg()["security-poll-suffix"]);
- if (*qstring.rbegin() != '.')
+ if (*qstring.rbegin() != '.') {
qstring += '.';
+ }
boost::replace_all(qstring, "+", "_");
boost::replace_all(qstring, "~", "_");
vState state = vState::Indeterminate;
DNSName query(qstring);
- int res = sr.beginResolve(query, QType(QType::TXT), 1, ret);
+ int res = resolver.beginResolve(query, QType(QType::TXT), 1, ret);
- if (g_dnssecmode != DNSSECMode::Off && res) {
- state = sr.getValidationState();
+ if (g_dnssecmode != DNSSECMode::Off && res != 0) {
+ state = resolver.getValidationState();
}
auto vlog = log->withValues("version", Logging::Loggable(pkgv), "query", Logging::Loggable(query));
if (vStateIsBogus(state)) {
SLOG(g_log << Logger::Error << "Failed to retrieve security status update for '" + pkgv + "' on '" << query << "', DNSSEC validation result was Bogus!" << endl,
vlog->info(Logr::Error, "Failed to retrieve security status update", "validationResult", Logging::Loggable(vStateToString(state))));
- if (g_security_status == 1) // If we were OK, go to unknown
+ if (g_security_status == 1) { // If we were OK, go to unknown
g_security_status = 0;
+ }
return;
}
}
string security_message;
- int security_status = g_security_status;
+ int security_status = static_cast<int>(g_security_status);
try {
processSecPoll(res, ret, security_status, security_message);
return;
}
- g_security_message = security_message;
+ g_security_message = std::move(security_message);
auto rlog = vlog->withValues("securitymessage", Logging::Loggable(g_security_message), "status", Logging::Loggable(security_status));
if (g_security_status != 1 && security_status == 1) {
--- /dev/null
+/Makefile
+/Makefile.in
+/cxxsettings-generated.cc
+/settings-old-generated.rst
--- /dev/null
+# It's a bit dirty that this Makefile also generates a file inside rust/src (lib.rs).
+
+EXTRA_DIST = \
+ cxxsettings-generated.cc \
+ cxxsettings-private.hh \
+ cxxsettings.hh \
+ docs-new-preamble-in.rst \
+ docs-old-preamble-in.rst \
+ generate.py \
+ rust-bridge-in.rs \
+ rust-preamble-in.rs \
+ table.py \
+ rust/src/lib.rs
+
+all: cxxsettings-generated.cc rust/src/lib.rs
+
+BUILT_SOURCES=cxxsettings-generated.cc rust/src/lib.rs
+
+# We need to clean in the Rust dir, as in some cases the Serde/CXX derive/generate code does not get
+# re-run by cargo after rust/src/lib.rs changed because of a generate.py run. In that case we end up
+# with an rust/src/lib.rs.h that does not contain e.g. field name or field type changes.
+#
+# Use patterns to avoid having two instances of generate run simultaneously, a well-known hack for GNU make
+cxxsettings-generated%cc rust/src/lib%rs: table.py generate.py rust-preamble-in.rs rust-bridge-in.rs docs-old-preamble-in.rst docs-new-preamble-in.rst
+ $(MAKE) -C rust clean
+ (cd ${srcdir} && $(PYTHON) generate.py)
+
+clean-local:
+ rm -f cxxsettings-generated.cc rust/src/lib.rs
+
--- /dev/null
+SETTINGS CODE
+=============
+This directory contains the code to generate both old-style as new style (YAML) settings code.
+
+Inside this directory, there is a `rust` subdirectory that contains the Rust code and build files.
+The Rust code uses CXX for bridging between C++ and Rust.
+At the moment of writing, we only call Rust code from C++ and not vice versa.
+
+Additionally, the Rust Serde crate (and specifically Serde-YAML) is used to generatec code to handle YAML.
+
+The entry point for code generation is `generate.py`, which uses `table.py` to produce C++, Rust and .rst files.
+See `generate.sh` for some details about the generation process.
+This directory also contains a couple of `*-in.*` files which are included into files generated by the generation process.
+
+From the C++ point of view, several namespaces are defined:
+
+* `rust`: This namespace contains the classes defined by CXX.
+Note that it is sometimes needed to explicitly name it `::rust`, as the `pdns` namespace also has a subnamespace called `rust`.
+* `pdns::rust::settings::rec`: The classes and functions generated by CXX, callable from C++.
+* `pdns::settings::rec`: The classes and functions of the settings code implemented in C++. This is mainly the code handling the conversion of old-style definitions to new-style (and vice versa).
+
+Internally, the old-style settings are still used by the recursor code.
+Rewriting the existing code to start using the new style settings directly would have made the PR introducing the new-style settings even bigger.
+Therefore, we chose to keep the code *using* settings the same.
+If a new-style settings YAML file is encountered, it will be parsed, validated and the resulting new-style settings will be used to set the old-style settings to the values found in the YAML file.
+In the future, when old-style settings do not need to be supported any longer, the recursor itself can start to use the new style settings directly.
+
+A `rec_control show-yaml [file]` command has been added to show the conversion of old-style settings to the new-style YAML.
+
+This directory
+--------------
+* `cxxsettings-generated.cc`: generated code that implements the C++ part of the settings code.
+* `cxxsettings-private.hh`: private interface used by C++ settings code internally.
+* `cxxsettings.hh`: public interface of C++ code to be used by pdns_recursor and rec_control.
+* `cxxsupport.cc`: hand written C++ code.
+* `docs-new-preamble-in.rst`: non-generated part of new settings docs.
+* `docs-old-preamble-in.rst`: non-generated part of old settings docs.
+* `generate.py`: the Python script to produce C++, Rust and .rst files based on `table.py`.
+* `rust`: the directory containing rust code.
+* `rust-bridge-in.rs`: file included in the generated Rust code, placed inside the CXX bridge module.
+* `rust-preamble-in.rs`: file included in the generated Rust code as a preamble.
+* `table.py`: the definitions of all settings.
+
+`rust` subdirectory
+-------------------
+* `Cargo.toml`: The definition of the Rust `settings` crate, including its dependencies.
+* `build.rs`: `The custom build file used by CXX, see CXX docs.
+* `cxx.h`: The generic types used by CXX generated code.
+* `lib.rs.h`: The project specific C++ types generated by CXX.
+* `libsettings.a`: The actual static library procuced by this crate.
+* `src`: The actual rust code, `lib.rs` is generated, `bridge.rs` and `helpers.rs` are maintained manually.
+* `target`: The `cargo` maintained Rust build directory.
+
+The YAML settings are stored in a struct called (on the C++ side) `pdns::rust::settings::rec::Recursorsettings`.
+This struct has a substruct for each section defined.
+Each section has multiple typed variables.
+Below we will tour some parts of the (generated) code.
+An example settings file in YAML format:
+
+```yaml
+dnssec:
+ log_bogus: true
+incoming:
+ listen:
+ - 0.0.0.0:5301
+ - '[::]:5301'
+logging:
+ common_errors: true
+ disable_syslog: true
+ loglevel: 6
+recursor:
+ daemon: false
+ extended_resolution_errors: true
+ socket_dir: /tmp/rec
+ threads: 4
+webservice:
+ address: 127.0.0.1
+ allow_from:
+ - 0.0.0.0/0
+ api_key: secret
+ port: 8083
+ webserver: true
+```
+
+The generated code
+------------------
+C++, Rust and docmentation generating is done by the `generate.py` Python script using `table.py` as input.
+After that, the C++ to Rust bridge code is generated by CXX.
+Lets take a look at the `log_bogus` setting.
+The source of its definition is in `table.py`:
+
+```python
+ {
+ 'name' : 'log_bogus',
+ 'section' : 'dnssec',
+ 'oldname' : 'dnssec-log-bogus',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Log DNSSEC bogus validations',
+ 'doc' : '''
+Log every DNSSEC validation failure.
+**Note**: This is not logged per-query but every time records are validated as Bogus.
+ ''',
+ },
+```
+
+The old-style documention generated for this can be found in `../docs/settings.rst`:
+
+```
+.. _setting-dnssec-log-bogus:
+
+``dnssec-log-bogus``
+~~~~~~~~~~~~~~~~~~~~
+
+- Boolean
+- Default: no
+
+- YAML setting: :ref:`setting-yaml-dnssec.log_bogus`
+
+Log every DNSSEC validation failure.
+**Note**: This is not logged per-query but every time records are validated as Bogus.
+```
+
+The new-style documention generated for this can be found in `../docs/yamlsettings.rst`, its name includes the section and it lists a YAML default:
+
+```
+.. _setting-yaml-dnssec.log_bogus:
+
+``dnssec.log_bogus``
+^^^^^^^^^^^^^^^^^^^^
+
+- Boolean
+- Default: ``false``
+
+- Old style setting: :ref:`setting-dnssec-log-bogus`
+
+Log every DNSSEC validation failure.
+**Note**: This is not logged per-query but every time records are validated as Bogus.
+```
+
+The C++ code generated from this entry can be found in `cxxsettings-generated.cc`.
+The code to define the old-style settings, which is called by the recursor very early after startup and is a replacement for the hand-written code that defines all settings in the old recursor code.
+Using generated code and generated docs makes sure the docs and the actual implementation are consistent.
+Something which was not true for the old code in all cases.
+
+```cpp
+void pdns::settings::rec::defineOldStyleSettings()
+ ...
+ ::arg().setSwitch("dnssec-log-bogus", "Log DNSSEC bogus validations") = "no";
+ ...
+```
+
+There is also code generated to assign the current value of old-style `log_bogus` value to the right field in a `Recursorsettings` struct.
+This code is used to convert the currently active settings to a YAML file (`pdns_recursor --config=diff`):
+```cpp
+void pdns::settings::rec::oldStyleSettingsToBridgeStruct(Recursorsettings& settings)
+ ...
+ settings.dnssec.log_bogus = arg().mustDo("dnssec-log-bogus");
+ ...
+```
+
+Plus code to set the old-style setting, given a new-style struct:
+
+```cpp
+void pdns::settings::rec::bridgeStructToOldStyleSettings(const Recursorsettings& settings)
+ ...
+ ::arg().set("dnssec-log-bogus") = to_arg(settings.dnssec.log_bogus);
+ ...
+```
+
+Lastly, there is code to support converting a old-style settings to a new-style struct found in `pdns::settings::rec::oldKVToBridgeStruct()`.
+This code is used to convert old style settings files to YAML (used by `rec_control show-yaml`) and to generate a yaml file with all the defaults values (used by `pdns_recursor --config=default`).
+The functions implementing that are `pdns::settings::rec::oldStyleSettingsFileToYaml` and `std::string pdns::settings::rec::defaultsToYaml()`, found in `cxxsupport.cc`.
+
+The Rust code generated by `generate.py` can be found in `rust/src/lib.rs`.
+It contains these snippets that define the `log_bogus` field in the section struct `Dnssec` and the `dnssec` field in the `Recursorsettings` struct:
+
+```rust
+pub struct Dnssec {
+ ...
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ log_bogus: bool,
+ ...
+}
+pub struct Recursorsettings {
+ ...
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ dnssec: Dnssec,
+ ...
+}
+```
+The `rust/src/lib.rs` file also contains the generated code to handle Serde defaults.
+More details on this can be found in `generate.py`.
+
+The C++ version of the `Recursorsettings` struct and its substructs can be found in the CXX generated `rust/lib.rs.h` file:
+
+```cpp
+struct Recursorsettings final {
+ ...
+ ::pdns::rust::settings::rec::Dnssec dnssec;
+ ...
+ };
+ ...
+struct Dnssec final {
+ ...
+ bool log_bogus;
+ ...
+};
+```
+
+The Rust functions callable from C++ are listed in `rust-bridge-in.rs` which gets included into `rust/src/lib.rs` by `generate.py`
+An example is the function
+
+```rust
+ fn parse_yaml_string(str: &String) -> Result<Recursorsettings>;
+```
+
+Which parses YAML and produces a struct with all the settings.
+Settings that are not mentioned in the YAML string wil have their default value.
+
+`rust/lib.rs.h` contains the corresponding C++ prototype, defined in the `pdns::rust::settings::rec` namespace:
+
+```cpp
+::pdns::rust::settings::rec::Recursorsettings parse_yaml_string(::rust::String const &str);
+```
+
+The C++ function `pdns::settings::rec::readYamlSettings()` defined in `cxxsupport.cc` and called in `../rec-main.cc` calls the Rust function `pdns::rust::settings::rec::parse_yaml_string()` to do the actual YAML parsing.
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <cinttypes>
+#include <sstream>
+#include <variant>
+#include <vector>
+
+#include "rust/lib.rs.h"
+#include "misc.hh"
+
+using pdns::rust::settings::rec::AuthZone;
+using pdns::rust::settings::rec::ForwardZone;
+using pdns::rust::settings::rec::Recursorsettings;
+
+namespace pdns::settings::rec
+{
+::rust::Vec<::rust::String> getStrings(const std::string& name);
+::rust::Vec<ForwardZone> getForwardZones(const std::string& name);
+::rust::Vec<AuthZone> getAuthZones(const std::string& name);
+
+inline std::string to_arg(bool arg)
+{
+ return arg ? "yes" : "no";
+}
+
+inline std::string to_arg(uint64_t arg)
+{
+ return std::to_string(arg);
+}
+
+inline std::string to_arg(double arg)
+{
+ return std::to_string(arg);
+}
+
+inline std::string to_arg(const ::rust::String& str)
+{
+ return std::string(str);
+}
+
+std::string to_arg(const AuthZone& authzone);
+std::string to_arg(const ForwardZone& forwardzone);
+
+template <typename T>
+std::string to_arg(const ::rust::Vec<T>& vec)
+{
+ std::ostringstream str;
+ for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
+ if (iter != vec.begin()) {
+ str << ',';
+ }
+ str << to_arg(*iter);
+ }
+ return str.str();
+}
+
+inline void to_yaml(bool& field, const std::string& val)
+{
+ field = val != "no" && val != "off";
+}
+
+inline void to_yaml(::rust::String& field, const std::string& val)
+{
+ field = val;
+}
+
+inline void to_yaml(::rust::Vec<::rust::String>& field, const std::string& val)
+{
+ stringtok(field, val, ", ;");
+}
+
+void to_yaml(uint64_t& field, const std::string& val);
+void to_yaml(double& field, const std::string& val);
+void to_yaml(::rust::Vec<AuthZone>& field, const std::string& val);
+void to_yaml(::rust::Vec<ForwardZone>& field, const std::string& val, bool recurse = false);
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <string>
+#include "rust/cxx.h"
+#include "rust/lib.rs.h"
+#include "logging.hh"
+
+namespace pdns::settings::rec
+{
+enum YamlSettingsStatus : uint8_t
+{
+ OK,
+ CannotOpen,
+ PresentButFailed,
+};
+
+void defineOldStyleSettings();
+void oldStyleSettingsToBridgeStruct(pdns::rust::settings::rec::Recursorsettings& settings);
+void oldStyleForwardsFileToBridgeStruct(const std::string& filename, ::rust::Vec<pdns::rust::settings::rec::ForwardZone>& vec);
+void oldStyleAllowFileToBridgeStruct(const std::string& filename, ::rust::Vec<::rust::String>& vec);
+bool oldKVToBridgeStruct(string& key, const string& value, ::rust::String& section, ::rust::String& fieldname, ::rust::String& type_name, pdns::rust::settings::rec::Value& rustvalue);
+std::string oldStyleSettingsFileToYaml(const string& fname, bool mainFile);
+std::string defaultsToYaml();
+YamlSettingsStatus readYamlSettings(const std::string& configname, const std::string& includeDirOnCommandLine, rust::settings::rec::Recursorsettings& settings, std::string& msg, Logr::log_t log);
+void processAPIDir(const string& includeDirOnCommandLine, pdns::rust::settings::rec::Recursorsettings& settings, Logr::log_t log);
+void bridgeStructToOldStyleSettings(const pdns::rust::settings::rec::Recursorsettings& settings);
+void readYamlForwardZonesFile(const std::string& filename, ::rust::Vec<pdns::rust::settings::rec::ForwardZone>& vec, Logr::log_t log);
+void readYamlAllowFromFile(const std::string& filename, ::rust::Vec<::rust::String>& vec, Logr::log_t log);
+void readYamlAllowNotifyForFile(const std::string& filename, ::rust::Vec<::rust::String>& vec, Logr::log_t log);
+void setArgsForZoneRelatedSettings(pdns::rust::settings::rec::Recursorsettings& settings);
+void setArgsForACLRelatedSettings(pdns::rust::settings::rec::Recursorsettings& settings);
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <fstream>
+#include <regex>
+#include <unistd.h>
+#include <cstdio>
+#include <libgen.h>
+
+#include "namespaces.hh"
+#include "arguments.hh"
+#include "misc.hh"
+#include "cxxsettings.hh"
+#include "cxxsettings-private.hh"
+#include "logger.hh"
+#include "logging.hh"
+
+::rust::Vec<::rust::String> pdns::settings::rec::getStrings(const std::string& name)
+{
+ ::rust::Vec<::rust::String> vec;
+ to_yaml(vec, arg()[name]);
+ return vec;
+}
+
+::rust::Vec<ForwardZone> pdns::settings::rec::getForwardZones(const string& name)
+{
+ ::rust::Vec<ForwardZone> vec;
+ const auto recurse = name == "forward-zones-recurse";
+ to_yaml(vec, arg()[name], recurse);
+ return vec;
+}
+
+::rust::Vec<AuthZone> pdns::settings::rec::getAuthZones(const string& name)
+{
+ ::rust::Vec<AuthZone> vec;
+ to_yaml(vec, arg()[name]);
+ return vec;
+}
+
+void pdns::settings::rec::oldStyleForwardsFileToBridgeStruct(const std::string& file, ::rust::Vec<ForwardZone>& vec)
+{
+ auto filePtr = pdns::UniqueFilePtr(fopen(file.c_str(), "r"));
+ if (!filePtr) {
+ throw PDNSException("Error opening forward-zones-file '" + file + "': " + stringerror());
+ }
+
+ string line;
+ int linenum = 0;
+ while (linenum++, stringfgets(filePtr.get(), line)) {
+ boost::trim(line);
+ if (line.length() == 0 || line.at(0) == '#') { // Comment line, skip to the next line
+ continue;
+ }
+ auto [domain, instructions] = splitField(line, '=');
+ instructions = splitField(instructions, '#').first; // Remove EOL comments
+ boost::trim(domain);
+ boost::trim(instructions);
+ if (domain.empty() || instructions.empty()) {
+ throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + file);
+ }
+ bool allowNotify = false;
+ bool recurse = false;
+ for (; !domain.empty(); domain.erase(0, 1)) {
+ switch (domain[0]) {
+ case '+':
+ recurse = true;
+ continue;
+ case '^':
+ allowNotify = true;
+ continue;
+ }
+ break;
+ }
+ if (domain.empty()) {
+ throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + file);
+ }
+ ::rust::Vec<::rust::String> addresses;
+ stringtok(addresses, instructions, ",; ");
+ ForwardZone forwardzone{domain, std::move(addresses), recurse, allowNotify};
+ vec.push_back(std::move(forwardzone));
+ }
+}
+
+void pdns::settings::rec::oldStyleAllowFileToBridgeStruct(const std::string& file, ::rust::Vec<::rust::String>& vec)
+{
+ string line;
+ ifstream ifs(file);
+ if (!ifs) {
+ int err = errno;
+ throw runtime_error("Could not open '" + file + "': " + stringerror(err));
+ }
+
+ while (getline(ifs, line)) {
+ auto pos = line.find('#');
+ if (pos != string::npos) {
+ line.resize(pos);
+ }
+ boost::trim(line);
+ if (line.empty()) {
+ continue;
+ }
+ vec.emplace_back(line);
+ }
+}
+
+static void mergeYamlSubFile(const std::string& configname, Recursorsettings& settings, bool allowabsent, Logr::log_t log)
+{
+ auto file = ifstream(configname);
+ if (!file.is_open()) {
+ if (allowabsent) {
+ return;
+ }
+ throw runtime_error("Cannot open " + configname);
+ }
+ SLOG(g_log << Logger::Notice << "Processing YAML settings from " << configname << endl,
+ log->info(Logr::Notice, "Processing YAML settings", "path", Logging::Loggable(configname)));
+ auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+ pdns::rust::settings::rec::merge(settings, data);
+}
+
+static void possiblyConvertACLFile(const string& includeDir, const string& apiDir, const std::string& filename, Logr::log_t log)
+{
+ auto path = includeDir;
+ path.append("/").append(filename).append(".conf");
+ auto file = ifstream(path);
+ if (!file.is_open()) {
+ // Not an error, file just is not there
+ return;
+ }
+ rust::vec<rust::string> result;
+ std::string line;
+ while (getline(file, line)) {
+ auto pos = line.find('#');
+ if (pos != string::npos) {
+ line.resize(pos);
+ }
+ boost::trim(line);
+ if (line.empty()) {
+ continue;
+ }
+ auto plusis = line.find("+=");
+ if (plusis != string::npos) {
+ auto val = line.substr(plusis + 2);
+ boost::trim(val);
+ result.emplace_back(val);
+ }
+ }
+
+ rust::string key = "allow_from";
+ rust::string filekey = "allow_from_file";
+ if (filename == "allow-notify-from") {
+ key = "allow_notify_from";
+ filekey = "allow_notify_from_file";
+ }
+ const auto yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming(key, filekey, result);
+
+ string yamlfilename = apiDir;
+ yamlfilename.append("/").append(filename).append(".yml");
+ string tmpfilename = yamlfilename + ".tmp";
+ ofstream ofconf(tmpfilename);
+ if (!ofconf) {
+ int err = errno;
+ log->error(Logr::Error, err, "Cannot open for file for writing YAML", "to", Logging::Loggable(tmpfilename));
+ throw runtime_error("YAML Conversion");
+ }
+ ofconf << "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
+ ofconf << yaml << endl;
+ ofconf.close();
+ if (ofconf.bad()) {
+ log->error(Logr::Error, "Error writing YAML", "to", Logging::Loggable(tmpfilename));
+ unlink(tmpfilename.c_str());
+ throw runtime_error("YAML Conversion");
+ }
+ if (rename(path.c_str(), (path + ".converted").c_str()) != 0) {
+ int err = errno;
+ log->error(Logr::Error, err, "Rename failed", "file", Logging::Loggable(path), "to", Logging::Loggable(path + ".converted"));
+ unlink(tmpfilename.c_str());
+ throw runtime_error("YAML Conversion");
+ }
+
+ if (rename(tmpfilename.c_str(), yamlfilename.c_str()) != 0) {
+ int err = errno;
+ log->error(Logr::Error, err, "Rename failed", "file", Logging::Loggable(tmpfilename), "to", Logging::Loggable(yamlfilename));
+ if (rename((path + ".converted").c_str(), path.c_str()) != 0) {
+ err = errno;
+ log->error(Logr::Error, err, "Rename failed", "file", Logging::Loggable(path + ".converted"), "to", Logging::Loggable(path));
+ }
+ throw runtime_error("YAML Conversion");
+ }
+ log->info(Logr::Notice, "Converted to YAML", "file", Logging::Loggable(path), "to", Logging::Loggable(yamlfilename));
+}
+
+static void fileCopy(const string& src, const string& dst, Logr::log_t log)
+{
+ ifstream ifconf(src);
+ if (!ifconf) {
+ log->info(Logr::Error, "Cannot open for file for reading", "file", Logging::Loggable(src));
+ throw runtime_error("YAML Conversion");
+ }
+ ofstream ofconf(dst);
+ if (!ofconf) {
+ log->info(Logr::Error, "Cannot open for file for writing YAML", "to", Logging::Loggable(dst));
+ throw runtime_error("YAML Conversion");
+ }
+ for (;;) {
+ auto character = ifconf.get();
+ if (ifconf.eof()) {
+ break;
+ }
+ if (ifconf.bad()) {
+ int err = errno;
+ log->error(Logr::Error, err, "Error reading", "to", Logging::Loggable(src));
+ throw runtime_error("YAML Conversion");
+ }
+ ofconf.put(static_cast<char>(character));
+ if (ofconf.bad()) {
+ int err = errno;
+ log->error(Logr::Error, err, "Error writing YAML", "to", Logging::Loggable(dst));
+ throw runtime_error("YAML Conversion");
+ }
+ }
+ ifconf.close();
+ ofconf.close();
+ if (ofconf.bad()) {
+ log->error(Logr::Error, "Error writing YAML", "to", Logging::Loggable(dst));
+ throw runtime_error("YAML Conversion");
+ }
+}
+
+static void possiblyConvertForwardsandAuths(const std::string& includeDir, const std::string& apiDir, Logr::log_t log)
+{
+ std::vector<std::string> forwAndAuthFiles{};
+ ::arg().gatherIncludes(includeDir, "..conf", forwAndAuthFiles);
+ pdns::rust::settings::rec::Recursorsettings settings{};
+ for (const auto& file : forwAndAuthFiles) {
+ auto yaml = pdns::settings::rec::oldStyleSettingsFileToYaml(file, false);
+ pdns::rust::settings::rec::merge(settings, yaml);
+ }
+ const string yamlAPiZonesFile = apiDir + "/apizones";
+
+ for (auto& zone : settings.recursor.auth_zones) {
+ const std::string origName(zone.file);
+ std::string newName(zone.file);
+ newName.replace(0, includeDir.length(), apiDir);
+ log->info(Logr::Notice, "Copying auth zone file", "file", Logging::Loggable(origName), "to", Logging::Loggable(newName));
+ fileCopy(origName, newName, log);
+ zone.file = ::rust::String(newName);
+ api_add_auth_zone(yamlAPiZonesFile, zone);
+ }
+ api_add_forward_zones(yamlAPiZonesFile, settings.recursor.forward_zones);
+ api_add_forward_zones(yamlAPiZonesFile, settings.recursor.forward_zones_recurse);
+ for (const auto& file : forwAndAuthFiles) {
+ if (rename(file.c_str(), (file + ".converted").c_str()) != 0) {
+ int err = errno;
+ log->error(Logr::Error, err, "Rename failed", "file", Logging::Loggable(file), "to", Logging::Loggable(file + ".converted"));
+ }
+ }
+}
+
+void pdns::settings::rec::processAPIDir(const string& includeDirOnCommandLine, pdns::rust::settings::rec::Recursorsettings& settings, Logr::log_t log)
+{
+ auto apiDir = std::string(settings.webservice.api_dir);
+ if (apiDir.empty()) {
+ return;
+ }
+ auto includeDir = std::string(settings.recursor.include_dir);
+ if (!includeDirOnCommandLine.empty()) {
+ includeDir = includeDirOnCommandLine;
+ }
+ if (includeDir == apiDir) {
+ throw runtime_error("Active YAML settings do not allow include_dir to be equal to api_dir");
+ }
+ const std::array<std::string, 2> aclFiles = {
+ "allow-from",
+ "allow-notify-from"};
+ for (const auto& file : aclFiles) {
+ possiblyConvertACLFile(includeDir, apiDir, file, log);
+ auto path = apiDir;
+ path.append("/").append(file).append(".yml");
+ mergeYamlSubFile(path, settings, true, log);
+ }
+ possiblyConvertForwardsandAuths(includeDir, apiDir, log);
+}
+
+pdns::settings::rec::YamlSettingsStatus pdns::settings::rec::readYamlSettings(const std::string& configname, const std::string& includeDirOnCommandLine, Recursorsettings& settings, std::string& msg, Logr::log_t log)
+{
+ auto file = ifstream(configname);
+ if (!file.is_open()) {
+ msg = stringerror(errno);
+ return YamlSettingsStatus::CannotOpen;
+ }
+ SLOG(g_log << Logger::Notice << "Processing main YAML settings from " << configname << endl,
+ log->info(Logr::Notice, "Processing main YAML settings", "path", Logging::Loggable(configname)));
+ try {
+ auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+ auto yamlstruct = pdns::rust::settings::rec::parse_yaml_string(data);
+ std::vector<std::string> yamlFiles;
+ ::arg().gatherIncludes(!includeDirOnCommandLine.empty() ? includeDirOnCommandLine : string(yamlstruct.recursor.include_dir),
+ ".yml", yamlFiles);
+ for (const auto& yamlfile : yamlFiles) {
+ mergeYamlSubFile(yamlfile, yamlstruct, false, log);
+ }
+ yamlstruct.validate();
+ settings = std::move(yamlstruct);
+ return YamlSettingsStatus::OK;
+ }
+ catch (const ::rust::Error& ex) {
+ msg = ex.what();
+ return YamlSettingsStatus::PresentButFailed;
+ }
+ catch (const std::exception& ex) {
+ msg = ex.what();
+ return YamlSettingsStatus::PresentButFailed;
+ }
+ catch (...) {
+ msg = "Unexpected exception processing YAML";
+ return YamlSettingsStatus::PresentButFailed;
+ }
+}
+
+void pdns::settings::rec::readYamlAllowFromFile(const std::string& filename, ::rust::Vec<::rust::String>& vec, Logr::log_t log)
+{
+ SLOG(g_log << Logger::Notice << "Processing allow YAML settings from " << filename << endl,
+ log->info(Logr::Notice, "Processing allow YAML settings", "path", Logging::Loggable(filename)));
+ auto file = ifstream(filename);
+ if (!file.is_open()) {
+ throw runtime_error(stringerror(errno));
+ }
+ auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+ auto yamlvec = pdns::rust::settings::rec::parse_yaml_string_to_allow_from(data);
+ pdns::rust::settings::rec::validate_allow_from(filename, yamlvec);
+ vec = std::move(yamlvec);
+}
+
+void pdns::settings::rec::readYamlForwardZonesFile(const std::string& filename, ::rust::Vec<pdns::rust::settings::rec::ForwardZone>& vec, Logr::log_t log)
+{
+ SLOG(g_log << Logger::Notice << "Processing forwarding YAML settings from " << filename << endl,
+ log->info(Logr::Notice, "Processing forwarding YAML settings", "path", Logging::Loggable(filename)));
+ auto file = ifstream(filename);
+ if (!file.is_open()) {
+ throw runtime_error(stringerror(errno));
+ }
+ auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+ auto yamlvec = pdns::rust::settings::rec::parse_yaml_string_to_forward_zones(data);
+ pdns::rust::settings::rec::validate_forward_zones("forward_zones", yamlvec);
+ vec = std::move(yamlvec);
+}
+
+void pdns::settings::rec::readYamlAllowNotifyForFile(const std::string& filename, ::rust::Vec<::rust::String>& vec, Logr::log_t log)
+{
+ SLOG(g_log << Logger::Notice << "Processing allow-notify-for YAML settings from " << filename << endl,
+ log->info(Logr::Notice, "Processing allow-notify-for YAML settings", "path", Logging::Loggable(filename)));
+ auto file = ifstream(filename);
+ if (!file.is_open()) {
+ throw runtime_error(stringerror(errno));
+ }
+ auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+ auto yamlvec = pdns::rust::settings::rec::parse_yaml_string_to_allow_notify_for(data);
+ pdns::rust::settings::rec::validate_allow_notify_for("allow-notify-for", yamlvec);
+ vec = std::move(yamlvec);
+}
+
+std::string pdns::settings::rec::to_arg(const AuthZone& authzone)
+{
+ std::ostringstream str;
+ str << to_arg(authzone.zone) << '=' << to_arg(authzone.file);
+ return str.str();
+}
+
+std::string pdns::settings::rec::to_arg(const ForwardZone& forwardzone)
+{
+ std::ostringstream str;
+ str << to_arg(forwardzone.zone) << '=';
+ const auto& vec = forwardzone.forwarders;
+ for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
+ if (iter != vec.begin()) {
+ str << ';';
+ }
+ str << to_arg(*iter);
+ }
+ return str.str();
+}
+
+void pdns::settings::rec::setArgsForZoneRelatedSettings(Recursorsettings& settings)
+{
+ ::arg().set("forward-zones") = to_arg(settings.recursor.forward_zones);
+ ::arg().set("forward-zones-file") = to_arg(settings.recursor.forward_zones_file);
+ ::arg().set("forward-zones-recurse") = to_arg(settings.recursor.forward_zones_recurse);
+ ::arg().set("auth-zones") = to_arg(settings.recursor.auth_zones);
+ ::arg().set("allow-notify-for") = to_arg(settings.incoming.allow_notify_for);
+ ::arg().set("allow-notify-for-file") = to_arg(settings.incoming.allow_notify_for_file);
+ ::arg().set("export-etc-hosts") = to_arg(settings.recursor.export_etc_hosts);
+ ::arg().set("serve-rfc1918") = to_arg(settings.recursor.serve_rfc1918);
+}
+
+void pdns::settings::rec::setArgsForACLRelatedSettings(Recursorsettings& settings)
+{
+ ::arg().set("allow-from") = to_arg(settings.incoming.allow_from);
+ ::arg().set("allow-from-file") = to_arg(settings.incoming.allow_from_file);
+ ::arg().set("allow-notify-from") = to_arg(settings.incoming.allow_notify_from);
+ ::arg().set("allow-notify-from-file") = to_arg(settings.incoming.allow_notify_from_file);
+}
+
+void pdns::settings::rec::to_yaml(uint64_t& field, const std::string& val)
+{
+ if (val.empty()) {
+ field = 0;
+ return;
+ }
+
+ checked_stoi_into(field, val, nullptr, 0);
+}
+
+void pdns::settings::rec::to_yaml(double& field, const std::string& val)
+{
+ if (val.empty()) {
+ field = 0.0;
+ return;
+ }
+
+ const auto* cptr_orig = val.c_str();
+ char* cptr_ret = nullptr;
+ auto retval = strtod(cptr_orig, &cptr_ret);
+
+ field = retval;
+}
+
+void pdns::settings::rec::to_yaml(::rust::Vec<AuthZone>& field, const std::string& val)
+{
+ vector<string> zones;
+ stringtok(zones, val, " ,\t\n\r");
+ for (const auto& zone : zones) {
+ auto headers = splitField(zone, '=');
+ boost::trim(headers.first);
+ boost::trim(headers.second);
+ AuthZone authzone{headers.first, headers.second};
+ field.push_back(std::move(authzone));
+ }
+}
+
+void pdns::settings::rec::to_yaml(::rust::Vec<ForwardZone>& field, const std::string& val, bool recurse)
+{
+ vector<string> zones;
+ stringtok(zones, val, " ,\t\n\r");
+ for (const auto& zone : zones) {
+ auto headers = splitField(zone, '=');
+ boost::trim(headers.first);
+ boost::trim(headers.second);
+ ::rust::Vec<::rust::String> addresses;
+ stringtok(addresses, headers.second, " ;");
+ ForwardZone forwardzone{headers.first, std::move(addresses), recurse, false};
+ field.push_back(std::move(forwardzone));
+ }
+}
+
+using FieldMap = std::map<pair<::rust::String, ::rust::String>, pdns::rust::settings::rec::OldStyle>;
+
+static bool simpleRustType(const ::rust::String& rname)
+{
+ return rname == "bool" || rname == "u64" || rname == "f64" || rname == "String";
+}
+
+static void processLine(const std::string& arg, FieldMap& map, bool mainFile)
+{
+ string var;
+ string val;
+ string::size_type pos = 0;
+ bool incremental = false;
+
+ if (arg.find("--") == 0 && (pos = arg.find("+=")) != string::npos) // this is a --port+=25 case
+ {
+ var = arg.substr(2, pos - 2);
+ val = arg.substr(pos + 2);
+ incremental = true;
+ }
+ else if (arg.find("--") == 0 && (pos = arg.find('=')) != string::npos) // this is a --port=25 case
+ {
+ var = arg.substr(2, pos - 2);
+ val = arg.substr(pos + 1);
+ }
+ else if (arg.find("--") == 0 && (arg.find('=') == string::npos)) // this is a --daemon case
+ {
+ var = arg.substr(2);
+ val = "";
+ }
+ else if (arg[0] == '-' && arg.length() > 1) {
+ var = arg.substr(1);
+ val = "";
+ }
+ boost::trim(var);
+ if (var.empty()) {
+ return;
+ }
+ pos = val.find_first_not_of(" \t"); // strip leading whitespace
+ if (pos != 0 && pos != string::npos) {
+ val = val.substr(pos);
+ }
+
+ ::rust::String section;
+ ::rust::String fieldname;
+ ::rust::String type_name;
+ pdns::rust::settings::rec::Value rustvalue = {false, 0, 0.0, "", {}, {}, {}};
+ if (pdns::settings::rec::oldKVToBridgeStruct(var, val, section, fieldname, type_name, rustvalue)) {
+ auto overriding = !mainFile && !incremental && !simpleRustType(type_name);
+ auto [existing, inserted] = map.emplace(std::pair{std::pair{section, fieldname}, pdns::rust::settings::rec::OldStyle{section, fieldname, var, std::move(type_name), rustvalue, overriding}});
+ if (!inserted) {
+ // Simple values overwrite always
+ existing->second.value.bool_val = rustvalue.bool_val;
+ existing->second.value.u64_val = rustvalue.u64_val;
+ existing->second.value.f64_val = rustvalue.f64_val;
+ existing->second.value.string_val = rustvalue.string_val;
+ // List values only if = was used
+ if (!incremental) {
+ existing->second.value.vec_string_val.clear();
+ existing->second.value.vec_forwardzone_val.clear();
+ existing->second.value.vec_authzone_val.clear();
+ }
+ for (const auto& add : rustvalue.vec_string_val) {
+ existing->second.value.vec_string_val.emplace_back(add);
+ }
+ for (const auto& add : rustvalue.vec_forwardzone_val) {
+ existing->second.value.vec_forwardzone_val.emplace_back(add);
+ }
+ for (const auto& add : rustvalue.vec_authzone_val) {
+ existing->second.value.vec_authzone_val.emplace_back(add);
+ }
+ }
+ }
+}
+
+std::string pdns::settings::rec::oldStyleSettingsFileToYaml(const string& fname, bool mainFile)
+{
+ string line;
+ string pline;
+
+ std::ifstream configFileStream(fname);
+ if (!configFileStream) {
+ int err = errno;
+ throw runtime_error("Cannot read " + fname + ": " + stringerror(err));
+ }
+
+ // Read the old-style config file and produce a map with the data, taking into account the
+ // difference between = and +=
+ FieldMap map;
+ while (getline(configFileStream, pline)) {
+ boost::trim_right(pline);
+
+ if (!pline.empty() && pline[pline.size() - 1] == '\\') {
+ line += pline.substr(0, pline.length() - 1);
+ continue;
+ }
+
+ line += pline;
+
+ // strip everything after a #
+ string::size_type pos = line.find('#');
+ if (pos != string::npos) {
+ // make sure it's either first char or has whitespace before
+ // fixes issue #354
+ if (pos == 0 || (std::isspace(line[pos - 1]) != 0)) {
+ line = line.substr(0, pos);
+ }
+ }
+
+ // strip trailing spaces
+ boost::trim_right(line);
+
+ // strip leading spaces
+ pos = line.find_first_not_of(" \t\r\n");
+ if (pos != string::npos) {
+ line = line.substr(pos);
+ }
+
+ processLine("--" + line, map, mainFile);
+ line = "";
+ }
+
+ // Convert the map to a vector, as CXX does not have any dictionary like support.
+ ::rust::Vec<pdns::rust::settings::rec::OldStyle> vec;
+ vec.reserve(map.size());
+ for (const auto& entry : map) {
+ vec.emplace_back(entry.second);
+ }
+ return std::string(pdns::rust::settings::rec::map_to_yaml_string(vec));
+}
+
+std::string pdns::settings::rec::defaultsToYaml()
+{
+ // In this function we make use of the fact that we know a little about the formatting of YAML by
+ // serde_yaml. ATM there's no way around that, as serde_yaml itself does not have any support for
+ // comments (other than stripping them while parsing). So produced YAML cannot have any comments.
+
+ // First we construct a map of (section, name) to info about the field (oldname, type, value etc)
+ FieldMap map;
+ for (const auto& var : arg().list()) {
+ if (const auto newname = ArgvMap::isDeprecated(var); !newname.empty()) {
+ continue;
+ }
+ ::rust::String section;
+ ::rust::String fieldname;
+ ::rust::String type_name;
+ pdns::rust::settings::rec::Value rustvalue{false, 0, 0.0, "", {}, {}, {}};
+ string name = var;
+ string val = arg().getDefault(var);
+ if (pdns::settings::rec::oldKVToBridgeStruct(name, val, section, fieldname, type_name, rustvalue)) {
+ map.emplace(std::pair{std::pair{section, fieldname}, pdns::rust::settings::rec::OldStyle{section, fieldname, name, std::move(type_name), std::move(rustvalue), false}});
+ }
+ }
+ // Convert the map to a vector, as CXX does not have any dictionary like support.
+ ::rust::Vec<pdns::rust::settings::rec::OldStyle> vec;
+ vec.reserve(map.size());
+ for (const auto& entry : map) {
+ vec.emplace_back(entry.second);
+ }
+ const auto defs = std::string(pdns::rust::settings::rec::map_to_yaml_string(vec));
+
+ // We now have a YAML string, with all sections and all default values. Do a litle bit of parsing
+ // to insert the help text lines.
+ std::vector<std::string> lines;
+ stringtok(lines, defs, "\n");
+ std::string res;
+
+ // These two RE's know about the formatting generated by serde_yaml
+ std::regex sectionRE("^(\\w+):");
+ std::regex fieldRE("^ (\\w+):");
+ std::string section;
+ std::string field;
+
+ for (const auto& line : lines) {
+ bool withHelp = false;
+ bool sectionChange = false;
+ std::smatch matches;
+ std::regex_search(line, matches, sectionRE);
+ if (!matches.empty()) {
+ section = matches[1];
+ sectionChange = true;
+ }
+ std::regex_search(line, matches, fieldRE);
+ if (!matches.empty()) {
+ field = matches[1];
+ withHelp = true;
+ }
+ if (withHelp) {
+ std::string oldname;
+ if (auto iter = map.find(make_pair(section, field)); iter != map.end()) {
+ oldname = std::string(iter->second.old_name);
+ }
+ res += "##### ";
+ res += arg().getHelp(oldname);
+ res += '\n';
+ }
+ if (sectionChange) {
+ res += "\n######### SECTION ";
+ res += section;
+ res += " #########\n";
+ res += line;
+ }
+ else {
+ res += "# ";
+ res += line;
+ }
+ res += '\n';
+ }
+ return res;
+}
--- /dev/null
+PowerDNS Recursor New Style (YAML) Settings
+===========================================
+
+Each setting can appear on the command line, prefixed by ``--`` and using the old style name, or in configuration files.
+Settings on the command line are processed after the file-based settings are processed.
+
+.. note::
+ Starting with version 5.0.0, :program:`Recursor` supports a new YAML syntax for configuration files
+ as described here.
+ If both ``recursor.conf`` and ``recursor.yml`` files are found in the configuration directory the YAML file is used.
+ A configuration using the old style syntax can be converted to a YAML configuration using the instructions in :doc:`appendices/yamlconversion`.
+
+ Release 5.0.0 will install a default old-style ``recursor.conf`` files only.
+
+ With the release of version 5.1.0, packages will stop installing a default ``recursor.conf`` and start installing a default ``recursor.yml`` file if no existing ``recursor.conf`` is present.
+ In the absense of a ``recursor.yml`` file, an existing ``recursor.conf`` file will be accepted and used.
+
+ With the release of 5.2.0, the default will be to expect a ``recursor.yml`` file and reading of ``recursor.conf`` files will have to be enabled specifically by providing a command line option.
+
+ In a future release support for the "old-style" ``recursor.conf`` settings file will be dropped.
+
+
+YAML settings file
+------------------
+Please refer to e.g. `<https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html>`_
+for a description of YAML syntax.
+
+A :program:`Recursor` configuration file has several sections. For example, ``incoming`` for
+settings related to receiving queries and ``dnssec`` for settings related to DNSSEC processing.
+
+An example :program:`Recursor` YAML configuration file looks like:
+
+.. code-block:: yaml
+
+ dnssec:
+ log_bogus: true
+ incoming:
+ listen:
+ - 0.0.0.0:5301
+ - '[::]:5301'
+ recursor:
+ extended_resolution_errors: true
+ forward_zones:
+ - zone: example.com
+ forwarders:
+ - 127.0.0.1:5301
+ outgoing:
+ source_address:
+ - 0.0.0.0
+ - '::'
+ logging:
+ loglevel: 6
+
+Take care when listing IPv6 addresses, as characters used for these are special to YAML.
+If in doubt, quote any string containing ``:``, ``!``, ``[`` or ``]`` and use (online) tools to check your YAML syntax.
+Specify an empty sequence using ``[]``.
+
+The main setting file is called ``recursor.yml`` and will be processed first.
+This settings file might refer to other files via the `recursor.include_dir`_ setting.
+The next section will describe how settings specified in multiple files are merged.
+
+Merging multiple setting files
+------------------------------
+If `recursor.include_dir`_ is set, all ``.yml`` files in it will be processed in alphabetical order, modifying the settings processed so far.
+
+For simple values like an boolean or number setting, a value in the processed file will overwrite an existing setting.
+
+For values of type sequence, the new value will *replace* the existing value if the existing value is equal to the ``default`` or if the new value is marked with the ``!override`` tag.
+Otherwise, the existing value will be *extended* with the new value by appending the new sequence to the existing.
+
+For example, with the above example ``recursor.yml`` and an include directory containing a file ``extra.yml``:
+
+.. code-block:: yaml
+
+ dnssec:
+ log_bogus: false
+ recursor:
+ forward_zones:
+ - zone: example.net
+ forwarders:
+ - '::1'
+ outgoing:
+ source_address: !override
+ - 0.0.0.0
+ dont_query: []
+
+After merging, ``dnssec.log_bogus`` will be ``false``, the sequence of ``recursor.forward_zones`` will contain 2 zones and the ``outgoing`` addresses used will contain one entry, as the ``extra.yml`` entry has overwritten the existing one.
+
+``outgoing.dont-query`` has a non-empty sequence as default value. The main ``recursor.yml`` did not set it, so before processing ``extra.yml`` had the default value.
+After processing ``extra.yml`` the value will be set to the empty sequence, as existing default values are overwritten by new values.
+
+.. warning::
+ The merging process does not process values deeper than the second level.
+ For example if the main ``recursor.yml`` specified a forward zone
+
+ .. code-block:: yaml
+
+ forward_zones:
+ - zone: example.net
+ forwarders:
+ - '::1'
+
+ and another settings file contains
+
+ .. code-block:: yaml
+
+ forward_zones:
+ - zone: example.net
+ forwarders:
+ - '::2'
+
+ The result will *not* be a a single forward with two IP addresses, but two entries for ``example.net``.
+ It depends on the specific setting how the sequence is processed and interpreted further.
+
+Socket Address
+^^^^^^^^^^^^^^
+A socket address is either an IP or and IP:port combination
+For example:
+
+.. code-block:: yaml
+
+ some_key: 127.0.0.1
+ another_key: '[::1]:8080'
+
+Subnet
+^^^^^^
+A subnet is a single IP address or an IP address followed by a slash and a prefix length.
+If no prefix length is specified, ``/32`` or ``/128`` is assumed, indicating a single IP address.
+Subnets can also be prefixed with a ``!``, specifying negation.
+This can be used to deny addresses from a previously allowed range.
+
+For example, ``alow-from`` takes a sequence of subnets:
+
+.. code-block:: yaml
+
+ allow_from:
+ - '2001:DB8::/32'
+ - 128.66.0.0/16
+ - '!128.66.1.2'
+
+In this case the address ``128.66.1.2`` is excluded from the addresses allowed access.
+
+Forward Zone
+^^^^^^^^^^^^
+A forward zone is defined as:
+
+.. code-block:: yaml
+
+ zone: zonename
+ forwarders:
+ - Socket Address
+ - ...
+ recurse: Boolean, default false
+ allow_notify: Boolean, default false
+
+An example of a ``forward_zones`` entry, which consists of a sequence of forward zone entries:
+
+.. code-block:: yaml
+
+ - zone: example1.com
+ forwarders:
+ - 127.0.0.1
+ - 127.0.0.1:5353
+ - '[::1]53'
+ - zone: example2.com
+ forwarders:
+ - '::1'
+ recurse: true
+ notify_allowed: true
+
+
+Auth Zone
+^^^^^^^^^
+A auth zone is defined as:
+
+.. code-block:: yaml
+
+ zone: name
+ file: filename
+
+An example of a ``auth_zones`` entry, consisting of a sequence of auth zones:
+
+.. code-block:: yaml
+
+ auth_zones:
+ - zone: example.com
+ file: zones/example.com.zone
+ - zone: example.net
+ file: zones/example.net.zone
+
+The YAML settings
+-----------------
+
--- /dev/null
+PowerDNS Recursor Settings
+==========================
+Each setting can appear on the command line, prefixed by ``--``, or in the configuration file.
+The command line overrides the configuration file.
+
+.. note::
+ Starting with version 5.0.0, :program:`Recursor` supports a new YAML syntax for configuration files.
+ A configuration using the old style syntax can be converted to a YAML configuration using the instructions in :doc:`appendices/yamlconversion`.
+ In a future release support for the "old-style" settings decribed here will be dropped.
+ See :doc:`yamlsettings` for details.
+
+.. note::
+ Settings marked as ``Boolean`` can either be set to an empty value, which means **on**, or to ``no`` or ``off`` which means **off**.
+ Anything else means **on**.
+
+ For example:
+
+ - ``serve-rfc1918`` on its own means: do serve those zones.
+ - ``serve-rfc1918 = off`` or ``serve-rfc1918 = no`` means: do not serve those zones.
+ - Anything else means: do serve those zones.
+
+You can use ``+=`` syntax to set some variables incrementally, but this
+requires you to have at least one non-incremental setting for the
+variable to act as base setting. This is mostly useful for
+:ref:`setting-include-dir` directive. An example::
+
+ forward-zones = foo.example.com=192.168.100.1;
+ forward-zones += bar.example.com=[1234::abcde]:5353;
+
+When a list of **Netmasks** is mentioned, a list of subnets can be specified.
+A subnet that is not followed by ``/`` will be interpreted as a ``/32`` or ``/128`` subnet (a single address), depending on address family.
+For most settings, it is possible to exclude ranges by prefixing an item with the negation character ``!``.
+For example::
+
+ allow-from = 2001:DB8::/32, 128.66.0.0/16, !128.66.1.2
+
+In this case the address ``128.66.1.2`` is excluded from the addresses allowed access.
+
+The Settings
+------------
--- /dev/null
+"""The Python script that takes table.py and generates C++, Rust ahd .rst code."""
+#
+# For C++ it generates cxxsettings-generated.cc containing support for old style
+# settings plus conversion from old style to new style.
+#
+# For Rust it generates rust/src/lib.rs, containing the structs with
+# CXX and Serde annotations and associated code. rust-preamble-in.rs is
+# included before the generated code and rus-bridge-in.rs inside the
+# bridge module in the generated lib.rs. Header files generated by CXX
+# (lib.rs.h and cxx.h) are copied from deep inside the generated code
+# hierarchy into the rust subdir as well.
+#
+# Two documentation files are generated: ../docs/settings.rst and
+# ../docs/yamlsettings.rst. Each of these files have a preamble as
+# well, describing the syntax and giving some examples.
+#
+# An example table.py entry:
+#
+# {
+# 'name' : "allow_from",
+# 'section' : "incoming",
+# 'oldname' : "allow-from",
+# 'type' : LType.ListSubnets,
+# 'default' : "127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16,
+# 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10",
+# 'help' : "If set, only allow these comma separated netmasks to recurse",
+# 'doc' : """
+# """
+# }
+#
+# The fields are:
+#
+# name: short name within section, also yaml key, must be unique within section and not a C++ or
+# Rust keyword
+# section: yaml section
+# oldname: name in old style settings, must be unique globally
+# type : logical type (leave the space before the : as pylint is buggy parsing lines with type
+# immediately followed by colon)x
+# default: default value, use 'true' and 'false' for Booleans
+# help: short help text
+# doc: the docs text will be put here
+# doc-rst: optional .rst annotations, typically used for ..version_added/changed::
+# doc-new: optional docs text specific to YAML format, e.g. not talking about comma separated
+# lists and giving YAML examples. (optional)
+# skip-yamL: optional if this key is present, the field wil be skipped in the new style settings.
+# Use for deprecated settings, the generated code will use deprecated map from arg()
+# to handle old names when converting .conf to .yml
+# versionadded: string or list of strings
+#
+# The above struct will generate in cxxsettings-generated.cc:
+#
+# ::arg().set("allow-from", "If set, only allow these comma separated netmasks to recurse") = \
+# "127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, ...";
+#
+# For Rust, it will generate a field `allow_from' within struct Incoming.
+# As CXX places restrictions on the Serde code (which means we cannot
+# use Serde extensions) we have to handle the default value and "skip if
+# default" functions ourselves.
+#
+# There are three cases:
+#
+# 1. Default is the same as the Rust default for the type
+# In that case, the field becomes:
+#
+# #[serde(default, skip_serializing_if = "crate::is_default")]
+# nameoffield: bool
+#
+# 2. Type is primitive and default value is different from the Rust default, e.g.:
+#
+# #[serde(default = "crate::U64::<2000>::value",
+# skip_serializing_if = "crate::U64::<2000>::is_equal")]
+# nameoffield: u64
+#
+# U64<constant> is implemented in rust/src/helpers.rs
+#
+# 3. Type is not primitive and has default value different from Rust default, e.g.:
+#
+# [serde(default = "crate::default_value_incoming_allow_from",
+# skip_serializing_if = "crate::default_value_equal_incoming_allow_from")]
+# allow_from: Vec<String>
+#
+# The two functions default_value_incoming_allow_from() and
+# default_value_equal_incoming_allow_from() are also generated.
+#
+from enum import Enum
+from enum import auto
+import os
+import re
+import sys
+
+class LType(Enum):
+ """The type we handle in table.py"""
+ Bool = auto()
+ Command = auto()
+ Double = auto()
+ ListAuthZones = auto()
+ ListSocketAddresses = auto()
+ ListStrings = auto()
+ ListSubnets = auto()
+ ListForwardZones = auto()
+ String = auto()
+ Uint64 = auto()
+
+def get_olddoc_typename(typ):
+ """Given a type from table.py, return the old-style type name"""
+ if typ == LType.Bool:
+ return 'Boolean'
+ if typ == LType.Uint64:
+ return 'Integer'
+ if typ == LType.Double:
+ return 'Double'
+ if typ == LType.String:
+ return 'String'
+ if typ == LType.ListSocketAddresses:
+ return 'Comma separated list or IPs of IP:port combinations'
+ if typ == LType.ListSubnets:
+ return 'Comma separated list of IP addresses or subnets, negation supported'
+ if typ == LType.ListStrings:
+ return 'Comma separated list of strings'
+ if typ == LType.ListForwardZones:
+ return 'Comma separated list of \'zonename=IP\' pairs'
+ if typ == LType.ListAuthZones:
+ return 'Comma separated list of \'zonename=filename\' pairs'
+ return 'Unknown' + str(typ)
+
+def get_newdoc_typename(typ):
+ """Given a type from table.py, return the new-style type name"""
+ if typ == LType.Bool:
+ return 'Boolean'
+ if typ == LType.Uint64:
+ return 'Integer'
+ if typ == LType.Double:
+ return 'Double'
+ if typ == LType.String:
+ return 'String'
+ if typ == LType.ListSocketAddresses:
+ return 'Sequence of `Socket Address`_ (IP or IP:port combinations)'
+ if typ == LType.ListSubnets:
+ return 'Sequence of `Subnet`_ (IP addresses or subnets, negation supported)'
+ if typ == LType.ListStrings:
+ return 'Sequence of strings'
+ if typ == LType.ListForwardZones:
+ return 'Sequence of `Forward Zone`_'
+ if typ == LType.ListAuthZones:
+ return 'Sequence of `Auth Zone`_'
+ return 'Unknown' + str(typ)
+
+def get_default_olddoc_value(typ, val):
+ """Given a type and a value from table.py return the old doc representation of the value"""
+ if typ == LType.Bool:
+ if val == 'false':
+ return 'no'
+ return 'yes'
+ if val == '':
+ return '(empty)'
+ return val
+
+def get_default_newdoc_value(typ, val):
+ """Given a type and a value from table.py return the new doc representation of the value"""
+ if typ in (LType.Bool, LType.Uint64, LType.Double):
+ return '``' + val + '``'
+ if typ == LType.String and val == '':
+ return '(empty)'
+ if typ == LType.String:
+ return '``' + val + '``'
+ parts = re.split('[ \t,]+', val)
+ if len(parts) > 0:
+ ret = ''
+ for part in parts:
+ if part == '':
+ continue
+ if ret != '':
+ ret += ', '
+ if ':' in part or '!' in part:
+ ret += "'" + part + "'"
+ else:
+ ret += part
+ else:
+ ret = ''
+ return '``[' + ret + ']``'
+
+def get_rust_type(typ):
+ """Determine which Rust type is used for a logical type"""
+ if typ == LType.Bool:
+ return 'bool'
+ if typ == LType.Uint64:
+ return 'u64'
+ if typ == LType.Double:
+ return 'f64'
+ if typ == LType.String:
+ return 'String'
+ if typ == LType.ListSocketAddresses:
+ return 'Vec<String>'
+ if typ == LType.ListSubnets:
+ return 'Vec<String>'
+ if typ == LType.ListStrings:
+ return 'Vec<String>'
+ if typ == LType.ListForwardZones:
+ return 'Vec<ForwardZone>'
+ if typ == LType.ListAuthZones:
+ return 'Vec<AuthZone>'
+ return 'Unknown' + str(typ)
+
+def quote(arg):
+ """Return a quoted string"""
+ return '"' + arg + '"'
+
+def isEnvVar(name):
+ return name in ('SYSCONFDIR', 'NODCACHEDIRNOD', 'NODCACHEDIRUDR')
+
+def gen_cxx_defineoldsettings(file, entries):
+ """Generate C++ code to declare old-style settings"""
+ file.write('void pdns::settings::rec::defineOldStyleSettings()\n{\n')
+ for entry in entries:
+ helptxt = quote(entry['help'])
+ oldname = quote(entry['oldname'])
+ if entry['type'] == LType.Bool:
+ if entry['default'] == "true":
+ cxxdef = "yes"
+ else:
+ cxxdef = "no"
+ cxxdef = quote(cxxdef)
+ file.write(f" ::arg().setSwitch({oldname}, {helptxt}) = {cxxdef};\n")
+ elif entry['type'] == LType.Command:
+ file.write(f" ::arg().setCmd({oldname}, {helptxt});\n")
+ else:
+ cxxdef = entry['default'] if isEnvVar(entry['default']) else quote(entry['default'])
+ file.write(f" ::arg().set({oldname}, {helptxt}) = {cxxdef};\n")
+ file.write('}\n\n')
+
+def gen_cxx_oldstylesettingstobridgestruct(file, entries):
+ """Generate C++ code the convert old-style settings to new-style struct"""
+ file.write('void pdns::settings::rec::oldStyleSettingsToBridgeStruct')
+ file.write('(Recursorsettings& settings)\n{\n')
+ for entry in entries:
+ if entry['type'] == LType.Command:
+ continue
+ if 'skip-yaml' in entry:
+ continue
+ rust_type = get_rust_type(entry['type'])
+ name = entry['name']
+ oldname = entry['oldname']
+ section = entry['section']
+ file.write(f' settings.{section}.{name} = ')
+ if rust_type == 'bool':
+ file.write(f'arg().mustDo("{oldname}")')
+ elif rust_type == 'u64':
+ file.write(f'static_cast<uint64_t>(arg().asNum("{oldname}"))')
+ elif rust_type == 'f64':
+ file.write(f'arg().asDouble("{oldname}")')
+ elif rust_type == 'String':
+ file.write(f'arg()["{oldname}"]')
+ elif rust_type == 'Vec<String>':
+ file.write(f'getStrings("{oldname}")')
+ elif rust_type == 'Vec<ForwardZone>':
+ file.write(f'getForwardZones("{oldname}")')
+ elif rust_type == 'Vec<AuthZone>':
+ file.write(f'getAuthZones("{oldname}")')
+ else:
+ file.write(f'Unknown type {rust_type}\n')
+ file.write(';\n')
+ file.write('}\n\n')
+
+def gen_cxx_oldkvtobridgestruct(file, entries):
+ """Generate C++ code for oldKVToBridgeStruct"""
+ file.write('// Inefficient, but only meant to be used for one-time conversion purposes\n')
+ file.write('bool pdns::settings::rec::oldKVToBridgeStruct(std::string& key, ')
+ file.write('const std::string& value, ::rust::String& section, ::rust::String& fieldname, ')
+ file.write('::rust::String& type_name, pdns::rust::settings::rec::Value& rustvalue)')
+ file.write('{ // NOLINT(readability-function-cognitive-complexity)\n')
+ file.write(' if (const auto newname = arg().isDeprecated(key); !newname.empty()) {\n')
+ file.write(' key = newname;\n')
+ file.write(' }\n')
+ for entry in entries:
+ if entry['type'] == LType.Command:
+ continue
+ if 'skip-yaml' in entry:
+ continue
+ rust_type = get_rust_type(entry['type'])
+ extra = ''
+ if entry['oldname'] == 'forward-zones-recurse':
+ extra = ', true'
+ name = entry['name']
+ section = entry['section']
+ oldname = entry['oldname']
+ file.write(f' if (key == "{oldname}") {{\n')
+ file.write(f' section = "{section}";\n')
+ file.write(f' fieldname = "{name}";\n')
+ file.write(f' type_name = "{rust_type}";\n')
+ if rust_type == 'bool':
+ file.write(f' to_yaml(rustvalue.bool_val, value{extra});\n')
+ file.write(' return true;\n }\n')
+ elif rust_type == 'u64':
+ file.write(f' to_yaml(rustvalue.u64_val, value{extra});\n')
+ file.write(' return true;\n }\n')
+ elif rust_type == 'f64':
+ file.write(f' to_yaml(rustvalue.f64_val, value{extra});\n')
+ file.write(' return true;\n }\n')
+ elif rust_type == 'String':
+ file.write(f' to_yaml(rustvalue.string_val, value{extra});\n')
+ file.write(' return true;\n }\n')
+ elif rust_type == 'Vec<String>':
+ file.write(f' to_yaml(rustvalue.vec_string_val, value{extra});\n')
+ file.write(' return true;\n }\n')
+ elif rust_type == 'Vec<ForwardZone>':
+ file.write(f' to_yaml(rustvalue.vec_forwardzone_val, value{extra});\n')
+ file.write(' return true;\n }\n')
+ elif rust_type == 'Vec<AuthZone>':
+ file.write(f' to_yaml(rustvalue.vec_authzone_val, value{extra});\n')
+ file.write(' return true;\n }\n')
+ else:
+ file.write(f'Unknown type {rust_type}\n')
+ file.write(' return false;\n')
+ file.write('}\n\n')
+
+def gen_cxx_brigestructtoldstylesettings(file, entries):
+ """Generate C++ Code for bridgeStructToOldStyleSettings"""
+ file.write('void pdns::settings::rec::bridgeStructToOldStyleSettings')
+ file.write('(const Recursorsettings& settings)\n{\n')
+ for entry in entries:
+ if entry['type'] == LType.Command:
+ continue
+ if 'skip-yaml' in entry:
+ continue
+ section = entry['section'].lower()
+ name = entry['name']
+ oldname = entry['oldname']
+ file.write(f' ::arg().set("{oldname}") = ')
+ file.write(f'to_arg(settings.{section}.{name});\n')
+ file.write('}\n')
+
+def gen_cxx(entries):
+ """Generate the C++ code from the defs in table.py"""
+ with open('cxxsettings-generated.cc', mode='w', encoding="UTF-8") as file:
+ file.write('// THIS IS A GENERATED FILE. DO NOT EDIT. SOURCE: see settings dir\n\n')
+ file.write('#include "arguments.hh"\n')
+ file.write('#include "cxxsettings.hh"\n')
+ file.write('#include "cxxsettings-private.hh"\n\n')
+ gen_cxx_defineoldsettings(file, entries)
+ gen_cxx_oldstylesettingstobridgestruct(file, entries)
+ gen_cxx_oldkvtobridgestruct(file, entries)
+ gen_cxx_brigestructtoldstylesettings(file, entries)
+
+def is_value_rust_default(typ, value):
+ """Is a value (represented as string) the same as its corresponding Rust default?"""
+ if typ == 'bool':
+ return value == 'false'
+ if typ == 'u64':
+ return value in ('0', '')
+ if typ == 'f64':
+ return value == '0.0'
+ if typ == 'String':
+ return value == ''
+ return False
+
+def gen_rust_forwardzonevec_default_functions(name):
+ """Generate Rust code for the default handling of a vector for ForwardZones"""
+ ret = f'// DEFAULT HANDLING for {name}\n'
+ ret += f'fn default_value_{name}() -> Vec<recsettings::ForwardZone> {{\n'
+ ret += ' Vec::new()\n'
+ ret += '}\n'
+ ret += f'fn default_value_equal_{name}(value: &Vec<recsettings::ForwardZone>)'
+ ret += '-> bool {\n'
+ ret += f' let def = default_value_{name}();\n'
+ ret += ' &def == value\n'
+ ret += '}\n\n'
+ return ret
+
+def gen_rust_authzonevec_default_functions(name):
+ """Generate Rust code for the default handling of a vector for AuthZones"""
+ ret = f'// DEFAULT HANDLING for {name}\n'
+ ret += f'fn default_value_{name}() -> Vec<recsettings::AuthZone> {{\n'
+ ret += ' Vec::new()\n'
+ ret += '}\n'
+ ret += f'fn default_value_equal_{name}(value: &Vec<recsettings::AuthZone>)'
+ ret += '-> bool {\n'
+ ret += f' let def = default_value_{name}();\n'
+ ret += ' &def == value\n'
+ ret += '}\n\n'
+ return ret
+
+# Example snippet generated
+# fn default_value_general_query_local_address() -> Vec<String> {
+# vec![String::from("0.0.0.0"), ]
+#}
+#fn default_value_equal_general_query_local_address(value: &Vec<String>) -> bool {
+# let def = default_value_general_query_local_address();
+# &def == value
+#}
+def gen_rust_stringvec_default_functions(entry, name):
+ """Generate Rust code for the default handling of a vector for Strings"""
+ ret = f'// DEFAULT HANDLING for {name}\n'
+ ret += f'fn default_value_{name}() -> Vec<String> {{\n'
+ parts = re.split('[ \t,]+', entry['default'])
+ if len(parts) > 0:
+ ret += ' vec![\n'
+ for part in parts:
+ if part == '':
+ continue
+ ret += f' String::from({quote(part)}),\n'
+ ret += ' ]\n'
+ else:
+ ret += ' vec![]\n'
+ ret += '}\n'
+ ret += f'fn default_value_equal_{name}(value: &Vec<String>) -> bool {{\n'
+ ret += f' let def = default_value_{name}();\n'
+ ret += ' &def == value\n'
+ ret += '}\n\n'
+ return ret
+
+def gen_rust_default_functions(entry, name, rust_type):
+ """Generate Rust code for the default handling"""
+ if entry['type'] in (LType.ListSocketAddresses, LType.ListSubnets, LType.ListStrings):
+ return gen_rust_stringvec_default_functions(entry, name)
+ if entry['type'] == LType.ListForwardZones:
+ return gen_rust_forwardzonevec_default_functions(name)
+ if entry['type'] == LType.ListAuthZones:
+ return gen_rust_authzonevec_default_functions(name)
+ ret = f'// DEFAULT HANDLING for {name}\n'
+ ret += f'fn default_value_{name}() -> {rust_type} {{\n'
+ defvalue = entry['default']
+ rustdef = f'env!("{defvalue}")' if isEnvVar(defvalue) else quote(defvalue)
+ ret += f" String::from({rustdef})\n"
+ ret += '}\n'
+ if rust_type == 'String':
+ rust_type = 'str'
+ ret += f'fn default_value_equal_{name}(value: &{rust_type})'
+ ret += '-> bool {\n'
+ ret += f' value == default_value_{name}()\n'
+ ret += '}\n\n'
+ return ret
+
+def write_rust_field(file, entry, default_funcs):
+ """Generate Rust code for a field with Serde annotations"""
+ rust_type = get_rust_type(entry['type'])
+ the_default = entry['default']
+ is_rust_default = is_value_rust_default(rust_type, the_default)
+ if is_rust_default:
+ file.write(' #[serde(default, skip_serializing_if = "crate::is_default")]\n')
+ else:
+ if entry['type'] == LType.Bool:
+ file.write(' #[serde(default = "crate::Bool::<true>::value", ')
+ file.write('skip_serializing_if = "crate::if_true")]\n')
+ elif entry['type'] == LType.Uint64:
+ file.write(f' #[serde(default = "crate::U64::<{the_default}>::value", ')
+ file.write(f'skip_serializing_if = "crate::U64::<{the_default}>::is_equal")]\n')
+ else:
+ basename = entry['section'] + '_' + entry['name']
+ file.write(f' #[serde(default = "crate::default_value_{basename}", ')
+ file.write(f'skip_serializing_if = "crate::default_value_equal_{basename}")]\n')
+ default_funcs.append(gen_rust_default_functions(entry, basename, rust_type))
+ file.write(f" {entry['name']}: {rust_type},\n\n")
+
+def write_rust_section(file, section, entries, default_funcs):
+ """Generate Rust code for a Section with Serde annotations"""
+ file.write(f' // SECTION {section.capitalize()}\n')
+ file.write(' #[derive(Deserialize, Serialize, Debug, PartialEq)]\n')
+ file.write(' #[serde(deny_unknown_fields)]\n')
+ file.write(f' pub struct {section.capitalize()} {{\n')
+ for entry in entries:
+ if entry['section'] != section:
+ continue
+ if entry['type'] == LType.Command:
+ continue
+ if 'skip-yaml' in entry:
+ continue
+ write_rust_field(file, entry, default_funcs)
+ file.write(f' }}\n // END SECTION {section.capitalize()}\n\n')
+
+#
+# Each section als has a Default implementation, so that a section with all entries having a default
+# value does not get generated into a yaml section. Such a tarit looks like:
+#
+#impl Default for recsettings::ForwardZone {
+# fn default() -> Self {
+# let deserialized: recsettings::ForwardZone = serde_yaml::from_str("").unwrap();
+# deserialized
+# }
+#}
+
+def write_rust_default_trait_impl(file, section):
+ """Generate Rust code for the default Trait for a section"""
+ file.write(f'impl Default for recsettings::{section.capitalize()} {{\n')
+ file.write(' fn default() -> Self {\n')
+ file.write(' let deserialized: recsettings::')
+ file.write(f'{section.capitalize()} = serde_yaml::from_str("").unwrap();\n')
+ file.write(' deserialized\n')
+ file.write(' }\n')
+ file.write('}\n\n')
+
+def write_validator(file, section, entries):
+ """Generate Rust code for the Validator Trait for a section"""
+ file.write(f'impl Validate for recsettings::{section.capitalize()} {{\n')
+ file.write(' fn validate(&self) -> Result<(), ValidationError> {\n')
+ for entry in entries:
+ if entry['section'] != section:
+ continue
+ name = entry['name'].lower()
+ typ = entry['type']
+ if typ == LType.ListSubnets:
+ validator = 'validate_subnet'
+ elif typ == LType.ListSocketAddresses:
+ validator = 'validate_socket_address'
+ elif typ == LType.ListForwardZones:
+ validator = '|field, element| element.validate(field)'
+ elif typ == LType.ListAuthZones:
+ validator = '|field, element| element.validate(field)'
+ else:
+ continue
+ file.write(f' let fieldname = "{section.lower()}.{name}".to_string();\n')
+ file.write(f' validate_vec(&fieldname, &self.{name}, {validator})?;\n')
+ file.write(' Ok(())\n')
+ file.write(' }\n')
+ file.write('}\n\n')
+
+def write_rust_merge_trait_impl(file, section, entries):
+ """Generate Rust code for the Merge Trait for a section"""
+ file.write(f'impl Merge for recsettings::{section.capitalize()} {{\n')
+ file.write(' fn merge(&mut self, rhs: &mut Self, map: Option<&serde_yaml::Mapping>) {\n')
+ file.write(' if let Some(m) = map {\n')
+ for entry in entries:
+ if entry['section'] != section:
+ continue
+ if 'skip-yaml' in entry:
+ continue
+ rtype = get_rust_type(entry['type'])
+ name = entry['name']
+ file.write(f' if m.contains_key("{name}") {{\n')
+ if rtype in ('bool', 'u64', 'f64', 'String'):
+ file.write(f' self.{name} = rhs.{name}.to_owned();\n')
+ else:
+ file.write(f' if is_overriding(m, "{name}") || ')
+ file.write(f'self.{name} == DEFAULT_CONFIG.{section}.{name} {{\n')
+ file.write(f' self.{name}.clear();\n')
+ file.write(' }\n')
+ file.write(f' merge_vec(&mut self.{name}, &mut rhs.{name});\n')
+ file.write(' }\n')
+ file.write(' }\n')
+ file.write(' }\n')
+ file.write('}\n\n')
+
+def gen_rust(entries):
+ """Generate Rust code all entries"""
+ def_functions = []
+ sections = {}
+ with open('rust/src/lib.rs', mode='w', encoding='UTF-8') as file:
+ file.write('// THIS IS A GENERATED FILE. DO NOT EDIT. SOURCE: see settings dir\n')
+ file.write('// START INCLUDE rust-preable-in.rs\n')
+ with open('rust-preamble-in.rs', mode='r', encoding='UTF-8') as pre:
+ file.write(pre.read())
+ file.write('// END INCLUDE rust-preamble-in.rs\n\n')
+
+ file.write('#[cxx::bridge(namespace = "pdns::rust::settings::rec")]\n')
+ file.write('mod recsettings {\n')
+ with open('rust-bridge-in.rs', mode='r', encoding='UTF-8') as bridge:
+ file.write(' // START INCLUDE rust-bridge-in.rs\n')
+ for line in bridge:
+ file.write(' ' + line)
+
+ file.write(' // END INCLUDE rust-bridge-in.rs\n\n')
+ for entry in entries:
+ if entry['section'] == 'commands':
+ continue
+ sections[entry['section']] = entry['section']
+
+ for section in sections:
+ write_rust_section(file, section, entries, def_functions)
+
+ file.write(' #[derive(Serialize, Deserialize, Debug)]\n')
+ file.write(' #[serde(deny_unknown_fields)]\n')
+ file.write(' pub struct Recursorsettings {\n')
+ for section in sections:
+ file.write(' #[serde(default, skip_serializing_if = "crate::is_default")]\n')
+ file.write(f' {section.lower()}: {section.capitalize()},\n')
+ file.write('} // End of generated structs\n')
+ file.write('}\n')
+
+ for section in sections:
+ write_rust_default_trait_impl(file, section)
+ write_rust_default_trait_impl(file, 'Recursorsettings')
+
+ for section in sections:
+ write_validator(file, section, entries)
+
+ file.write('impl crate::recsettings::Recursorsettings {\n')
+ file.write(' fn validate(&self) -> Result<(), ValidationError> {\n')
+ for section in sections:
+ file.write(f' self.{section.lower()}.validate()?;\n')
+ file.write(' Ok(())\n')
+ file.write(' }\n')
+ file.write('}\n\n')
+
+ for section in sections:
+ write_rust_merge_trait_impl(file, section, entries)
+
+ file.write('impl Merge for crate::recsettings::Recursorsettings {\n')
+ file.write(' fn merge(&mut self, rhs: &mut Self, map: Option<&serde_yaml::Mapping>) {\n')
+ file.write(' if let Some(m) = map {\n')
+ for section in sections:
+ file.write(f' if let Some(s) = m.get("{section}") {{\n')
+ file.write(' if s.is_mapping() {\n')
+ file.write((f' self.{section}.merge(&mut rhs.{section},'
+ ' s.as_mapping());\n'))
+ file.write(' }\n')
+ file.write(' }\n')
+ file.write(' }\n')
+ file.write(' }\n')
+ file.write('}\n\n')
+
+ for entry in def_functions:
+ file.write(entry)
+ file.close()
+
+def gen_docs_meta(file, entry, name, is_tuple):
+ """Write .. versionadded:: and related entries"""
+ if name in entry:
+ val = entry[name]
+ if not isinstance(val, list):
+ val = [val]
+ for vers in val:
+ if is_tuple:
+ file.write(f'.. {name}:: {vers[0]}\n\n')
+ file.write(f' {vers[1].strip()}\n')
+ else:
+ file.write(f'.. {name}:: {vers}\n')
+
+def gen_oldstyle_docs(entries):
+ """Write old style docs"""
+ with open('../docs/settings.rst', mode='w', encoding='UTF-8') as file:
+ file.write('.. THIS IS A GENERATED FILE. DO NOT EDIT. SOURCE: see settings dir\n')
+ file.write(' START INCLUDE docs-old-preamble-in.rst\n\n')
+ with open('docs-old-preamble-in.rst', mode='r', encoding='UTF-8') as pre:
+ file.write(pre.read())
+ file.write('.. END INCLUDE docs-old-preamble-in.rst\n\n')
+
+ for entry in entries:
+ if entry['type'] == LType.Command:
+ continue
+ if entry['doc'].strip() == 'SKIP':
+ continue
+ oldname = entry['oldname']
+ section = entry['section']
+ file.write(f'.. _setting-{oldname}:\n\n')
+ file.write(f'``{oldname}``\n')
+ dots = '~' * (len(entry['oldname']) + 4)
+ file.write(f'{dots}\n')
+ gen_docs_meta(file, entry, 'versionadded', False)
+ gen_docs_meta(file, entry, 'versionchanged', True)
+ gen_docs_meta(file, entry, 'deprecated', True)
+ if 'doc-rst' in entry:
+ file.write(entry['doc-rst'].strip())
+ file.write('\n')
+ file.write('\n')
+ typ = get_olddoc_typename(entry['type'])
+ file.write(f'- {typ}\n')
+ if 'docdefault' in entry:
+ file.write(f"- Default: {entry['docdefault']}\n\n")
+ else:
+ file.write((f"- Default: "
+ f"{get_default_olddoc_value(entry['type'], entry['default'])}\n\n"))
+ if 'skip-yaml' in entry:
+ file.write('- YAML setting does not exist\n\n')
+ else:
+ file.write(f"- YAML setting: :ref:`setting-yaml-{section}.{entry['name']}`\n\n")
+ file.write(entry['doc'].strip())
+ file.write('\n\n')
+
+def fixxrefs(entries, arg):
+ """Docs in table refer to old style names, we modify them to ref to new style"""
+ matches = re.findall(':ref:`setting-(.*?)`', arg)
+ # We want to replace longest match first, to avoid a short match modifying a long one
+ matches = sorted(matches, key = lambda x: -len(x))
+ for match in matches:
+ for entry in entries:
+ if entry['oldname'] == match:
+ key = ':ref:`setting-' + match
+ repl = ':ref:`setting-yaml-' + entry['section'] + '.' + entry['name']
+ arg = arg.replace(key, repl)
+ return arg
+
+def gen_newstyle_docs(argentries):
+ """Write new style docs"""
+ entries = sorted(argentries, key = lambda entry: [entry['section'], entry['name']])
+ with open('../docs/yamlsettings.rst', 'w', encoding='utf-8') as file:
+ file.write('.. THIS IS A GENERATED FILE. DO NOT EDIT. SOURCE: see settings dir\n')
+ file.write(' START INCLUDE docs-new-preamble-in.rst\n\n')
+ with open('docs-new-preamble-in.rst', mode='r', encoding='utf-8') as pre:
+ file.write(pre.read())
+ file.write('.. END INCLUDE docs-new-preamble-in.rst\n\n')
+
+ for entry in entries:
+ if entry['type'] == LType.Command:
+ continue
+ if entry['doc'].strip() == 'SKIP':
+ continue
+ if 'skip-yaml' in entry:
+ continue
+ section = entry['section']
+ name = entry['name']
+ fullname = section + '.' + name
+ file.write(f'.. _setting-yaml-{fullname}:\n\n')
+ file.write(f'``{fullname}``\n')
+ dots = '^' * (len(fullname) + 4)
+ file.write(f'{dots}\n')
+ gen_docs_meta(file, entry, 'versionadded', False)
+ gen_docs_meta(file, entry, 'versionchanged', True)
+ gen_docs_meta(file, entry, 'deprecated', True)
+ if 'doc-rst' in entry:
+ file.write(fixxrefs(entries, entry['doc-rst'].strip()))
+ file.write('\n')
+ file.write('\n')
+ file.write(f"- {get_newdoc_typename(entry['type'])}\n")
+ if 'docdefault' in entry:
+ file.write(f"- Default: {entry['docdefault']}\n\n")
+ else:
+ file.write((f"- Default: "
+ f"{get_default_newdoc_value(entry['type'], entry['default'])}\n\n"))
+ file.write(f"- Old style setting: :ref:`setting-{entry['oldname']}`\n\n")
+ if 'doc-new' in entry:
+ file.write(fixxrefs(entries, entry['doc-new'].strip()))
+ else:
+ file.write(fixxrefs(entries, entry['doc'].strip()))
+ file.write('\n\n')
+
+RUNTIME = '*runtime determined*'
+
+def generate():
+ """Read table, validate and generate C++, Rst and .rst files"""
+ # read table
+ with open('table.py', mode='r', encoding="utf-8") as file:
+ entries = eval(file.read())
+
+ for entry in entries:
+ the_oldname = entry['name'].replace('_', '-')
+ if 'oldname' in entry:
+ if entry['oldname'] == the_oldname:
+ sys.stderr.write(f"Redundant old name {entry['oldname']}\n")
+ else:
+ entry['oldname'] = the_oldname
+
+ dupcheck1 = {}
+ dupcheck2 = {}
+ for entry in entries:
+ if entry['oldname'] in dupcheck1:
+ sys.stderr.write(f"duplicate entries with oldname = {entry['oldname']}\n")
+ sys.exit(1)
+ if entry['section'] + '.' + entry['name'] in dupcheck2:
+ sys.stderr.write((f"duplicate entries with section.name = "
+ f"{entry['section']}.{ entry['name']}\n"))
+ sys.exit(1)
+ dupcheck1[entry['oldname']] = True
+ dupcheck2[entry['section'] + '.' + entry['name']] = True
+ # And generate C++, Rust and docs code based on table
+ gen_cxx(entries)
+ gen_rust(entries)
+ # Avoid generating doc files in a sdist based build
+ if os.path.isdir('../docs'):
+ gen_oldstyle_docs(entries)
+ gen_newstyle_docs(entries)
+
+generate()
--- /dev/null
+// This file (rust-bridge-in.rs) is included into lib.rs inside the bridge module
+/*
+ * Implement non-generated structs that need to be handled by Serde and CXX
+ */
+
+// A single forward zone
+#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct ForwardZone {
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ zone: String,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ forwarders: Vec<String>,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ recurse: bool,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ notify_allowed: bool,
+}
+
+// A single auth zone
+#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct AuthZone {
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ zone: String,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ file: String,
+}
+
+// A struct holding bot a vector of forward zones and a vector o auth zones, used by REST API code
+#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct ApiZones {
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ auth_zones: Vec<AuthZone>,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ forward_zones: Vec<ForwardZone>,
+}
+
+// Two structs used to generated YAML based on a vector of name to value mappings
+// Cannot use Enum as CXX has only very basic Enum support
+struct Value {
+ bool_val: bool,
+ u64_val: u64,
+ f64_val: f64,
+ string_val: String,
+ vec_string_val: Vec<String>,
+ vec_forwardzone_val: Vec<ForwardZone>,
+ vec_authzone_val: Vec<AuthZone>,
+}
+
+struct OldStyle {
+ section: String,
+ name: String,
+ old_name: String,
+ type_name: String,
+ value: Value,
+ overriding: bool,
+}
+
+/*
+ * Functions callable from C++
+ */
+extern "Rust" {
+ // Parse a string representing YAML text and produce the corresponding data structure known to Serde
+ // The settings that can be stored in individual files get their own parse function
+ // Main recursor settings
+ fn parse_yaml_string(str: &str) -> Result<Recursorsettings>;
+ // Allow from sequence
+ fn parse_yaml_string_to_allow_from(str: &str) -> Result<Vec<String>>;
+ // Forward zones sequence
+ fn parse_yaml_string_to_forward_zones(str: &str) -> Result<Vec<ForwardZone>>;
+ // Allow notify for sequence
+ fn parse_yaml_string_to_allow_notify_for(str: &str) -> Result<Vec<String>>;
+ // REST APIU zones
+ fn parse_yaml_string_to_api_zones(str: &str) -> Result<ApiZones>;
+
+ // Prdoduce a YAML formatted sting given a data structure known to Serde
+ fn to_yaml_string(self: &Recursorsettings) -> Result<String>;
+ // When doing a conversion of old-style to YAML style we use a vector of OldStyle structs
+ fn map_to_yaml_string(map: &Vec<OldStyle>) -> Result<String>;
+ fn forward_zones_to_yaml_string(vec: &Vec<ForwardZone>) -> Result<String>;
+ fn allow_from_to_yaml_string(vec: &Vec<String>) -> Result<String>;
+ fn allow_from_to_yaml_string_incoming(key: &String, filekey: &String, vec: &Vec<String>) -> Result<String>;
+ fn allow_for_to_yaml_string(vec: &Vec<String>) -> Result<String>;
+
+ // Merge a string representing YAML settings into a existing setttings struct
+ fn merge(lhs: &mut Recursorsettings, rhs: &str) -> Result<()>;
+
+ // Validate the sections inside the main settings struct, sections themselves will valdiate their fields
+ fn validate(self: &Recursorsettings) -> Result<()>;
+ // The validate function bewlo are "hand-crafted" as their structs afre mnot generated
+ fn validate(self: &AuthZone, field: &str) -> Result<()>;
+ fn validate(self: &ForwardZone, field: &str) -> Result<()>;
+ fn validate(self: &ApiZones, field: &str) -> Result<()>;
+
+ // Helper functions to call the proper validate function on vectors of various kinds
+ fn validate_auth_zones(field: &str, vec: &Vec<AuthZone>) -> Result<()>;
+ fn validate_forward_zones(field: &str, vec: &Vec<ForwardZone>) -> Result<()>;
+ fn validate_allow_for(field: &str, vec: &Vec<String>) -> Result<()>;
+ fn validate_allow_notify_for(field: &str, vec: &Vec<String>) -> Result<()>;
+ fn validate_allow_from(field: &str, vec: &Vec<String>) -> Result<()>;
+
+ // The functions to maintain REST API managed zones
+ fn api_read_zones(path: &str) -> Result<UniquePtr<ApiZones>>;
+ fn api_add_auth_zone(file: &str, authzone: AuthZone) -> Result<()>;
+ fn api_add_forward_zone(file: &str, forwardzone: ForwardZone) -> Result<()>;
+ fn api_add_forward_zones(file: &str, forwardzones: &mut Vec<ForwardZone>) -> Result<()>;
+ fn api_delete_zone(file: &str, zone: &str) -> Result<()>;
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+// This file (rust-preamble-in.rs) is included at the start of lib.rs
+
+use serde::{Deserialize, Serialize};
+
+mod helpers;
+use helpers::*;
+
+mod bridge;
+use bridge::*;
+
+// Suppresses "Deserialize unused" warning
+#[derive(Deserialize, Serialize)]
+struct UnusedStruct {}
+
+trait Validate {
+ fn validate(&self) -> Result<(), ValidationError>;
+}
+
+#[derive(Debug)]
+pub struct ValidationError {
+ msg: String,
+}
+
+trait Merge {
+ fn merge(&mut self, rhs: &mut Self, map: Option<&serde_yaml::Mapping>);
+}
--- /dev/null
+/target
+/Makefile
+/Makefile.in
+/cxx.h
+/lib.rs.h
+src/lib.rs
+.dir-locals.el
--- /dev/null
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "cxx"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de00f15a6fa069c99b88c5c78c4541d0e7899a33b86f7480e23df2431fce0bc"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a71e1e631fa2f2f5f92e8b0d860a00c198c6771623a6cefcc863e3554f0d8d6"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f3fed61d56ba497c4efef9144dfdbaa25aa58f2f6b3a7cf441d4591c583745c"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8908e380a8efd42150c017b0cfa31509fc49b6d47f7cb6b33e93ffb8f4e3661e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "scratch"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
+
+[[package]]
+name = "serde"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
+name = "settings"
+version = "0.1.0"
+dependencies = [
+ "cxx",
+ "cxx-build",
+ "ipnet",
+ "once_cell",
+ "serde",
+ "serde_yaml",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
--- /dev/null
+[package]
+name = "settings"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "settings"
+crate-type = ["staticlib"]
+
+[dependencies]
+cxx = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+serde_yaml = "0.9"
+ipnet = "2.8"
+once_cell = "1.18.0"
+
+[build-dependencies]
+cxx-build = "1.0"
+
--- /dev/null
+CARGO ?= cargo
+
+all install: libsettings.a
+
+EXTRA_DIST = \
+ Cargo.toml \
+ Cargo.lock \
+ build.rs \
+ src/bridge.rs \
+ src/helpers.rs
+
+# should actually end up in a target specific dir...
+libsettings.a lib.rs.h: src/bridge.rs src/lib.rs src/helpers.rs Cargo.toml Cargo.lock build.rs
+ SYSCONFDIR=$(sysconfdir) NODCACHEDIRNOD=$(localstatedir)/nod NODCACHEDIRUDR=$(localstatedir)/udr $(CARGO) build --release $(RUST_TARGET) --target-dir=$(builddir)/target --manifest-path ${srcdir}/Cargo.toml
+ cp target/$(RUSTC_TARGET_ARCH)/release/libsettings.a libsettings.a
+ cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/lib.rs.h lib.rs.h
+ cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/rust/cxx.h cxx.h
+
+clean-local:
+ rm -rf libsettings.a src/lib.rs lib.rs.h cxx.h target
--- /dev/null
+fn main() {
+ cxx_build::bridge("src/lib.rs")
+ // .file("src/source.cc") at the moment no C++ code callable from Rust
+ .flag_if_supported("-std=c++17")
+ .compile("settings");
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+use once_cell::sync::Lazy;
+use std::fs::File;
+use std::io::{BufReader, BufWriter, ErrorKind, Write};
+use std::net::{IpAddr, SocketAddr};
+use std::str::FromStr;
+use std::sync::Mutex;
+
+use crate::helpers::OVERRIDE_TAG;
+use crate::recsettings::*;
+use crate::{Merge, ValidationError};
+
+impl Default for ForwardZone {
+ fn default() -> Self {
+ let deserialized: ForwardZone = serde_yaml::from_str("").unwrap();
+ deserialized
+ }
+}
+
+impl Default for AuthZone {
+ fn default() -> Self {
+ let deserialized: AuthZone = serde_yaml::from_str("").unwrap();
+ deserialized
+ }
+}
+
+impl Default for ApiZones {
+ fn default() -> Self {
+ let deserialized: ApiZones = serde_yaml::from_str("").unwrap();
+ deserialized
+ }
+}
+
+pub fn validate_socket_address(field: &str, val: &String) -> Result<(), ValidationError> {
+ let sa = SocketAddr::from_str(val);
+ if sa.is_err() {
+ let ip = IpAddr::from_str(val);
+ if ip.is_err() {
+ let msg = format!(
+ "{}: value `{}' is not an IP or IP:port combination",
+ field, val
+ );
+ return Err(ValidationError { msg });
+ }
+ }
+ Ok(())
+}
+
+fn validate_name(field: &str, val: &String) -> Result<(), ValidationError> {
+ if val.is_empty() {
+ let msg = format!("{}: value may not be empty", field);
+ return Err(ValidationError { msg });
+ }
+ if val == "." {
+ return Ok(());
+ }
+ // Strip potential dot at end
+ let mut testval = val.as_str();
+ if testval.ends_with('.') {
+ testval = &testval[0..testval.len() - 1];
+ }
+ for label in testval.split('.') {
+ if label.is_empty() {
+ let msg = format!("{}: `{}' has empty label", field, val);
+ return Err(ValidationError { msg });
+ }
+ // XXX Too liberal, should check for alnum, - and proper \ddd
+ if !label.chars().all(|ch| ch.is_ascii()) {
+ let msg = format!("{}: `{}' contains non-ascii character", field, val);
+ return Err(ValidationError { msg });
+ }
+ }
+ Ok(())
+}
+
+pub fn validate_subnet(field: &str, val: &String) -> Result<(), ValidationError> {
+ if val.is_empty() {
+ let msg = format!("{}: value `{}' is not a subnet or IP", field, val);
+ return Err(ValidationError { msg });
+ }
+ let mut ip = val.as_str();
+ if val.starts_with('!') {
+ ip = &ip[1..];
+ }
+ let subnet = ipnet::IpNet::from_str(ip);
+ if subnet.is_err() {
+ let ip = IpAddr::from_str(ip);
+ if ip.is_err() {
+ let msg = format!("{}: value `{}' is not a subnet or IP", field, val);
+ return Err(ValidationError { msg });
+ }
+ }
+ Ok(())
+}
+
+pub fn validate_vec<T, F>(field: &str, vec: &[T], func: F) -> Result<(), ValidationError>
+where
+ F: Fn(&str, &T) -> Result<(), ValidationError>,
+{
+ vec.iter().try_for_each(|element| func(field, element))
+}
+
+pub fn parse_yaml_string(str: &str) -> Result<Recursorsettings, serde_yaml::Error> {
+ serde_yaml::from_str(str)
+}
+
+pub fn parse_yaml_string_to_allow_from(str: &str) -> Result<Vec<String>, serde_yaml::Error> {
+ serde_yaml::from_str(str)
+}
+
+pub fn parse_yaml_string_to_forward_zones(
+ str: &str,
+) -> Result<Vec<ForwardZone>, serde_yaml::Error> {
+ serde_yaml::from_str(str)
+}
+
+pub fn parse_yaml_string_to_api_zones(str: &str) -> Result<ApiZones, serde_yaml::Error> {
+ serde_yaml::from_str(str)
+}
+
+pub fn parse_yaml_string_to_allow_notify_for(
+ str: &str,
+) -> Result<Vec<String>, serde_yaml::Error> {
+ serde_yaml::from_str(str)
+}
+
+pub fn forward_zones_to_yaml_string(vec: &Vec<ForwardZone>) -> Result<String, serde_yaml::Error> {
+ serde_yaml::to_string(vec)
+}
+
+impl ForwardZone {
+ pub fn validate(&self, field: &str) -> Result<(), ValidationError> {
+ validate_name(&(field.to_owned() + ".zone"), &self.zone)?;
+ if self.forwarders.is_empty() {
+ let msg = format!("{}.forwarders cannot be empty", field);
+ return Err(ValidationError { msg });
+ }
+ validate_vec(
+ &(field.to_owned() + ".forwarders"),
+ &self.forwarders,
+ validate_socket_address,
+ )
+ }
+
+ fn to_yaml_map(&self) -> serde_yaml::Value {
+ let mut seq = serde_yaml::Sequence::new();
+ for entry in &self.forwarders {
+ seq.push(serde_yaml::Value::String(entry.to_owned()));
+ }
+
+ let mut map = serde_yaml::Mapping::new();
+ map.insert(
+ serde_yaml::Value::String("zone".to_owned()),
+ serde_yaml::Value::String(self.zone.to_owned()),
+ );
+ map.insert(
+ serde_yaml::Value::String("recurse".to_owned()),
+ serde_yaml::Value::Bool(self.recurse),
+ );
+ map.insert(
+ serde_yaml::Value::String("forwarders".to_owned()),
+ serde_yaml::Value::Sequence(seq),
+ );
+ serde_yaml::Value::Mapping(map)
+ }
+}
+
+impl AuthZone {
+ pub fn validate(&self, field: &str) -> Result<(), ValidationError> {
+ validate_name(&(field.to_owned() + ".zone"), &self.zone)?;
+ if self.file.is_empty() {
+ let msg = format!("{}.file cannot be empty", field);
+ return Err(ValidationError { msg });
+ }
+ Ok(())
+ }
+
+ fn to_yaml_map(&self) -> serde_yaml::Value {
+ let mut map = serde_yaml::Mapping::new();
+ map.insert(
+ serde_yaml::Value::String("zone".to_owned()),
+ serde_yaml::Value::String(self.zone.to_owned()),
+ );
+ map.insert(
+ serde_yaml::Value::String("file".to_owned()),
+ serde_yaml::Value::String(self.file.to_owned()),
+ );
+ serde_yaml::Value::Mapping(map)
+ }
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_auth_zones(field: &str, vec: &Vec<AuthZone>) -> Result<(), ValidationError> {
+ validate_vec(field, vec, |field, element| element.validate(field))
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_forward_zones(
+ field: &str,
+ vec: &Vec<ForwardZone>,
+) -> Result<(), ValidationError> {
+ validate_vec(field, vec, |field, element| element.validate(field))
+}
+
+pub fn allow_from_to_yaml_string(vec: &Vec<String>) -> Result<String, serde_yaml::Error> {
+ let mut seq = serde_yaml::Sequence::new();
+ for entry in vec {
+ seq.push(serde_yaml::Value::String(entry.to_owned()));
+ }
+ let val = serde_yaml::Value::Sequence(seq);
+
+ serde_yaml::to_string(&val)
+}
+
+pub fn allow_from_to_yaml_string_incoming(
+ key: &String,
+ filekey: &String,
+ vec: &Vec<String>,
+) -> Result<String, serde_yaml::Error> {
+ // Produce
+ // incoming:
+ // allow-from-file: ''
+ // allow-from: !override
+ // - ...
+ let mut seq = serde_yaml::Sequence::new();
+ for entry in vec {
+ seq.push(serde_yaml::Value::String(entry.to_owned()));
+ }
+
+ let mut innermap = serde_yaml::Mapping::new();
+ innermap.insert(
+ serde_yaml::Value::String(filekey.to_owned()),
+ serde_yaml::Value::String("".to_owned()),
+ );
+ let af = Box::new(serde_yaml::value::TaggedValue {
+ tag: serde_yaml::value::Tag::new(OVERRIDE_TAG),
+ value: serde_yaml::Value::Sequence(seq),
+ });
+ innermap.insert(
+ serde_yaml::Value::String(key.to_owned()),
+ serde_yaml::Value::Tagged(af),
+ );
+
+ let mut outermap = serde_yaml::Mapping::new();
+ outermap.insert(
+ serde_yaml::Value::String("incoming".to_owned()),
+ serde_yaml::Value::Mapping(innermap),
+ );
+ let outerval = serde_yaml::Value::Mapping(outermap);
+
+ serde_yaml::to_string(&outerval)
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_allow_from(field: &str, vec: &Vec<String>) -> Result<(), ValidationError> {
+ validate_vec(field, vec, validate_subnet)
+}
+
+pub fn allow_for_to_yaml_string(vec: &Vec<String>) -> Result<String, serde_yaml::Error> {
+ // For purpose of generating yaml allow-for is no different than allow-from as we're handling a
+ // vector of Strings
+ allow_from_to_yaml_string(vec)
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_allow_for(field: &str, vec: &Vec<String>) -> Result<(), ValidationError> {
+ validate_vec(field, vec, validate_name)
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_allow_notify_for(field: &str, vec: &Vec<String>) -> Result<(), ValidationError> {
+ validate_vec(field, vec, validate_name)
+}
+
+impl Recursorsettings {
+ pub fn to_yaml_string(&self) -> Result<String, serde_yaml::Error> {
+ serde_yaml::to_string(self)
+ }
+
+ // validate() is implemented in the (generated) lib.rs
+}
+
+pub static DEFAULT_CONFIG: Lazy<Recursorsettings> = Lazy::new(Recursorsettings::default);
+
+pub fn merge_vec<T>(lhs: &mut Vec<T>, rhs: &mut Vec<T>) {
+ lhs.append(rhs);
+}
+
+// This is used for conversion, where we want to have !override tags in some cases, so we craft a YAML Mapping by hand
+pub fn map_to_yaml_string(vec: &Vec<OldStyle>) -> Result<String, serde_yaml::Error> {
+ let mut map = serde_yaml::Mapping::new();
+ for entry in vec {
+ let section = entry.section.as_str();
+ if !map.contains_key(section) {
+ let newmap = serde_yaml::Mapping::new();
+ map.insert(
+ serde_yaml::Value::String(section.to_string()),
+ serde_yaml::Value::Mapping(newmap),
+ );
+ }
+ if let Some(mapentry) = map.get_mut(section) {
+ if let Some(mapping) = mapentry.as_mapping_mut() {
+ let val = match entry.type_name.as_str() {
+ "bool" => serde_yaml::Value::Bool(entry.value.bool_val),
+ "u64" => {
+ serde_yaml::Value::Number(serde_yaml::Number::from(entry.value.u64_val))
+ }
+ "f64" => {
+ serde_yaml::Value::Number(serde_yaml::Number::from(entry.value.f64_val))
+ }
+ "String" => serde_yaml::Value::String(entry.value.string_val.to_owned()),
+ "Vec<String>" => {
+ let mut seq = serde_yaml::Sequence::new();
+ for element in &entry.value.vec_string_val {
+ seq.push(serde_yaml::Value::String(element.to_owned()))
+ }
+ serde_yaml::Value::Sequence(seq)
+ }
+ "Vec<ForwardZone>" => {
+ let mut seq = serde_yaml::Sequence::new();
+ for element in &entry.value.vec_forwardzone_val {
+ seq.push(element.to_yaml_map());
+ }
+ serde_yaml::Value::Sequence(seq)
+ }
+ "Vec<AuthZone>" => {
+ let mut seq = serde_yaml::Sequence::new();
+ for element in &entry.value.vec_authzone_val {
+ seq.push(element.to_yaml_map());
+ }
+ serde_yaml::Value::Sequence(seq)
+ }
+ other => serde_yaml::Value::String("map_to_yaml_string: Unknown type: ".to_owned() + other),
+ };
+ if entry.overriding {
+ let tagged_value = Box::new(serde_yaml::value::TaggedValue {
+ tag: serde_yaml::value::Tag::new(OVERRIDE_TAG),
+ value: val,
+ });
+ mapping.insert(
+ serde_yaml::Value::String(entry.name.to_owned()),
+ serde_yaml::Value::Tagged(tagged_value),
+ );
+ } else {
+ mapping.insert(serde_yaml::Value::String(entry.name.to_owned()), val);
+ }
+ }
+ }
+ }
+ serde_yaml::to_string(&map)
+}
+
+pub fn merge(lhs: &mut Recursorsettings, yaml_str: &str) -> Result<(), serde_yaml::Error> {
+ // Parse the yaml for the values
+ let mut rhs: Recursorsettings = serde_yaml::from_str(yaml_str)?;
+ // Parse again for the map containing the keys present, which is used to only override specific values,
+ // taking into account !override tags
+ let map: serde_yaml::Value = serde_yaml::from_str(yaml_str)?;
+ if map.is_mapping() {
+ lhs.merge(&mut rhs, map.as_mapping());
+ }
+
+ Ok(())
+}
+
+// API zones maintenance. In contrast to the old settings code, which creates a settings file per
+// zone, we maintain a single yaml file with all settings. File locking is no issue, as we are the
+// single process managing this dir. So we only use process specific locking.
+
+impl ApiZones {
+ pub fn validate(&self, field: &str) -> Result<(), ValidationError> {
+ validate_auth_zones(&(field.to_owned() + ".auth_zones"), &self.auth_zones)?;
+ validate_forward_zones(&(field.to_owned() + ".forward_zones"), &self.forward_zones)?;
+ Ok(())
+ }
+}
+
+static LOCK: Mutex<bool> = Mutex::new(false);
+
+// Assume we hold the lock
+fn api_read_zones_locked(
+ path: &str,
+ create: bool,
+) -> Result<cxx::UniquePtr<ApiZones>, std::io::Error> {
+ let zones = match File::open(path) {
+ Ok(file) => {
+ let data: Result<ApiZones, serde_yaml::Error> =
+ serde_yaml::from_reader(BufReader::new(file));
+ match data {
+ Err(error) => return Err(std::io::Error::new(ErrorKind::Other, error.to_string())),
+ Ok(yaml) => yaml,
+ }
+ }
+ Err(error) => match error.kind() {
+ // If the file does not exist we return an empty struct
+ ErrorKind::NotFound => {
+ if create {
+ ApiZones::default()
+ } else {
+ return Err(error);
+ }
+ }
+ // Any other error is fatal
+ _ => return Err(error),
+ },
+ };
+ Ok(cxx::UniquePtr::new(zones))
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_read_zones(path: &str) -> Result<cxx::UniquePtr<ApiZones>, std::io::Error> {
+ let _lock = LOCK.lock().unwrap();
+ api_read_zones_locked(path, false)
+}
+
+// Assume we hold the lock
+fn api_write_zones(path: &str, zones: &ApiZones) -> Result<(), std::io::Error> {
+ let mut tmpfile = path.to_owned();
+ tmpfile.push_str(".tmp");
+
+ let file = File::create(tmpfile.as_str())?;
+ let mut buffered_writer = BufWriter::new(&file);
+ if let Err(error) = serde_yaml::to_writer(&mut buffered_writer, &zones) {
+ return Err(std::io::Error::new(ErrorKind::Other, error.to_string()));
+ }
+ buffered_writer.flush()?;
+ file.sync_all()?;
+ drop(buffered_writer);
+ std::fs::rename(tmpfile.as_str(), path)
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_add_auth_zone(path: &str, authzone: AuthZone) -> Result<(), std::io::Error> {
+ let _lock = LOCK.lock().unwrap();
+ let mut zones = api_read_zones_locked(path, true)?;
+ zones.auth_zones.push(authzone);
+ api_write_zones(path, &zones)
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_add_forward_zone(path: &str, forwardzone: ForwardZone) -> Result<(), std::io::Error> {
+ let _lock = LOCK.lock().unwrap();
+ let mut zones = api_read_zones_locked(path, true)?;
+ zones.forward_zones.push(forwardzone);
+ api_write_zones(path, &zones)
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_add_forward_zones(path: &str, forwardzones: &mut Vec<ForwardZone>) -> Result<(), std::io::Error> {
+ let _lock = LOCK.lock().unwrap();
+ let mut zones = api_read_zones_locked(path, true)?;
+ zones.forward_zones.append(forwardzones);
+ api_write_zones(path, &zones)
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_delete_zone(path: &str, zone: &str) -> Result<(), std::io::Error> {
+ let _lock = LOCK.lock().unwrap();
+ let mut zones = api_read_zones_locked(path, true)?;
+ zones.auth_zones.retain(|x| x.zone != zone);
+ // Zone data file is unlinked in the C++ caller ws-recursor.cc:doDeleteZone()
+ zones.forward_zones.retain(|x| x.zone != zone);
+ api_write_zones(path, &zones)
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+use std::{error::Error, fmt};
+use crate::ValidationError;
+
+/* Helper code for validation */
+impl Error for ValidationError {}
+impl fmt::Display for ValidationError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.msg)
+ }
+}
+
+// Generic helpers
+
+// A helper to define a function returning a constant value and an equal function as a Rust path */
+pub struct U64<const U: u64>;
+impl<const U: u64> U64<U> {
+ pub const fn value() -> u64 {
+ U
+ }
+ pub fn is_equal(v: &u64) -> bool {
+ v == &U
+ }
+}
+
+// A helper to define constant value as a Rust path */
+pub struct Bool<const U: bool>;
+impl<const U: bool> Bool<U> {
+ pub const fn value() -> bool {
+ U
+ }
+}
+
+// A helper used to decide if a bool value should be skipped
+pub fn if_true(v: &bool) -> bool {
+ *v
+}
+
+/* Helper to decide if a value has a default value, as defined by Default trait */
+pub fn is_default<T: Default + PartialEq>(t: &T) -> bool {
+ t == &T::default()
+}
+
+pub const OVERRIDE_TAG: &str = "!override";
+
+pub fn is_overriding(m: &serde_yaml::Mapping, key: &str) -> bool{
+ if let Some(serde_yaml::Value::Tagged(vvv)) = m.get(key) {
+ return vvv.tag == OVERRIDE_TAG;
+ }
+ false
+}
+
--- /dev/null
+# This file contains the table used to generate old and new-style settings code
+#
+# Example:
+# {
+# 'name' : 'allow_from',
+# 'section' : 'incoming',
+# 'oldname' : 'allow-from'
+# 'type' : LType.ListSubnets,
+# 'default' : '127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10',
+# 'help' : 'If set, only allow these comma separated netmasks to recurse',
+# 'doc' : '''
+# '''
+# }
+#
+# See generate.py for a description of the fields.
+#
+# Sections
+# - incoming
+# - outgoing
+# - packetcache
+# - recursor
+# - recordcache
+# - dnssec
+# - webservice
+# - carbon
+# - ecs
+# - logging
+# - nod
+# - snmp
+
+[
+ {
+ 'name' : 'aggressive_nsec_cache_size',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '100000',
+ 'help' : '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',
+ 'doc' : '''
+The number of records to cache in the aggressive cache. If set to a value greater than 0, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in :rfc:`8198`.
+To use this, DNSSEC processing or validation must be enabled by setting :ref:`setting-dnssec` to ``process``, ``log-fail`` or ``validate``.
+ ''',
+ 'versionadded': '4.5.0',
+ },
+ {
+ 'name' : 'aggressive_cache_min_nsec3_hit_ratio',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '2000',
+ 'help' : 'The minimum expected hit ratio to store NSEC3 records into the aggressive cache',
+ 'doc' : '''
+The limit for which to put NSEC3 records into the aggressive cache.
+A value of ``n`` means that an NSEC3 record is only put into the aggressive cache if the estimated probability of a random name hitting the NSEC3 record is higher than ``1/n``.
+A higher ``n`` will cause more records to be put into the aggressive cache, e.g. a value of 4000 will cause records to be put in the aggressive cache even if the estimated probability of hitting them is twice as low as would be the case for ``n=2000``.
+A value of 0 means no NSEC3 records will be put into the aggressive cache.
+
+For large zones the effectiveness of the NSEC3 cache is reduced since each NSEC3 record only covers a randomly distributed subset of all possible names.
+This setting avoids doing unnecessary work for such large zones.
+ ''',
+ 'versionadded' : '4.9.0',
+ },
+ {
+ 'name' : 'allow_from',
+ 'section' : 'incoming',
+ 'type' : LType.ListSubnets,
+ 'default' : '127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10',
+ 'help' : 'If set, only allow these comma separated netmasks to recurse',
+ 'doc' : '''
+Netmasks (both IPv4 and IPv6) that are allowed to use the server.
+The default allows access only from :rfc:`1918` private IP addresses.
+An empty value means no checking is done, all clients are allowed.
+Due to the aggressive nature of the internet these days, it is highly recommended to not open up the recursor for the entire internet.
+Questions from IP addresses not listed here are ignored and do not get an answer.
+
+When the Proxy Protocol is enabled (see :ref:`setting-proxy-protocol-from`), the recursor will check the address of the client IP advertised in the Proxy Protocol header instead of the one of the proxy.
+
+Note that specifying an IP address without a netmask uses an implicit netmask of /32 or /128.
+ ''',
+ },
+ {
+ 'name' : 'allow_from_file',
+ 'section' : 'incoming',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set, load allowed netmasks from this file',
+ 'doc' : '''
+Like :ref:`setting-allow-from`, except reading from file.
+Overrides the :ref:`setting-allow-from` setting. To use this feature, supply one netmask per line, with optional comments preceded by a '#'.
+ ''',
+ 'doc-new' : '''
+Like :ref:`setting-allow-from`, except reading a sequence of `Subnet`_ from file.
+Overrides the :ref:`setting-allow-from` setting. Example content of th specified file:
+
+.. code-block:: yaml
+
+ - 127.0.01
+ - ::1
+
+ ''',
+ },
+ {
+ 'name' : 'allow_notify_for',
+ 'section' : 'incoming',
+ 'type' : LType.ListStrings,
+ 'default' : '',
+ 'help' : 'If set, NOTIFY requests for these zones will be allowed',
+ 'doc' : '''
+Domain names specified in this list are used to permit incoming
+NOTIFY operations to wipe any cache entries that match the domain
+name. If this list is empty, all NOTIFY operations will be ignored.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'allow_notify_for_file',
+ 'section' : 'incoming',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set, load NOTIFY-allowed zones from this file',
+ 'doc' : '''
+Like :ref:`setting-allow-notify-for`, except reading from file. To use this
+feature, supply one domain name per line, with optional comments
+preceded by a '#'.
+
+NOTIFY-allowed zones can also be specified using :ref:`setting-forward-zones-file`.
+ ''',
+ 'doc-new' : '''
+Like :ref:`setting-allow-notify-for`, except reading a sequence of names from file. Example contents of specified file:
+
+.. code-block:: yaml
+
+ - example.com
+ - example.org
+
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'allow_notify_from',
+ 'section' : 'incoming',
+ 'type' : LType.ListSubnets,
+ 'default' : '',
+ 'help' : 'If set, NOTIFY requests from these comma separated netmasks will be allowed',
+ 'doc' : '''
+Netmasks (both IPv4 and IPv6) that are allowed to issue NOTIFY operations
+to the server. NOTIFY operations from IP addresses not listed here are
+ignored and do not get an answer.
+
+When the Proxy Protocol is enabled (see :ref:`setting-proxy-protocol-from`), the
+recursor will check the address of the client IP advertised in the
+Proxy Protocol header instead of the one of the proxy.
+
+Note that specifying an IP address without a netmask uses an implicit
+netmask of /32 or /128.
+
+NOTIFY operations received from a client listed in one of these netmasks
+will be accepted and used to wipe any cache entries whose zones match
+the zone specified in the NOTIFY operation, but only if that zone (or
+one of its parents) is included in :ref:`setting-allow-notify-for`,
+:ref:`setting-allow-notify-for-file`, or :ref:`setting-forward-zones-file` with a '^' prefix.
+ ''',
+ 'doc-new' : '''
+Subnets (both IPv4 and IPv6) that are allowed to issue NOTIFY operations
+to the server. NOTIFY operations from IP addresses not listed here are
+ignored and do not get an answer.
+
+When the Proxy Protocol is enabled (see :ref:`setting-proxy-protocol-from`), the
+recursor will check the address of the client IP advertised in the
+Proxy Protocol header instead of the one of the proxy.
+
+Note that specifying an IP address without a netmask uses an implicit
+netmask of /32 or /128.
+
+NOTIFY operations received from a client listed in one of these netmasks
+will be accepted and used to initiate a freshness check for an RPZ zone or wipe any cache entries whose zones match
+the zone specified in the NOTIFY operation, but only if that zone (or
+one of its parents) is included in :ref:`setting-allow-notify-for`,
+:ref:`setting-allow-notify-for-file`, or :ref:`setting-forward-zones-file` with a ``allow_notify`` set to ``true``.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'allow_notify_from_file',
+ 'section' : 'incoming',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set, load NOTIFY-allowed netmasks from this file',
+ 'doc' : '''
+Like :ref:`setting-allow-notify-from`, except reading from file. To use this
+feature, supply one netmask per line, with optional comments preceded
+by a '#'.
+ ''',
+ 'doc-new' : '''
+Like :ref:`setting-allow-notify-from`, except reading a sequence of `Subnet`_ from file.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'allow_no_rd',
+ 'section' : 'incoming',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Allow \'no recursion desired (RD=0)\' queries.',
+ 'doc' : '''
+Allow ``no recursion desired (RD=0) queries`` to query cache contents.
+If not set (the default), these queries are answered with rcode ``Refused``.
+ ''',
+ 'versionadded': '5.0.0'
+ },
+ {
+ 'name' : 'any_to_tcp',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Answer ANY queries with tc=1, shunting to TCP',
+ 'doc' : '''
+Answer questions for the ANY type on UDP with a truncated packet that refers the remote server to TCP.
+Useful for mitigating ANY reflection attacks.
+ ''',
+ },
+ {
+ 'name' : 'allow_trust_anchor_query',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Allow queries for trustanchor.server CH TXT and negativetrustanchor.server CH TXT',
+ 'doc' : '''
+Allow ``trustanchor.server CH TXT`` and ``negativetrustanchor.server CH TXT`` queries to view the configured :doc:`DNSSEC <dnssec>` (negative) trust anchors.
+ ''',
+ 'versionadded': '4.3.0'
+ },
+ {
+ 'name' : 'api_dir',
+ 'section' : 'webservice',
+ 'oldname' : 'api-config-dir',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Directory where REST API stores config and zones',
+ 'doc' : '''
+Directory where the REST API stores its configuration and zones.
+For configuration updates to work, :ref:`setting-include-dir` should have the same value when using old-style settings.
+When using YAML settings :ref:`setting-yaml-recursor.include_dir` and :ref:`setting-yaml-webservice.api_dir` must have a different value.
+ ''',
+ 'versionadded': '4.0.0'
+ },
+ {
+ 'name' : 'api_key',
+ 'section' : 'webservice',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Static pre-shared authentication key for access to the REST API',
+ 'doc' : '''
+Static pre-shared authentication key for access to the REST API. Since 4.6.0 the key can be hashed and salted using ``rec_control hash-password`` instead of being stored in the configuration in plaintext, but the plaintext version is still supported.
+ ''',
+ 'versionadded': '4.0.0',
+ 'versionchanged': ('4.6.0', 'This setting now accepts a hashed and salted version.')
+ },
+ {
+ 'name' : 'auth_zones',
+ 'section' : 'recursor',
+ 'type' : LType.ListAuthZones,
+ 'default' : '',
+ 'help' : 'Zones for which we have authoritative data, comma separated domain=file pairs',
+ 'doc' : '''
+Zones read from these files (in BIND format) are served authoritatively (but without the AA bit set in responses).
+DNSSEC is not supported. Example:
+
+.. code-block:: none
+
+ auth-zones=example.org=/var/zones/example.org, powerdns.com=/var/zones/powerdns.com
+ ''',
+ 'doc-new' : '''
+Zones read from these files (in BIND format) are served authoritatively (but without the AA bit set in responses).
+DNSSEC is not supported. Example:
+
+.. code-block:: yaml
+
+ recursor:
+ auth-zones:
+ - zone: example.org
+ file: /var/zones/example.org
+ - zone: powerdns.com
+ file: /var/zones/powerdns.com
+ ''',
+ },
+ {
+ 'name' : 'interval',
+ 'section' : 'carbon',
+ 'oldname' : 'carbon-interval',
+ 'type' : LType.Uint64,
+ 'default' : '30',
+ 'help' : 'Number of seconds between carbon (graphite) updates',
+ 'doc' : '''
+If sending carbon updates, this is the interval between them in seconds.
+See :doc:`metrics`.
+ ''',
+ },
+ {
+ 'name' : 'ns',
+ 'section' : 'carbon',
+ 'oldname' : 'carbon-namespace',
+ 'type' : LType.String,
+ 'default' : 'pdns',
+ 'help' : 'If set overwrites the first part of the carbon string',
+ 'doc' : '''
+Change the namespace or first string of the metric key. The default is pdns.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'ourname',
+ 'section' : 'carbon',
+ 'oldname' : 'carbon-ourname',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set, overrides our reported hostname for carbon stats',
+ 'doc' : '''
+If sending carbon updates, if set, this will override our hostname.
+Be careful not to include any dots in this setting, unless you know what you are doing.
+See :ref:`metricscarbon`.
+ ''',
+ },
+ {
+ 'name' : 'instance',
+ 'section' : 'carbon',
+ 'oldname' : 'carbon-instance',
+ 'type' : LType.String,
+ 'default' : 'recursor',
+ 'help' : 'If set overwrites the instance name default',
+ 'doc' : '''
+Change the instance or third string of the metric key. The default is recursor.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'server',
+ 'section' : 'carbon',
+ 'oldname' : 'carbon-server',
+ 'type' : LType.ListSocketAddresses,
+ 'default' : '',
+ 'help' : 'If set, send metrics in carbon (graphite) format to this server IP address',
+ 'doc' : '''
+If set to an IP or IPv6 address, will send all available metrics to this server via the carbon protocol, which is used by graphite and metronome. Moreover you can specify more than one server using a comma delimited list, ex: carbon-server=10.10.10.10,10.10.10.20.
+You may specify an alternate port by appending :port, for example: ``127.0.0.1:2004``.
+See :doc:`metrics`.
+ ''',
+ 'doc-new' : '''
+Will send all available metrics to these servers via the carbon protocol, which is used by graphite and metronome.
+See :doc:`metrics`.
+ ''',
+ },
+ {
+ 'name' : 'chroot',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'switch to chroot jail',
+ 'doc' : '''
+If set, chroot to this directory for more security.
+This is not recommended; instead, we recommend containing PowerDNS using operating system features.
+We ship systemd unit files with our packages to make this easy.
+
+Make sure that ``/dev/log`` is available from within the chroot.
+Logging will silently fail over time otherwise (on logrotate).
+
+When using ``chroot``, all other paths (except for :ref:`setting-config-dir`) set in the configuration are relative to the new root.
+
+When running on a system where systemd manages services, ``chroot`` does not work out of the box, as PowerDNS cannot use the ``NOTIFY_SOCKET``.
+Either do not ``chroot`` on these systems or set the 'Type' of this service to 'simple' instead of 'notify' (refer to the systemd documentation on how to modify unit-files).
+ ''',
+ },
+ {
+ 'name' : 'tcp_timeout',
+ 'section' : 'incoming',
+ 'oldname' : 'client-tcp-timeout',
+ 'type' : LType.Uint64,
+ 'default' : '2',
+ 'help' : 'Timeout in seconds when talking to TCP clients',
+ 'doc' : '''
+Time to wait for data from TCP clients.
+ ''',
+ },
+ {
+ 'name' : 'config',
+ 'section' : 'commands',
+ 'type' : LType.Command,
+ 'default' : 'no',
+ 'help' : 'Output blank configuration. You can use --config=check to test the config file and command line arguments.',
+ 'doc' : '''
+EMPTY? '''
+ },
+ {
+ 'name' : 'config_dir',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : 'SYSCONFDIR',
+ 'docdefault': 'Determined by distribution',
+ 'help' : 'Location of configuration directory (recursor.conf or recursor.yml)',
+ 'doc' : '''
+Location of configuration directory (where ``recursor.conf`` or ``recursor.yml`` is stored).
+Usually ``/etc/powerdns``, but this depends on ``SYSCONFDIR`` during compile-time.
+Use default or set on command line.
+ ''',
+ },
+ {
+ 'name' : 'config_name',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Name of this virtual configuration - will rename the binary image',
+ 'doc' : '''
+When running multiple recursors on the same server, read settings from :file:`recursor-{name}.conf`, this will also rename the binary image.
+ ''',
+ },
+ {
+ 'name' : 'cpu_map',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Thread to CPU mapping, space separated thread-id=cpu1,cpu2..cpuN pairs',
+ 'doc' : '''
+Set CPU affinity for threads, asking the scheduler to run those threads on a single CPU, or a set of CPUs.
+This parameter accepts a space separated list of thread-id=cpu-id, or thread-id=cpu-id-1,cpu-id-2,...,cpu-id-N.
+For example, to make the worker thread 0 run on CPU id 0 and the worker thread 1 on CPUs 1 and 2::
+
+ cpu-map=0=0 1=1,2
+
+The thread handling the control channel, the webserver and other internal stuff has been assigned id 0, the distributor
+threads if any are assigned id 1 and counting, and the worker threads follow behind.
+The number of distributor threads is determined by :ref:`setting-distributor-threads`, the number of worker threads is determined by the :ref:`setting-threads` setting.
+
+This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function.
+
+Note that depending on the configuration the Recursor can start more threads.
+Typically these threads will sleep most of the time.
+These threads cannot be specified in this setting as their thread-ids are left unspecified.
+ ''',
+ 'doc' : '''
+Set CPU affinity for threads, asking the scheduler to run those threads on a single CPU, or a set of CPUs.
+This parameter accepts a space separated list of thread-id=cpu-id, or thread-id=cpu-id-1,cpu-id-2,...,cpu-id-N.
+For example, to make the worker thread 0 run on CPU id 0 and the worker thread 1 on CPUs 1 and 2:
+
+.. code-block:: yaml
+
+ recursor:
+ cpu_map: 0=0 1=1,2
+
+The thread handling the control channel, the webserver and other internal stuff has been assigned id 0, the distributor
+threads if any are assigned id 1 and counting, and the worker threads follow behind.
+The number of distributor threads is determined by :ref:`setting-distributor-threads`, the number of worker threads is determined by the :ref:`setting-threads` setting.
+
+This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function.
+
+Note that depending on the configuration the Recursor can start more threads.
+Typically these threads will sleep most of the time.
+These threads cannot be specified in this setting as their thread-ids are left unspecified.
+ ''',
+ },
+ {
+ 'name' : 'daemon',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Operate as a daemon',
+ 'doc' : '''
+Operate in the background.
+ ''',
+ 'versionchanged': ('4.0.0', 'Default is now ``no``, was ``yes`` before.')
+ },
+ {
+ 'name' : 'dont_throttle_names',
+ 'section' : 'outgoing',
+ 'type' : LType.ListStrings,
+ 'default' : '',
+ 'help' : 'Do not throttle nameservers with this name or suffix',
+ 'doc' : '''
+When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
+Any servers' name suffix-matching the supplied names will never be throttled.
+
+.. warning::
+ Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-names`` could make this load on the upstream server even higher, resulting in further service degradation.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'dont_throttle_netmasks',
+ 'section' : 'outgoing',
+ 'type' : LType.ListSubnets,
+ 'default' : '',
+ 'help' : 'Do not throttle nameservers with this IP netmask',
+ 'doc' : '''
+When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
+Any servers matching the supplied netmasks will never be throttled.
+
+This can come in handy on lossy networks when forwarding, where the same server is configured multiple times (e.g. with ``forward-zones-recurse=example.com=192.0.2.1;192.0.2.1``).
+By default, the PowerDNS Recursor would throttle the 'first' server on a timeout and hence not retry the 'second' one.
+In this case, ``dont-throttle-netmasks`` could be set to ``192.0.2.1``.
+
+.. warning::
+ Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-netmasks`` could make this load on the upstream server even higher, resulting in further service degradation.
+ ''',
+ 'doc-new' : '''
+When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
+Any servers matching the supplied netmasks will never be throttled.
+
+This can come in handy on lossy networks when forwarding, where the same server is configured multiple times (e.g. with ``forward_zones_recurse: [ {zone: example.com, forwarders: [ 192.0.2.1, 192.0.2.1 ] } ]``.
+By default, the PowerDNS Recursor would throttle the 'first' server on a timeout and hence not retry the 'second' one.
+In this case, :ref:`setting-dont-throttle-netmasks` could be set to include ``192.0.2.1``.
+
+.. warning::
+ Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-netmasks`` could make this load on the upstream server even higher, resulting in further service degradation.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'devonly_regression_test_mode',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'internal use only',
+ 'doc' : 'SKIP',
+ },
+ {
+ 'name' : 'disable',
+ 'section' : 'packetcache',
+ 'oldname' : 'disable-packetcache',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Disable packetcache',
+ 'doc' : '''
+Turn off the packet cache. Useful when running with Lua scripts that cannot be cached, though individual query caching can be controlled from Lua as well.
+ ''',
+ },
+ {
+ 'name' : 'disable_syslog',
+ 'section' : 'logging',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Disable logging to syslog, useful when running inside a supervisor that logs stderr',
+ 'doc' : '''
+Do not log to syslog, only to stderr.
+Use this setting when running inside a supervisor that handles logging (like systemd).
+**Note**: do not use this setting in combination with :ref:`setting-daemon` as all logging will disappear.
+ ''',
+ },
+ {
+ 'name' : 'distribution_load_factor',
+ 'section' : 'incoming',
+ 'type' : LType.Double,
+ 'default' : '0.0',
+ 'help' : 'The load factor used when PowerDNS is distributing queries to worker threads',
+ 'doc' : '''
+If :ref:`setting-pdns-distributes-queries` is set and this setting is set to another value
+than 0, the distributor thread will use a bounded load-balancing algorithm while
+distributing queries to worker threads, making sure that no thread is assigned
+more queries than distribution-load-factor times the average number of queries
+currently processed by all the workers.
+For example, with a value of 1.25, no server should get more than 125 % of the
+average load. This helps making sure that all the workers have roughly the same
+share of queries, even if the incoming traffic is very skewed, with a larger
+number of requests asking for the same qname.
+ ''',
+ 'versionadded': '4.1.12'
+ },
+ {
+ 'name' : 'distribution_pipe_buffer_size',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread',
+ 'doc' : '''
+Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread.
+Requires support for `F_SETPIPE_SZ` which is present in Linux since 2.6.35. The actual size might be rounded up to
+a multiple of a page size. 0 means that the OS default size is used.
+A large buffer might allow the recursor to deal with very short-lived load spikes during which a worker thread gets
+overloaded, but it will be at the cost of an increased latency.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'distributor_threads',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'docdefault' : '1 if :ref:`setting-pdns-distributes-queries` is set, 0 otherwise',
+ 'help' : 'Launch this number of distributor threads, distributing queries to other threads',
+ 'doc' : '''
+If :ref:`setting-pdns-distributes-queries` is set, spawn this number of distributor threads on startup. Distributor threads
+handle incoming queries and distribute them to other threads based on a hash of the query.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'dot_to_auth_names',
+ 'section' : 'outgoing',
+ 'type' : LType.ListStrings,
+ 'default' : '',
+ 'help' : 'Use DoT to authoritative servers with these names or suffixes',
+ 'doc' : '''
+Force DoT to the listed authoritative nameservers. For this to work, DoT support has to be compiled in.
+Currently, the certificate is not checked for validity in any way.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'dot_to_port_853',
+ 'section' : 'outgoing',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Force DoT connection to target port 853 if DoT compiled in',
+ 'doc' : '''
+Enable DoT to forwarders that specify port 853.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'dns64_prefix',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'DNS64 prefix',
+ 'doc' : '''
+Enable DNS64 (:rfc:`6147`) support using the supplied /96 IPv6 prefix. This will generate 'fake' ``AAAA`` records for names
+with only ``A`` records, as well as 'fake' ``PTR`` records to make sure that reverse lookup of DNS64-generated IPv6 addresses
+generate the right name.
+See :doc:`dns64` for more flexible but slower alternatives using Lua.
+ ''',
+ 'versionadded': '4.4.0'
+ },
+ {
+ 'name' : 'validation',
+ 'section' : 'dnssec',
+ 'oldname' : 'dnssec',
+ 'type' : LType.String,
+ 'default' : 'process',
+ 'help' : 'DNSSEC mode: off/process-no-validate/process (default)/log-fail/validate',
+ 'doc' : '''
+One of ``off``, ``process-no-validate``, ``process``, ``log-fail``, ``validate``
+
+Set the mode for DNSSEC processing, as detailed in :doc:`dnssec`.
+
+``off``
+ No DNSSEC processing whatsoever.
+ Ignore DO-bits in queries, don't request any DNSSEC information from authoritative servers.
+ This behaviour is similar to PowerDNS Recursor pre-4.0.
+``process-no-validate``
+ Respond with DNSSEC records to clients that ask for it, set the DO bit on all outgoing queries.
+ Don't do any validation.
+``process``
+ Respond with DNSSEC records to clients that ask for it, set the DO bit on all outgoing queries.
+ Do validation for clients that request it (by means of the AD- bit or DO-bit in the query).
+``log-fail``
+ Similar behaviour to ``process``, but validate RRSIGs on responses and log bogus responses.
+``validate``
+ Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses.
+ ''',
+ 'versionadded': '4.0.0',
+ 'versionchanged': ('4.5.0',
+ 'The default changed from ``process-no-validate`` to ``process``')
+ },
+ {
+ 'name' : 'disabled_algorithms',
+ 'section' : 'dnssec',
+ 'oldname' : 'dnssec-disabled-algorithms',
+ 'type' : LType.ListStrings,
+ 'default' : '',
+ 'help' : 'List of DNSSEC algorithm numbers that are considered unsupported',
+ 'doc' : '''
+A list of DNSSEC algorithm numbers that should be considered disabled.
+These algorithms will not be used to validate DNSSEC signatures.
+Zones (only) signed with these algorithms will be considered ``Insecure``.
+
+If this setting is empty (the default), :program:`Recursor` will determine which algorithms to disable automatically.
+This is done for specific algorithms only, currently algorithms 5 (``RSASHA1``) and 7 (``RSASHA1NSEC3SHA1``).
+
+This is important on systems that have a default strict crypto policy, like RHEL9 derived systems.
+On such systems not disabling some algorithms (or changing the security policy) will make affected zones to be considered ``Bogus`` as using these algorithms fails.
+ ''',
+ 'versionadded': '4.9.0'
+ },
+ {
+ 'name' : 'log_bogus',
+ 'section' : 'dnssec',
+ 'oldname' : 'dnssec-log-bogus',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Log DNSSEC bogus validations',
+ 'doc' : '''
+Log every DNSSEC validation failure.
+**Note**: This is not logged per-query but every time records are validated as Bogus.
+ ''',
+ },
+ {
+ 'name' : 'dont_query',
+ 'section' : 'outgoing',
+ 'type' : LType.ListSubnets,
+ 'default' : '127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10, 0.0.0.0/8, 192.0.0.0/24, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 240.0.0.0/4, ::/96, ::ffff:0:0/96, 100::/64, 2001:db8::/32',
+ 'help' : 'If set, do not query these netmasks for DNS data',
+ 'doc' : '''
+The DNS is a public database, but sometimes contains delegations to private IP addresses, like for example 127.0.0.1.
+This can have odd effects, depending on your network, and may even be a security risk.
+Therefore, the PowerDNS Recursor by default does not query private space IP addresses.
+This setting can be used to expand or reduce the limitations.
+
+Queries for names in forward zones and to addresses as configured in any of the settings :ref:`setting-forward-zones`, :ref:`setting-forward-zones-file` or :ref:`setting-forward-zones-recurse` are performed regardless of these limitations.
+ ''',
+ },
+ {
+ 'name' : 'add_for',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-add-for',
+ 'type' : LType.ListSubnets,
+ 'default' : '0.0.0.0/0, ::/0, !127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10',
+ 'help' : 'List of client netmasks for which EDNS Client Subnet will be added',
+ 'doc' : '''
+List of requestor netmasks for which the requestor IP Address should be used as the :rfc:`EDNS Client Subnet <7871>` for outgoing queries. Outgoing queries for requestors that do not match this list will use the :ref:`setting-ecs-scope-zero-address` instead.
+Valid incoming ECS values from :ref:`setting-use-incoming-edns-subnet` are not replaced.
+
+Regardless of the value of this setting, ECS values are only sent for outgoing queries matching the conditions in the :ref:`setting-edns-subnet-allow-list` setting. This setting only controls the actual value being sent.
+
+This defaults to not using the requestor address inside RFC1918 and similar 'private' IP address spaces.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'ipv4_bits',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-ipv4-bits',
+ 'type' : LType.Uint64,
+ 'default' : '24',
+ 'help' : 'Number of bits of IPv4 address to pass for EDNS Client Subnet',
+ 'doc' : '''
+Number of bits of client IPv4 address to pass when sending EDNS Client Subnet address information.
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'ipv4_cache_bits',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-ipv4-cache-bits',
+ 'type' : LType.Uint64,
+ 'default' : '24',
+ 'help' : 'Maximum number of bits of IPv4 mask to cache ECS response',
+ 'doc' : '''
+Maximum number of bits of client IPv4 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
+That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
+ ''',
+ 'versionadded': '4.1.12'
+ },
+ {
+ 'name' : 'ipv6_bits',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-ipv6-bits',
+ 'type' : LType.Uint64,
+ 'default' : '56',
+ 'help' : 'Number of bits of IPv6 address to pass for EDNS Client Subnet',
+ 'doc' : '''
+Number of bits of client IPv6 address to pass when sending EDNS Client Subnet address information.
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'ipv6_cache_bits',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-ipv6-cache-bits',
+ 'type' : LType.Uint64,
+ 'default' : '56',
+ 'help' : 'Maximum number of bits of IPv6 mask to cache ECS response',
+ 'doc' : '''
+Maximum number of bits of client IPv6 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
+That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
+ ''',
+ 'versionadded': '4.1.12'
+ },
+ {
+ 'name' : 'ipv4_never_cache',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-ipv4-never-cache',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'If we should never cache IPv4 ECS responses',
+ 'doc' : '''
+When set, never cache replies carrying EDNS IPv4 Client Subnet scope in the record cache.
+In this case the decision made by ```ecs-ipv4-cache-bits`` and ``ecs-cache-limit-ttl`` is no longer relevant.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'ipv6_never_cache',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-ipv6-never-cache',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'If we should never cache IPv6 ECS responses',
+ 'doc' : '''
+When set, never cache replies carrying EDNS IPv6 Client Subnet scope in the record cache.
+In this case the decision made by ```ecs-ipv6-cache-bits`` and ``ecs-cache-limit-ttl`` is no longer relevant.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'minimum_ttl_override',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-minimum-ttl-override',
+ 'type' : LType.Uint64,
+ 'default' : '1',
+ 'help' : 'The minimum TTL for records in ECS-specific answers',
+ 'doc' : '''
+This setting artificially raises the TTLs of records in the ANSWER section of ECS-specific answers to be at least this long.
+Setting this to a value greater than 1 technically is an RFC violation, but might improve performance a lot.
+Using a value of 0 impacts performance of TTL 0 records greatly, since it forces the recursor to contact
+authoritative servers every time a client requests them.
+Can be set at runtime using ``rec_control set-ecs-minimum-ttl 3600``.
+ ''',
+ 'versionchanged': ('4.5.0', 'Old versions used default 0.')
+ },
+ {
+ 'name' : 'cache_limit_ttl',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-cache-limit-ttl',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Minimum TTL to cache ECS response',
+ 'doc' : '''
+The minimum TTL for an ECS-specific answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-ipv4-cache-bits`` or ``ecs-ipv6-cache-bits``.
+That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
+ ''',
+ 'versionadded': '4.1.12'
+ },
+ {
+ 'name' : 'scope_zero_address',
+ 'section' : 'ecs',
+ 'oldname' : 'ecs-scope-zero-address',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Address to send to allow-listed authoritative servers for incoming queries with ECS prefix-length source of 0',
+ 'doc' : '''
+The IP address sent via EDNS Client Subnet to authoritative servers listed in
+:ref:`setting-edns-subnet-allow-list` when :ref:`setting-use-incoming-edns-subnet` is set and the query has
+an ECS source prefix-length set to 0.
+The default is to look for the first usable (not an ``any`` one) address in
+:ref:`setting-query-local-address` (starting with IPv4). If no suitable address is
+found, the recursor fallbacks to sending 127.0.0.1.
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'edns_bufsize',
+ 'section' : 'outgoing',
+ 'oldname' : 'edns-outgoing-bufsize',
+ 'type' : LType.Uint64,
+ 'default' : '1232',
+ 'help' : 'Outgoing EDNS buffer size',
+ 'doc' : '''
+.. note:: Why 1232?
+
+ 1232 is the largest number of payload bytes that can fit in the smallest IPv6 packet.
+ IPv6 has a minimum MTU of 1280 bytes (:rfc:`RFC 8200, section 5 <8200#section-5>`), minus 40 bytes for the IPv6 header, minus 8 bytes for the UDP header gives 1232, the maximum payload size for the DNS response.
+
+This is the value set for the EDNS0 buffer size in outgoing packets.
+Lower this if you experience timeouts.
+ ''',
+ 'versionchanged': ('4.2.0', 'Before 4.2.0, the default was 1680')
+ },
+ {
+ 'name' : 'edns_padding_from',
+ 'section' : 'incoming',
+ 'type' : LType.ListSubnets,
+ 'default' : '',
+ 'help' : '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',
+ 'doc' : '''
+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 :ref:`setting-edns-padding-mode` applies.
+ ''',
+ 'versionadded' : '4.5.0',
+ 'versionchanged' : ('5.0.4', 'YAML settings only: previously this was defined as a string instead of a sequence')
+ },
+ {
+ 'name' : 'edns_padding_mode',
+ 'section' : 'incoming',
+ 'type' : LType.String,
+ 'default' : 'padded-queries-only',
+ 'help' : '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 \'setting-edns-padding-from\' sources',
+ 'doc' : '''
+One of ``always``, ``padded-queries-only``.
+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 :ref:`setting-edns-padding-from` sources.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'edns_padding',
+ 'section' : 'outgoing',
+ 'oldname' : 'edns-padding-out',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Whether to add EDNS padding to outgoing DoT messages',
+ 'doc' : '''
+Whether to add EDNS padding to outgoing DoT queries.
+ ''',
+ 'versionadded': '4.8.0'
+ },
+ {
+ 'name' : 'edns_padding_tag',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '7830',
+ 'help' : 'Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to clients for which padding is not enabled.',
+ 'doc' : '''
+The packetcache tag to use for padded responses, to prevent a client not allowed by the :ref::`setting-edns-padding-from` list to be served a cached answer generated for an allowed one. This
+effectively divides the packet cache in two when :ref:`setting-edns-padding-from` is used. Note that this will not override a tag set from one of the ``Lua`` hooks.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'edns_subnet_whitelist',
+ 'section' : 'outgoing',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'List of netmasks and domains that we should enable EDNS subnet for (deprecated)',
+ 'doc' : '',
+ 'deprecated': ('4.5.0', 'Use :ref:`setting-edns-subnet-allow-list`.'),
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'edns_subnet_allow_list',
+ 'section' : 'outgoing',
+ 'type' : LType.ListStrings,
+ 'default' : '',
+ 'help' : 'List of netmasks and domains that we should enable EDNS subnet for',
+ 'doc' : '''
+List of netmasks and domains that :rfc:`EDNS Client Subnet <7871>` should be enabled for in outgoing queries.
+
+For example, an EDNS Client Subnet option containing the address of the initial requestor (but see :ref:`setting-ecs-add-for`) will be added to an outgoing query sent to server 192.0.2.1 for domain X if 192.0.2.1 matches one of the supplied netmasks, or if X matches one of the supplied domains.
+The initial requestor address will be truncated to 24 bits for IPv4 (see :ref:`setting-ecs-ipv4-bits`) and to 56 bits for IPv6 (see :ref:`setting-ecs-ipv6-bits`), as recommended in the privacy section of RFC 7871.
+
+
+Note that this setting describes the destination of outgoing queries, not the sources of incoming queries, nor the subnets described in the EDNS Client Subnet option.
+
+By default, this option is empty, meaning no EDNS Client Subnet information is sent.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'entropy_source',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '/dev/urandom',
+ 'help' : 'If set, read entropy from this file',
+ 'doc' : '''
+PowerDNS can read entropy from a (hardware) source.
+This is used for generating random numbers which are very hard to predict.
+Generally on UNIX platforms, this source will be ``/dev/urandom``, which will always supply random numbers, even if entropy is lacking.
+Change to ``/dev/random`` if PowerDNS should block waiting for enough entropy to arrive.
+ ''',
+ 'skip-yaml': True,
+ 'versionchanged': ('4.9.0', 'This setting is no longer used.'),
+ },
+ {
+ 'name' : 'etc_hosts_file',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '/etc/hosts',
+ 'help' : 'Path to \'hosts\' file',
+ 'doc' : '''
+The path to the /etc/hosts file, or equivalent.
+This file can be used to serve data authoritatively using :ref:`setting-export-etc-hosts`.
+ ''',
+ },
+ {
+ 'name' : 'event_trace_enabled',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'If set, event traces are collected and send out via protobuf logging (1), logfile (2) or both(3)',
+ 'doc' : '''
+Enable the recording and logging of ref:`event traces`. This is an experimental feature and subject to change.
+Possible values are 0: (disabled), 1 (add information to protobuf logging messages) and 2 (write to log) and 3 (both).
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'export_etc_hosts',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'If we should serve up contents from /etc/hosts',
+ 'doc' : '''
+If set, this flag will export the host names and IP addresses mentioned in ``/etc/hosts``.
+ ''',
+ },
+ {
+ 'name' : 'export_etc_hosts_search_suffix',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Also serve up the contents of /etc/hosts with this suffix',
+ 'doc' : '''
+If set, all hostnames in the :ref:`setting-export-etc-hosts` file are loaded in canonical form, based on this suffix, unless the name contains a '.', in which case the name is unchanged.
+So an entry called 'pc' with ``export-etc-hosts-search-suffix='home.com'`` will lead to the generation of 'pc.home.com' within the recursor.
+An entry called 'server1.home' will be stored as 'server1.home', regardless of this setting.
+ ''',
+ },
+ {
+ 'name' : 'extended_resolution_errors',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'If set, send an EDNS Extended Error extension on resolution failures, like DNSSEC validation errors',
+ 'doc' : '''
+If set, the recursor will add an EDNS Extended Error (:rfc:`8914`) to responses when resolution failed, like DNSSEC validation errors, explaining the reason it failed. This setting is not needed to allow setting custom error codes from Lua or from a RPZ hit.
+ ''',
+ 'versionadded': '4.5.0',
+ 'versionchanged': ('5.0.0', 'Default changed to enabled, previously it was disabled.'),
+ },
+ {
+ 'name' : 'forward_zones',
+ 'section' : 'recursor',
+ 'type' : LType.ListForwardZones,
+ 'default' : '',
+ 'help' : 'Zones for which we forward queries, comma separated domain=ip pairs',
+ 'doc' : '''
+Queries for zones listed here will be forwarded to the IP address listed. i.e.
+
+.. code-block:: none
+
+ forward-zones=example.org=203.0.113.210, powerdns.com=2001:DB8::BEEF:5
+
+Multiple IP addresses can be specified and port numbers other than 53 can be configured:
+
+.. code-block:: none
+
+ forward-zones=example.org=203.0.113.210:5300;127.0.0.1, powerdns.com=127.0.0.1;198.51.100.10:530;[2001:DB8::1:3]:5300
+
+Forwarded queries have the ``recursion desired (RD)`` bit set to ``0``, meaning that this setting is intended to forward queries to authoritative servers.
+If an ``NS`` record set for a subzone of the forwarded zone is learned, that record set will be used to determine addresses for name servers of the subzone.
+This allows e.g. a forward to a local authoritative server holding a copy of the root zone, delegations received from that server will work.
+
+**IMPORTANT**: When using DNSSEC validation (which is default), forwards to non-delegated (e.g. internal) zones that have a DNSSEC signed parent zone will validate as Bogus.
+To prevent this, add a Negative Trust Anchor (NTA) for this zone in the :ref:`setting-lua-config-file` with ``addNTA('your.zone', 'A comment')``.
+If this forwarded zone is signed, instead of adding NTA, add the DS record to the :ref:`setting-lua-config-file`.
+See the :doc:`dnssec` information.
+ ''',
+ 'doc-new' : '''
+Queries for zones listed here will be forwarded to the IP address listed. i.e.
+
+.. code-block:: yaml
+
+ recursor:
+ forward-zones:
+ - zone: example.org
+ forwarders:
+ - 203.0.113.210
+ - zone: powerdns.com
+ forwarders:
+ - 2001:DB8::BEEF:5
+
+Multiple IP addresses can be specified and port numbers other than 53 can be configured:
+
+.. code-block:: yaml
+
+ recursor:
+ forward-zones:
+ - zone: example.org
+ forwarders:
+ - 203.0.113.210:5300
+ - 127.0.0.1
+ - zone: powerdns.com
+ forwarders:
+ - 127.0.0.1
+ - 198.51.100.10:530
+ - '[2001:DB8::1:3]:5300'
+
+Forwarded queries have the ``recursion desired (RD)`` bit set to ``0``, meaning that this setting is intended to forward queries to authoritative servers.
+If an ``NS`` record set for a subzone of the forwarded zone is learned, that record set will be used to determine addresses for name servers of the subzone.
+This allows e.g. a forward to a local authoritative server holding a copy of the root zone, delegations received from that server will work.
+
+**IMPORTANT**: When using DNSSEC validation (which is default), forwards to non-delegated (e.g. internal) zones that have a DNSSEC signed parent zone will validate as Bogus.
+To prevent this, add a Negative Trust Anchor (NTA) for this zone in the :ref:`setting-lua-config-file` with ``addNTA('your.zone', 'A comment')``.
+If this forwarded zone is signed, instead of adding NTA, add the DS record to the :ref:`setting-lua-config-file`.
+See the :doc:`dnssec` information.
+ ''',
+ },
+ {
+ 'name' : 'forward_zones_file',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'File with (+)domain=ip pairs for forwarding',
+ 'doc' : '''
+Same as :ref:`setting-forward-zones`, parsed from a file. Only 1 zone is allowed per line, specified as follows:
+
+.. code-block:: none
+
+ example.org=203.0.113.210, 192.0.2.4:5300
+
+Zones prefixed with a ``+`` are treated as with
+:ref:`setting-forward-zones-recurse`. Default behaviour without ``+`` is as with
+:ref:`setting-forward-zones`.
+
+The DNSSEC notes from :ref:`setting-forward-zones` apply here as well.
+ ''',
+ 'doc-new' : '''
+ Same as :ref:`setting-forward-zones`, parsed from a file as a sequence of `ZoneForward`.
+
+.. code-block:: yaml
+
+ - zone: example1.com
+ forwarders:
+ - 127.0.0.1
+ - 127.0.0.1:5353
+ - '[::1]53'
+ - zone: example2.com
+ forwarders:
+ - ::1
+ recurse: true
+ notify_allowed: true
+
+The DNSSEC notes from :ref:`setting-forward-zones` apply here as well.
+ ''',
+ 'versionchanged': [('4.0.0', '(Old style settings only) Comments are allowed, everything behind ``#`` is ignored.'),
+ ('4.6.0', '(Old style settings only) Zones prefixed with a ``^`` are added to the :ref:`setting-allow-notify-for` list. Both prefix characters can be used if desired, in any order.')],
+ },
+ {
+ 'name' : 'forward_zones_recurse',
+ 'section' : 'recursor',
+ 'type' : LType.ListForwardZones,
+ 'default' : '',
+ 'help' : 'Zones for which we forward queries with recursion bit, comma separated domain=ip pairs',
+ 'doc' : '''
+Like regular :ref:`setting-forward-zones`, but forwarded queries have the ``recursion desired (RD)`` bit set to ``1``, meaning that this setting is intended to forward queries to other recursive servers.
+In contrast to regular forwarding, the rule that delegations of the forwarded subzones are respected is not active.
+This is because we rely on the forwarder to resolve the query fully.
+
+See :ref:`setting-forward-zones` for additional options (such as supplying multiple recursive servers) and an important note about DNSSEC.
+ ''',
+ },
+ {
+ 'name' : 'gettag_needs_edns_options',
+ 'section' : 'incoming',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'If EDNS Options should be extracted before calling the gettag() hook',
+ 'doc' : '''
+If set, EDNS options in incoming queries are extracted and passed to the :func:`gettag` hook in the ``ednsoptions`` table.
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'help',
+ 'section' : 'commands',
+ 'type' : LType.Command,
+ 'default' : 'no',
+ 'help' : 'Provide a helpful message',
+ 'doc' : '''
+EMPTY? '''
+ },
+ {
+ 'name' : 'hint_file',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set, load root hints from this file',
+ 'doc' : '''
+If set, the root-hints are read from this file. If empty, the default built-in root hints are used.
+
+In some special cases, processing the root hints is not needed, for example when forwarding all queries to another recursor.
+For these special cases, it is possible to disable the processing of root hints by setting the value to ``no`` or ``no-refresh``.
+See :ref:`handling-of-root-hints` for more information on root hints handling.
+ ''',
+ 'versionchanged': [('4.6.2', 'Introduced the value ``no`` to disable root-hints processing.'),
+ ('4.9.0', 'Introduced the value ``no-refresh`` to disable both root-hints processing and periodic refresh of the cached root `NS` records.')]
+ },
+ {
+ 'name' : 'ignore_unknown_settings',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : '',
+ 'help' : 'Configuration settings to ignore if they are unknown',
+ 'doc' : '''
+Names of settings to be ignored while parsing configuration files, if the setting
+name is unknown to PowerDNS.
+
+Useful during upgrade testing.
+ ''',
+ },
+ {
+ 'name' : 'include_dir',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Include *.conf files from this directory',
+ 'doc' : '''
+Directory to scan for additional config files. All files that end with .conf are loaded in order using ``POSIX`` as locale.
+ ''',
+ },
+ {
+ 'name' : 'latency_statistic_size',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '10000',
+ 'help' : 'Number of latency values to calculate the qa-latency average',
+ 'doc' : '''
+Indication of how many queries will be averaged to get the average latency reported by the 'qa-latency' metric.
+ ''',
+ },
+ {
+ 'name' : 'listen',
+ 'section' : 'incoming',
+ 'oldname' : 'local-address',
+ 'type' : LType.ListSocketAddresses,
+ 'default' : '127.0.0.1',
+ 'help' : 'IP addresses to listen on, separated by spaces or commas. Also accepts ports.',
+ 'doc' : '''
+Local IP addresses to which we bind. Each address specified can
+include a port number; if no port is included then the
+:ref:`setting-local-port` port will be used for that address. If a
+port number is specified, it must be separated from the address with a
+':'; for an IPv6 address the address must be enclosed in square
+brackets.
+
+Examples::
+
+ local-address=127.0.0.1 ::1
+ local-address=0.0.0.0:5353
+ local-address=[::]:8053
+ local-address=127.0.0.1:53, [::1]:5353
+ ''',
+ 'doc-new' : '''
+Local IP addresses to which we bind. Each address specified can
+include a port number; if no port is included then the
+:ref:`setting-local-port` port will be used for that address. If a
+port number is specified, it must be separated from the address with a
+':'; for an IPv6 address the address must be enclosed in square
+brackets.
+
+Example:
+
+.. code-block:: yaml
+
+ incoming:
+ listen:
+ - 127.0.0.1
+ - listen: '[::1]:5353'
+ - listen: '::'
+ ''',
+ },
+ {
+ 'name' : 'port',
+ 'section' : 'incoming',
+ 'oldname' : 'local-port',
+ 'type' : LType.Uint64,
+ 'default' : '53',
+ 'help' : 'port to listen on',
+ 'doc' : '''
+Local port to bind to.
+If an address in :ref:`setting-local-address` does not have an explicit port, this port is used.
+ ''',
+ },
+ {
+ 'name' : 'timestamp',
+ 'section' : 'logging',
+ 'oldname' : 'log-timestamp',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Print timestamps in log lines, useful to disable when running with a tool that timestamps stderr already',
+ 'doc' : '''
+
+ ''',
+ },
+ {
+ 'name' : 'non_local_bind',
+ 'section' : 'incoming',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Enable binding to non-local addresses by using FREEBIND / BINDANY socket options',
+ 'doc' : '''
+Bind to addresses even if one or more of the :ref:`setting-local-address`'s do not exist on this server.
+Setting this option will enable the needed socket options to allow binding to non-local addresses.
+This feature is intended to facilitate ip-failover setups, but it may also mask configuration issues and for this reason it is disabled by default.
+ ''',
+ },
+ {
+ 'name' : 'loglevel',
+ 'section' : 'logging',
+ 'type' : LType.Uint64,
+ 'default' : '6',
+ 'help' : 'Amount of logging. Higher is more. Do not set below 3',
+ 'doc' : '''
+Amount of logging. The higher the number, the more lines logged.
+Corresponds to ``syslog`` level values (e.g. 0 = ``emergency``, 1 = ``alert``, 2 = ``critical``, 3 = ``error``, 4 = ``warning``, 5 = ``notice``, 6 = ``info``, 7 = ``debug``).
+Each level includes itself plus the lower levels before it.
+Not recommended to set this below 3.
+If :ref:`setting-quiet` is ``no/false``, :ref:`setting-loglevel` will be minimally set to ``6 (info)``.
+ ''',
+ 'versionchanged': ('5.0.0', 'Previous version would not allow setting a level below ``3 (error)``.')
+ },
+ {
+ 'name' : 'common_errors',
+ 'section' : 'logging',
+ 'oldname' : 'log-common-errors',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'If we should log rather common errors',
+ 'doc' : '''
+Some DNS errors occur rather frequently and are no cause for alarm.
+ ''',
+ },
+ {
+ 'name' : 'rpz_changes',
+ 'section' : 'logging',
+ 'oldname' : 'log-rpz-changes',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Log additions and removals to RPZ zones at Info level',
+ 'doc' : '''
+Log additions and removals to RPZ zones at Info (6) level instead of Debug (7).
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'facility',
+ 'section' : 'logging',
+ 'oldname' : 'logging-facility',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Facility to log messages as. 0 corresponds to local0',
+ 'doc' : '''
+If set to a digit, logging is performed under this LOCAL facility.
+See :ref:`logging`.
+Do not pass names like 'local0'!
+ ''',
+ },
+ {
+ 'name' : 'lowercase',
+ 'section' : 'outgoing',
+ 'oldname' : 'lowercase-outgoing',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Force outgoing questions to lowercase',
+ 'doc' : '''
+Set to true to lowercase the outgoing queries.
+When set to 'no' (the default) a query from a client using mixed case in the DNS labels (such as a user entering mixed-case names or `draft-vixie-dnsext-dns0x20-00 <http://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00>`_), PowerDNS preserves the case of the query.
+Broken authoritative servers might give a wrong or broken answer on this encoding.
+Setting ``lowercase-outgoing`` to 'yes' makes the PowerDNS Recursor lowercase all the labels in the query to the authoritative servers, but still return the proper case to the client requesting.
+ ''',
+ },
+ {
+ 'name' : 'lua_config_file',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'More powerful configuration options',
+ 'doc' : '''
+If set, and Lua support is compiled in, this will load an additional configuration file for newer features and more complicated setups.
+See :doc:`lua-config/index` for the options that can be set in this file.
+ ''',
+ },
+ {
+ 'name' : 'lua_dns_script',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Filename containing an optional Lua script that will be used to modify dns answers',
+ 'doc' : '''
+Path to a lua file to manipulate the Recursor's answers. See :doc:`lua-scripting/index` for more information.
+ ''',
+ },
+ {
+ 'name' : 'lua_maintenance_interval',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '1',
+ 'help' : 'Number of seconds between calls to the lua user defined maintenance() function',
+ 'doc' : '''
+The interval between calls to the Lua user defined `maintenance()` function in seconds.
+See :ref:`hooks-maintenance-callback`
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'max_busy_dot_probes',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Maximum number of concurrent DoT probes',
+ 'doc' : '''
+Limit the maximum number of simultaneous DoT probes the Recursor will schedule.
+The default value 0 means no DoT probes are scheduled.
+
+DoT probes are used to check if an authoritative server's IP address supports DoT.
+If the probe determines an IP address supports DoT, the Recursor will use DoT to contact it for subsequent queries until a failure occurs.
+After a failure, the Recursor will stop using DoT for that specific IP address for a while.
+The results of probes are remembered and can be viewed by the ``rec_control dump-dot-probe-map`` command.
+If the maximum number of pending probes is reached, no probes will be scheduled, even if no DoT status is known for an address.
+If the result of a probe is not yet available, the Recursor will contact the authoritative server in the regular way, unless an authoritative server is configured to be contacted over DoT always using :ref:`setting-dot-to-auth-names`.
+In that case no probe will be scheduled.
+
+.. note::
+ DoT probing is an experimental feature.
+ Please test thoroughly to determine if it is suitable in your specific production environment before enabling.
+ ''',
+ 'versionadded': '4.7.0'
+ },
+ {
+ 'name' : 'max_cache_bogus_ttl',
+ 'section' : 'recordcache',
+ 'type' : LType.Uint64,
+ 'default' : '3600',
+ 'help' : 'maximum number of seconds to keep a Bogus (positive or negative) cached entry in memory',
+ 'doc' : '''
+Maximum number of seconds to cache an item in the DNS cache (negative or positive) if its DNSSEC validation failed, no matter what the original TTL specified, to reduce the impact of a broken domain.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'max_entries',
+ 'section' : 'recordcache',
+ 'oldname' : 'max-cache-entries',
+ 'type' : LType.Uint64,
+ 'default' : '1000000',
+ 'help' : 'If set, maximum number of entries in the main cache',
+ 'doc' : '''
+Maximum number of DNS record cache entries, shared by all threads since 4.4.0.
+Each entry associates a name and type with a record set.
+The size of the negative cache is 10% of this number.
+ ''',
+ },
+ {
+ 'name' : 'max_ttl',
+ 'section' : 'recordcache',
+ 'oldname' : 'max-cache-ttl',
+ 'type' : LType.Uint64,
+ 'default' : '86400',
+ 'help' : 'maximum number of seconds to keep a cached entry in memory',
+ 'doc' : '''
+Maximum number of seconds to cache an item in the DNS cache, no matter what the original TTL specified.
+This value also controls the refresh period of cached root data.
+See :ref:`handling-of-root-hints` for more information on this.
+ ''',
+ 'versionchanged': ('4.1.0', 'The minimum value of this setting is 15. i.e. setting this to lower than 15 will make this value 15.')
+ },
+ {
+ 'name' : 'max_concurrent_requests_per_tcp_connection',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '10',
+ 'help' : 'Maximum number of requests handled concurrently per TCP connection',
+ 'doc' : '''
+Maximum number of incoming requests handled concurrently per tcp
+connection. This number must be larger than 0 and smaller than 65536
+and also smaller than `max-mthreads`.
+ ''',
+ 'versionadded': '4.3.0'
+ },
+ {
+ 'name' : 'max_include_depth',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '20',
+ 'help' : 'Maximum nested $INCLUDE depth when loading a zone from a file',
+ 'doc' : '''
+Maximum number of nested ``$INCLUDE`` directives while processing a zone file.
+Zero mean no ``$INCLUDE`` directives will be accepted.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'max_generate_steps',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Maximum number of $GENERATE steps when loading a zone from a file',
+ 'doc' : '''
+Maximum number of steps for a '$GENERATE' directive when parsing a
+zone file. This is a protection measure to prevent consuming a lot of
+CPU and memory when untrusted zones are loaded. Default to 0 which
+means unlimited.
+ ''',
+ 'versionadded': '4.3.0'
+ },
+ {
+ 'name' : 'max_mthreads',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '2048',
+ 'help' : 'Maximum number of simultaneous Mtasker threads',
+ 'doc' : '''
+Maximum number of simultaneous MTasker threads.
+ ''',
+ },
+ {
+ 'name' : 'max_entries',
+ 'section' : 'packetcache',
+ 'oldname' : 'max-packetcache-entries',
+ 'type' : LType.Uint64,
+ 'default' : '500000',
+ 'help' : 'maximum number of entries to keep in the packetcache',
+ 'doc' : '''
+Maximum number of Packet Cache entries. Sharded and shared by all threads since 4.9.0.
+''',
+ },
+ {
+ 'name' : 'max_qperq',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '50',
+ 'help' : 'Maximum outgoing queries per query',
+ 'doc' : '''
+The maximum number of outgoing queries that will be sent out during the resolution of a single client query.
+This is used to avoid cycles resolving names.
+ ''',
+ 'versionchanged': ('5.1.0', 'The default used to be 60, with an extra allowance if qname minimization was enabled. Having better algorithms allows for a lower default limit.'),
+ },
+ {
+ 'name' : 'max_ns_address_qperq',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '10',
+ 'help' : 'Maximum outgoing NS address queries per query',
+ 'doc' : '''
+The maximum number of outgoing queries with empty replies for
+resolving nameserver names to addresses we allow during the resolution
+of a single client query. If IPv6 is enabled, an A and a AAAA query
+for a name counts as 1. If a zone publishes more than this number of
+NS records, the limit is further reduced for that zone by lowering
+it by the number of NS records found above the
+:ref:`setting-max-ns-address-qperq` value. The limit wil not be reduced to a
+number lower than 5.
+ ''',
+ 'versionadded' : ['4.1.16', '4.2.2', '4.3.1']
+ },
+ {
+ 'name' : 'max_ns_per_resolve',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '13',
+ 'help' : 'Maximum number of NS records to consider to resolve a name, 0 is no limit',
+ 'doc' : '''
+The maximum number of NS records that will be considered to select a nameserver to contact to resolve a name.
+If a zone has more than :ref:`setting-max-ns-per-resolve` NS records, a random sample of this size will be used.
+If :ref:`setting-max-ns-per-resolve` is zero, no limit applies.
+ ''',
+ 'versionadded': ['4.8.0', '4.7.3', '4.6.4', '4.5.11']
+ },
+ {
+ 'name' : 'max_negative_ttl',
+ 'section' : 'recordcache',
+ 'type' : LType.Uint64,
+ 'default' : '3600',
+ 'help' : 'maximum number of seconds to keep a negative cached entry in memory',
+ 'doc' : '''
+A query for which there is authoritatively no answer is cached to quickly deny a record's existence later on, without putting a heavy load on the remote server.
+In practice, caches can become saturated with hundreds of thousands of hosts which are tried only once.
+This setting, which defaults to 3600 seconds, puts a maximum on the amount of time negative entries are cached.
+ ''',
+ },
+ {
+ 'name' : 'max_recursion_depth',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '16',
+ 'help' : 'Maximum number of internal recursion calls per query, 0 for unlimited',
+ 'doc' : '''
+Total maximum number of internal recursion calls the server may use to answer a single query.
+0 means unlimited.
+The value of :ref:`setting-stack-size` should be increased together with this one to prevent the stack from overflowing.
+If :ref:`setting-qname-minimization` is enabled, the fallback code in case of a failing resolve is allowed an additional `max-recursion-depth/2`.
+ ''',
+ 'versionchanged': [('4.1.0', 'Before 4.1.0, this settings was unlimited.'),
+ ('4.9.0', "Before 4.9.0 this setting's default was 40 and the limit on ``CNAME`` chains (fixed at 16) acted as a bound on he recursion depth.")]
+ },
+ {
+ 'name' : 'max_tcp_clients',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '128',
+ 'help' : 'Maximum number of simultaneous TCP clients',
+ 'doc' : '''
+Maximum number of simultaneous incoming TCP connections allowed.
+ ''',
+ },
+ {
+ 'name' : 'max_tcp_per_client',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'If set, maximum number of TCP sessions per client (IP address)',
+ 'doc' : '''
+Maximum number of simultaneous incoming TCP connections allowed per client (remote IP address).
+ 0 means unlimited.
+ ''',
+ },
+ {
+ 'name' : 'max_tcp_queries_per_connection',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'If set, maximum number of TCP queries in a TCP connection',
+ 'doc' : '''
+Maximum number of DNS queries in a TCP connection.
+0 means unlimited.
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'max_total_msec',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '7000',
+ 'help' : 'Maximum total wall-clock time per query in milliseconds, 0 for unlimited',
+ 'doc' : '''
+Total maximum number of milliseconds of wallclock time the server may use to answer a single query.
+0 means unlimited.
+ ''',
+ },
+ {
+ 'name' : 'max_udp_queries_per_round',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '10000',
+ 'help' : 'Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing',
+ 'doc' : '''
+Under heavy load the recursor might be busy processing incoming UDP queries for a long while before there is no more of these, and might therefore
+neglect scheduling new ``mthreads``, handling responses from authoritative servers or responding to :doc:`rec_control <manpages/rec_control.1>`
+requests.
+This setting caps the maximum number of incoming UDP DNS queries processed in a single round of looping on ``recvmsg()`` after being woken up by the multiplexer, before
+returning back to normal processing and handling other events.
+ ''',
+ 'versionadded': '4.1.4'
+ },
+ {
+ 'name' : 'minimum_ttl_override',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '1',
+ 'help' : 'The minimum TTL',
+ 'doc' : '''
+This setting artificially raises all TTLs to be at least this long.
+Setting this to a value greater than 1 technically is an RFC violation, but might improve performance a lot.
+Using a value of 0 impacts performance of TTL 0 records greatly, since it forces the recursor to contact
+authoritative servers each time a client requests them.
+Can be set at runtime using ``rec_control set-minimum-ttl 3600``.
+ ''',
+ 'versionchanged': ('4.5.0', 'Old versions used default 0.')
+ },
+ {
+ 'name' : 'tracking',
+ 'section' : 'nod',
+ 'oldname' : 'new-domain-tracking',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Track newly observed domains (i.e. never seen before).',
+ 'doc' : '''
+Whether to track newly observed domains, i.e. never seen before. This
+is a probabilistic algorithm, using a stable bloom filter to store
+records of previously seen domains. When enabled for the first time,
+all domains will appear to be newly observed, so the feature is best
+left enabled for e.g. a week or longer before using the results. Note
+that this feature is optional and must be enabled at compile-time,
+thus it may not be available in all pre-built packages.
+If protobuf is enabled and configured, then the newly observed domain
+status will appear as a flag in Response messages.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'log',
+ 'section' : 'nod',
+ 'oldname' : 'new-domain-log',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Log newly observed domains.',
+ 'doc' : '''
+If a newly observed domain is detected, log that domain in the
+recursor log file. The log line looks something like::
+
+ Jul 18 11:31:25 Newly observed domain nod=sdfoijdfio.com
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'lookup',
+ 'section' : 'nod',
+ 'oldname' : 'new-domain-lookup',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Perform a DNS lookup newly observed domains as a subdomain of the configured domain',
+ 'doc' : '''
+If a domain is specified, then each time a newly observed domain is
+detected, the recursor will perform an A record lookup of '<newly
+observed domain>.<lookup domain>'. For example if 'new-domain-lookup'
+is configured as 'nod.powerdns.com', and a new domain 'xyz123.tv' is
+detected, then an A record lookup will be made for
+'xyz123.tv.nod.powerdns.com'. This feature gives a way to share the
+newly observed domain with partners, vendors or security teams. The
+result of the DNS lookup will be ignored by the recursor.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'db_size',
+ 'section' : 'nod',
+ 'oldname' : 'new-domain-db-size',
+ 'type' : LType.Uint64,
+ 'default' : '67108864',
+ 'help' : 'Size of the DB used to track new domains in terms of number of cells. Defaults to 67108864',
+ 'doc' : '''
+The default size of the stable bloom filter used to store previously
+observed domains is 67108864. To change the number of cells, use this
+setting. For each cell, the SBF uses 1 bit of memory, and one byte of
+disk for the persistent file.
+If there are already persistent files saved to disk, this setting will
+have no effect unless you remove the existing files.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'history_dir',
+ 'section' : 'nod',
+ 'oldname' : 'new-domain-history-dir',
+ 'type' : LType.String,
+ 'default' : 'NODCACHEDIRNOD',
+ 'docdefault': 'Determined by distribution',
+ 'help' : 'Persist new domain tracking data here to persist between restarts',
+ 'doc' : '''
+This setting controls which directory is used to store the on-disk
+cache of previously observed domains.
+
+The default depends on ``LOCALSTATEDIR`` when building the software.
+Usually this comes down to ``/var/lib/pdns-recursor/nod`` or ``/usr/local/var/lib/pdns-recursor/nod``).
+
+The newly observed domain feature uses a stable bloom filter to store
+a history of previously observed domains. The data structure is
+synchronized to disk every 10 minutes, and is also initialized from
+disk on startup. This ensures that previously observed domains are
+preserved across recursor restarts.
+If you change the new-domain-db-size setting, you must remove any files
+from this directory.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'whitelist',
+ 'section' : 'nod',
+ 'oldname' : 'new-domain-whitelist',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'List of domains (and implicitly all subdomains) which will never be considered a new domain (deprecated)',
+ 'doc' : '',
+ 'versionadded': '4.2.0',
+ 'deprecated': ('4.5.0', 'Use :ref:`setting-new-domain-ignore-list`.'),
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'ignore_list',
+ 'section' : 'nod',
+ 'oldname' : 'new-domain-ignore-list',
+ 'type' : LType.ListStrings,
+ 'default' : '',
+ 'help' : 'List of domains (and implicitly all subdomains) which will never be considered a new domain',
+ 'doc' : '''
+This setting is a list of all domains (and implicitly all subdomains)
+that will never be considered a new domain. For example, if the domain
+'xyz123.tv' is in the list, then 'foo.bar.xyz123.tv' will never be
+considered a new domain. One use-case for the ignore list is to never
+reveal details of internal subdomains via the new-domain-lookup
+feature.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'pb_tag',
+ 'section' : 'nod',
+ 'oldname' : 'new-domain-pb-tag',
+ 'type' : LType.String,
+ 'default' : 'pdns-nod',
+ 'help' : 'If protobuf is configured, the tag to use for messages containing newly observed domains. Defaults to \'pdns-nod\'',
+ 'doc' : '''
+If protobuf is configured, then this tag will be added to all protobuf response messages when
+a new domain is observed.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'network_timeout',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '1500',
+ 'help' : 'Wait this number of milliseconds for network i/o',
+ 'doc' : '''
+Number of milliseconds to wait for a remote authoritative server to respond.
+ ''',
+ },
+ {
+ 'name' : 'no_shuffle',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Don\'t change',
+ 'doc' : 'SKIP',
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'non_resolving_ns_max_fails',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '5',
+ 'help' : 'Number of failed address resolves of a nameserver to start throttling it, 0 is disabled',
+ 'doc' : '''
+Number of failed address resolves of a nameserver name to start throttling it, 0 is disabled.
+Nameservers matching :ref:`setting-dont-throttle-names` will not be throttled.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'non_resolving_ns_throttle_time',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '60',
+ 'help' : 'Number of seconds to throttle a nameserver with a name failing to resolve',
+ 'doc' : '''
+Number of seconds to throttle a nameserver with a name failing to resolve.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'nothing_below_nxdomain',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : 'dnssec',
+ 'help' : 'When an NXDOMAIN exists in cache for a name with fewer labels than the qname, send NXDOMAIN without doing a lookup (see RFC 8020)',
+ 'doc' : '''
+- One of ``no``, ``dnssec``, ``yes``.
+
+The type of :rfc:`8020` handling using cached NXDOMAIN responses.
+This RFC specifies that NXDOMAIN means that the DNS tree under the denied name MUST be empty.
+When an NXDOMAIN exists in the cache for a shorter name than the qname, no lookup is done and an NXDOMAIN is sent to the client.
+
+For instance, when ``foo.example.net`` is negatively cached, any query
+matching ``*.foo.example.net`` will be answered with NXDOMAIN directly
+without consulting authoritative servers.
+
+``no``
+ No :rfc:`8020` processing is done.
+
+``dnssec``
+ :rfc:`8020` processing is only done using cached NXDOMAIN records that are
+ DNSSEC validated.
+
+``yes``
+ :rfc:`8020` processing is done using any non-Bogus NXDOMAIN record
+ available in the cache.
+ ''',
+ 'versionadded': '4.3.0'
+ },
+ {
+ 'name' : 'nsec3_max_iterations',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '50',
+ 'help' : 'Maximum number of iterations allowed for an NSEC3 record',
+ 'doc' : '''
+Maximum number of iterations allowed for an NSEC3 record.
+If an answer containing an NSEC3 record with more iterations is received, its DNSSEC validation status is treated as ``Insecure``.
+ ''',
+ 'versionadded': '4.1.0',
+ 'versionchanged': [('4.5.2', 'Default is now 150, was 2500 before.'),
+ ('5.0.0', 'Default is now 50, was 150 before.')]
+ },
+ {
+ 'name' : 'max_rrsigs_per_record',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '2',
+ 'help' : 'Maximum number of RRSIGs to consider when validating a given record',
+ 'doc' : '''
+Maximum number of RRSIGs we are willing to cryptographically check when validating a given record. Expired or not yet incepted RRSIGs do not count toward to this limit.
+ ''',
+ 'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+ },
+ {
+ 'name' : 'max_nsec3s_per_record',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '10',
+ 'help' : 'Maximum number of NSEC3s to consider when validating a given denial of existence',
+ 'doc' : '''
+Maximum number of NSEC3s to consider when validating a given denial of existence.
+ ''',
+ 'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+ },
+ {
+ 'name' : 'max_signature_validations_per_query',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '30',
+ 'help' : 'Maximum number of RRSIG signatures we are willing to validate per incoming query',
+ 'doc' : '''
+Maximum number of RRSIG signatures we are willing to validate per incoming query.
+ ''',
+ 'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+ },
+ {
+ 'name' : 'max_nsec3_hash_computations_per_query',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '600',
+ 'help' : 'Maximum number of NSEC3 hashes that we are willing to compute during DNSSEC validation, per incoming query',
+ 'doc' : '''
+Maximum number of NSEC3 hashes that we are willing to compute during DNSSEC validation, per incoming query.
+ ''',
+ 'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+ },
+ {
+ 'name' : 'aggressive_cache_max_nsec3_hash_cost',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '150',
+ 'help' : 'Maximum estimated NSEC3 cost for a given query to consider aggressive use of the NSEC3 cache',
+ 'doc' : '''
+Maximum estimated NSEC3 cost for a given query to consider aggressive use of the NSEC3 cache. The cost is estimated based on a heuristic taking the zone's NSEC3 salt and iterations parameters into account, as well at the number of labels of the requested name. For example a query for a name like a.b.c.d.e.f.example.com. in an example.com zone. secured with NSEC3 and 10 iterations (NSEC3 iterations count of 9) and an empty salt will have an estimated worst-case cost of 10 (iterations) * 6 (number of labels) = 60. The aggressive NSEC cache is an optimization to reduce the number of queries to authoritative servers, which is especially useful when a zone is under pseudo-random subdomain attack, and we want to skip it the zone parameters make it expensive.
+''',
+ 'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+ },
+ {
+ 'name' : 'max_ds_per_zone',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '8',
+ 'help' : 'Maximum number of DS records to consider per zone',
+ 'doc' : '''
+Maximum number of DS records to consider when validating records inside a zone..
+ ''',
+ 'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+ },
+ {
+ 'name' : 'max_dnskeys',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '2',
+ 'help' : 'Maximum number of DNSKEYs with the same algorithm and tag to consider when validating a given record',
+ 'doc' : '''
+Maximum number of DNSKEYs with the same algorithm and tag to consider when validating a given record. Setting this value to 1 effectively denies DNSKEY tag collisions in a zone.
+ ''',
+ 'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+ },
+ {
+ 'name' : 'ttl',
+ 'section' : 'packetcache',
+ 'oldname' : 'packetcache-ttl',
+ 'type' : LType.Uint64,
+ 'default' : '86400',
+ 'help' : 'maximum number of seconds to keep a cached entry in packetcache',
+ 'doc' : '''
+Maximum number of seconds to cache an item in the packet cache, no matter what the original TTL specified.
+ ''',
+ 'versionchanged': ('4.9.0', 'The default was changed from 3600 (1 hour) to 86400 (24 hours).')
+ },
+ {
+ 'name' : 'negative_ttl',
+ 'section' : 'packetcache',
+ 'oldname' : 'packetcache-negative-ttl',
+ 'type' : LType.Uint64,
+ 'default' : '60',
+ 'help' : 'maximum number of seconds to keep a cached NxDomain or NoData entry in packetcache',
+ 'doc' : '''
+Maximum number of seconds to cache an ``NxDomain`` or ``NoData`` answer in the packetcache.
+This setting's maximum is capped to :ref:`setting-packetcache-ttl`.
+i.e. setting ``packetcache-ttl=15`` and keeping ``packetcache-negative-ttl`` at the default will lower ``packetcache-negative-ttl`` to ``15``.
+ ''',
+ 'versionadded': '4.9.0'
+ },
+ {
+ 'name' : 'servfail_ttl',
+ 'section' : 'packetcache',
+ 'oldname' : 'packetcache-servfail-ttl',
+ 'type' : LType.Uint64,
+ 'default' : '60',
+ 'help' : 'maximum number of seconds to keep a cached servfail entry in packetcache',
+ 'doc' : '''
+Maximum number of seconds to cache an answer indicating a failure to resolve in the packet cache.
+Before version 4.6.0 only ``ServFail`` answers were considered as such. Starting with 4.6.0, all responses with a code other than ``NoError`` and ``NXDomain``, or without records in the answer and authority sections, are considered as a failure to resolve.
+Since 4.9.0, negative answers are handled separately from resolving failures.
+ ''',
+ 'doc-rst' : '''
+ 'versionchanged': ('4.0.0', "This setting's maximum is capped to :ref:`setting-packetcache-ttl`.
+ i.e. setting ``packetcache-ttl=15`` and keeping ``packetcache-servfail-ttl`` at the default will lower ``packetcache-servfail-ttl`` to ``15``.")
+ '''
+ },
+ {
+ 'name' : 'shards',
+ 'section' : 'packetcache',
+ 'oldname' : 'packetcache-shards',
+ 'type' : LType.Uint64,
+ 'default' : '1024',
+ 'help' : 'Number of shards in the packet cache',
+ 'doc' : '''
+Sets the number of shards in the packet cache. If you have high contention as reported by ``packetcache-contented/packetcache-acquired``,
+you can try to enlarge this value or run with fewer threads.
+ ''',
+ 'versionadded': '4.9.0'
+ },
+ {
+ 'name' : 'pdns_distributes_queries',
+ 'section' : 'incoming',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'If PowerDNS itself should distribute queries over threads',
+ 'doc' : '''
+If set, PowerDNS will use distinct threads to listen to client sockets and distribute that work to worker-threads using a hash of the query.
+This feature should maximize the cache hit ratio on versions before 4.9.0.
+To use more than one thread set :ref:`setting-distributor-threads` in version 4.2.0 or newer.
+Enabling should improve performance on systems where :ref:`setting-reuseport` does not have the effect of
+balancing the queries evenly over multiple worker threads.
+ ''',
+ 'versionchanged': ('4.9.0', 'Default changed to ``no``, previously it was ``yes``.')
+ },
+ {
+ 'name' : 'processes',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '1',
+ 'help' : 'Launch this number of processes (EXPERIMENTAL, DO NOT CHANGE)',
+ 'doc' : '''SKIP''',
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'protobuf_use_kernel_timestamp',
+ 'section' : 'logging',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Compute the latency of queries in protobuf messages by using the timestamp set by the kernel when the query was received (when available)',
+ 'doc' : '''
+Whether to compute the latency of responses in protobuf messages using the timestamp set by the kernel when the query packet was received (when available), instead of computing it based on the moment we start processing the query.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'proxy_protocol_from',
+ 'section' : 'incoming',
+ 'type' : LType.ListSubnets,
+ 'default' : '',
+ 'help' : 'A Proxy Protocol header is required from these subnets',
+ 'doc' : '''
+Ranges that are required to send a Proxy Protocol version 2 header in front of UDP and TCP queries, to pass the original source and destination addresses and ports to the recursor, as well as custom values.
+Queries that are not prefixed with such a header will not be accepted from clients in these ranges. Queries prefixed by headers from clients that are not listed in these ranges will be dropped.
+
+Note that once a Proxy Protocol header has been received, the source address from the proxy header instead of the address of the proxy will be checked against the :ref:`setting-allow-from` ACL.
+
+The dnsdist docs have `more information about the PROXY protocol <https://dnsdist.org/advanced/passing-source-address.html#proxy-protocol>`_.
+ ''',
+ 'versionadded' : '4.4.0',
+ 'versionchanged' : ('5.0.4', 'YAML settings only: previously this was defined as a string instead of a sequence')
+ },
+ {
+ 'name' : 'proxy_protocol_maximum_size',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '512',
+ 'help' : 'The maximum size of a proxy protocol payload, including the TLV values',
+ 'doc' : '''
+The maximum size, in bytes, of a Proxy Protocol payload (header, addresses and ports, and TLV values). Queries with a larger payload will be dropped.
+ ''',
+ 'versionadded': '4.4.0'
+ },
+ {
+ 'name' : 'public_suffix_list_file',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Path to the Public Suffix List file, if any',
+ 'doc' : '''
+Path to the Public Suffix List file, if any. If set, PowerDNS will try to load the Public Suffix List from this file instead of using the built-in list. The PSL is used to group the queries by relevant domain names when displaying the top queries.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'qname_minimization',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Use Query Name Minimization',
+ 'doc' : '''
+Enable Query Name Minimization. This implements a relaxed form of Query Name Mimimization as
+described in :rfc:`9156`.
+ ''',
+ 'versionadded': '4.3.0'
+ },
+ {
+ 'name' : 'qname_max_minimize_count',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '10',
+ 'help' : 'RFC9156 max minimize count',
+ 'doc' : '''
+``Max minimize count`` parameter, described in :rfc:`9156`. This is the maximum number of iterations
+of the Query Name Minimization Algorithm.
+ ''',
+ 'versionadded': '5.0.0'
+ },
+ {
+ 'name' : 'qname_minimize_one_label',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '4',
+ 'help' : 'RFC9156 minimize one label parameter',
+ 'doc' : '''
+``Minimize one label`` parameter, described in :rfc:`9156`.
+The value for the number of iterations of the Query Name Minimization Algorithm that should only have one label appended.
+This value has precedence over :ref:`setting-qname-max-minimize-count`.
+ ''',
+ 'versionadded': '5.0.0'
+ },
+ {
+ 'name' : 'source_address',
+ 'section' : 'outgoing',
+ 'oldname' : 'query-local-address',
+ 'type' : LType.ListSubnets,
+ 'default' : '0.0.0.0',
+ 'help' : 'Source IP address for sending queries',
+ 'doc' : '''
+Send out local queries from this address, or addresses. By adding multiple
+addresses, increased spoofing resilience is achieved. When no address of a certain
+address family is configured, there are *no* queries sent with that address family.
+In the default configuration this means that IPv6 is not used for outgoing queries.
+ ''',
+ 'versionchanged': ('4.4.0', 'IPv6 addresses can be set with this option as well.')
+ },
+ {
+ 'name' : 'quiet',
+ 'section' : 'logging',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Suppress logging of questions and answers',
+ 'doc' : '''
+Don't log queries.
+ ''',
+ },
+ {
+ 'name' : 'locked_ttl_perc',
+ 'section' : 'recordcache',
+ 'oldname' : 'record-cache-locked-ttl-perc',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Replace records in record cache only after this % of original TTL has passed',
+ 'doc' : '''
+Replace record sets in the record cache only after this percentage of the original TTL has passed.
+The PowerDNS Recursor already has several mechanisms to protect against spoofing attempts.
+This adds an extra layer of protection---as it limits the window of time cache updates are accepted---at the cost of a less efficient record cache.
+
+The default value of 0 means no extra locking occurs.
+When non-zero, record sets received (e.g. in the Additional Section) will not replace existing record sets in the record cache until the given percentage of the original TTL has expired.
+A value of 100 means only expired record sets will be replaced.
+
+There are a few cases where records will be replaced anyway:
+
+- Record sets that are expired will always be replaced.
+- Authoritative record sets will replace unauthoritative record sets unless DNSSEC validation of the new record set failed.
+- If the new record set belongs to a DNSSEC-secure zone and successfully passed validation it will replace an existing entry.
+- Record sets produced by :ref:`setting-refresh-on-ttl-perc` tasks will also replace existing record sets.
+ ''',
+ 'versionadded': '4.8.0'
+ },
+ {
+ 'name' : 'shards',
+ 'section' : 'recordcache',
+ 'oldname' : 'record-cache-shards',
+ 'type' : LType.Uint64,
+ 'default' : '1024',
+ 'help' : 'Number of shards in the record cache',
+ 'doc' : '''
+Sets the number of shards in the record cache. If you have high
+contention as reported by
+``record-cache-contented/record-cache-acquired``, you can try to
+enlarge this value or run with fewer threads.
+ ''',
+ 'versionadded': '4.4.0'
+ },
+ {
+ 'name' : 'refresh_on_ttl_perc',
+ 'section' : 'recordcache',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'If a record is requested from the cache and only this % of original TTL remains, refetch',
+ 'doc' : '''
+Sets the 'refresh almost expired' percentage of the record cache. Whenever a record is fetched from the packet or record cache
+and only ``refresh-on-ttl-perc`` percent or less of its original TTL is left, a task is queued to refetch the name/type combination to
+update the record cache. In most cases this causes future queries to always see a non-expired record cache entry.
+A typical value is 10. If the value is zero, this functionality is disabled.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'reuseport',
+ 'section' : 'incoming',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address',
+ 'doc' : '''
+If ``SO_REUSEPORT`` support is available, allows multiple threads and processes to open listening sockets for the same port.
+
+Since 4.1.0, when :ref:`setting-pdns-distributes-queries` is disabled and :ref:`setting-reuseport` is enabled, every worker-thread will open a separate listening socket to let the kernel distribute the incoming queries instead of running a distributor thread (which could otherwise be a bottleneck) and avoiding thundering herd issues, thus leading to much higher performance on multi-core boxes.
+ ''',
+ 'versionchanged': ('4.9.0', 'The default is changed to ``yes``, previously it was ``no``. If ``SO_REUSEPORT`` support is not available, the setting defaults to ``no``.')
+ },
+ {
+ 'name' : 'rng',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : 'auto',
+ 'help' : 'Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.',
+ 'doc' : '''
+- String
+- Default: auto
+
+Specify which random number generator to use. Permissible choices are
+ - auto - choose automatically
+ - sodium - Use libsodium ``randombytes_uniform``
+ - openssl - Use libcrypto ``RAND_bytes``
+ - getrandom - Use libc getrandom, falls back to urandom if it does not really work
+ - arc4random - Use BSD ``arc4random_uniform``
+ - urandom - Use ``/dev/urandom``
+ - kiss - Use simple settable deterministic RNG. **FOR TESTING PURPOSES ONLY!**
+ ''',
+ 'skip-yaml': True,
+ 'versionchanged': ('4.9.0', 'This setting is no longer used.')
+ },
+ {
+ 'name' : 'root_nx_trust',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'If set, believe that an NXDOMAIN from the root means the TLD does not exist',
+ 'doc' : '''
+If set, an NXDOMAIN from the root-servers will serve as a blanket NXDOMAIN for the entire TLD the query belonged to.
+The effect of this is far fewer queries to the root-servers.
+ ''',
+ 'versionchanged': ('4.0.0', "Default is ``yes`` now, was ``no`` before 4.0.0")
+ },
+ {
+ 'name' : 'save_parent_ns_set',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Save parent NS set to be used if child NS set fails',
+ 'doc' : '''
+If set, a parent (non-authoritative) ``NS`` set is saved if it contains more entries than a newly encountered child (authoritative) ``NS`` set for the same domain.
+The saved parent ``NS`` set is tried if resolution using the child ``NS`` set fails.
+ ''',
+ 'versionadded': '4.7.0'
+ },
+ {
+ 'name' : 'security_poll_suffix',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : 'secpoll.powerdns.com.',
+ 'help' : 'Domain name from which to query security update notifications',
+ 'doc' : '''
+Domain name from which to query security update notifications.
+Setting this to an empty string disables secpoll.
+ ''',
+ },
+ {
+ 'name' : 'serve_rfc1918',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'If we should be authoritative for RFC 1918 private IP space',
+ 'doc' : '''
+This makes the server authoritatively aware of: ``10.in-addr.arpa``, ``168.192.in-addr.arpa``, ``16-31.172.in-addr.arpa``, which saves load on the AS112 servers.
+Individual parts of these zones can still be loaded or forwarded.
+ ''',
+ },
+ {
+ 'name' : 'serve_stale_extensions',
+ 'section' : 'recordcache',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Number of times a record\'s ttl is extended by 30s to be served stale',
+ 'doc' : '''
+Maximum number of times an expired record's TTL is extended by 30s when serving stale.
+Extension only occurs if a record cannot be refreshed.
+A value of 0 means the ``Serve Stale`` mechanism is not used.
+To allow records becoming stale to be served for an hour, use a value of 120.
+See :ref:`serve-stale` for a description of the Serve Stale mechanism.
+ ''',
+ 'versionadded': '4.8.0'
+ },
+ {
+ 'name' : 'server_down_max_fails',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '64',
+ 'help' : 'Maximum number of consecutive timeouts (and unreachables) to mark a server as down ( 0 => disabled )',
+ 'doc' : '''
+If a server has not responded in any way this many times in a row, no longer send it any queries for :ref:`setting-server-down-throttle-time` seconds.
+Afterwards, we will try a new packet, and if that also gets no response at all, we again throttle for :ref:`setting-server-down-throttle-time` seconds.
+Even a single response packet will drop the block.
+ ''',
+ },
+ {
+ 'name' : 'server_down_throttle_time',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '60',
+ 'help' : 'Number of seconds to throttle all queries to a server after being marked as down',
+ 'doc' : '''
+Throttle a server that has failed to respond :ref:`setting-server-down-max-fails` times for this many seconds.
+ ''',
+ },
+ {
+ 'name' : 'bypass_server_throttling_probability',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '25',
+ 'help' : 'Determines the probability of a server marked down to be used anyway',
+ 'doc' : '''
+This setting determines the probability of a server marked down to be used anyway.
+A value of ``n`` means that the chance of a server marked down still being used after it wins speed selection is is ``1/n``.
+If this setting is zero throttled servers will never be selected to be used anyway.
+ ''',
+ 'versionadded': '5.0.0'
+ },
+ {
+ 'name' : 'server_id',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : RUNTIME,
+ 'help' : 'Returned when queried for \'id.server\' TXT or NSID, defaults to hostname, set custom or \'disabled\'',
+ 'doc' : '''
+The reply given by The PowerDNS recursor to a query for 'id.server' with its hostname, useful for in clusters.
+When a query contains the :rfc:`NSID EDNS0 Option <5001>`, this value is returned in the response as the NSID value.
+
+This setting can be used to override the answer given to these queries.
+Set to 'disabled' to disable NSID and 'id.server' answers.
+
+Query example (where 192.0.2.14 is your server):
+
+.. code-block:: sh
+
+ dig @192.0.2.14 CHAOS TXT id.server.
+ dig @192.0.2.14 example.com IN A +nsid
+ ''',
+ },
+ {
+ 'name' : 'setgid',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set, change group id to this gid for more security',
+ 'doc' : '''
+PowerDNS can change its user and group id after binding to its socket.
+Can be used for better :doc:`security <security>`.
+ '''
+ },
+ {
+ 'name' : 'setuid',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set, change user id to this uid for more security',
+ 'doc' : '''
+PowerDNS can change its user and group id after binding to its socket.
+Can be used for better :doc:`security <security>`.
+ '''
+ },
+ {
+ 'name' : 'signature_inception_skew',
+ 'section' : 'dnssec',
+ 'type' : LType.Uint64,
+ 'default' : '60',
+ 'help' : 'Allow the signature inception to be off by this number of seconds',
+ 'doc' : '''
+Allow the signature inception to be off by this number of seconds. Negative values are not allowed.
+ ''',
+ 'versionadded': '4.1.5',
+ 'versionchanged': ('4.2.0', 'Default is now 60, was 0 before.')
+ },
+ {
+ 'name' : 'single_socket',
+ 'section' : 'outgoing',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'If set, only use a single socket for outgoing queries',
+ 'doc' : '''
+Use only a single socket for outgoing queries.
+ ''',
+ },
+ {
+ 'name' : 'agent',
+ 'section' : 'snmp',
+ 'oldname' : 'snmp-agent',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'If set, register as an SNMP agent',
+ 'doc' : '''
+If set to true and PowerDNS has been compiled with SNMP support, it will register as an SNMP agent to provide statistics and be able to send traps.
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'master_socket',
+ 'section' : 'snmp',
+ 'oldname' : 'snmp-master-socket',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set and snmp-agent is set, the socket to use to register to the SNMP daemon (deprecated)',
+ 'doc' : '''
+ ''',
+ 'versionadded': '4.1.0',
+ 'deprecated': ('4.5.0', 'Use :ref:`setting-snmp-daemon-socket`.'),
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'daemon_socket',
+ 'section' : 'snmp',
+ 'oldname' : 'snmp-daemon-socket',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'If set and snmp-agent is set, the socket to use to register to the SNMP daemon',
+ 'doc' : '''
+If not empty and ``snmp-agent`` is set to true, indicates how PowerDNS should contact the SNMP daemon to register as an SNMP agent.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'soa_minimum_ttl',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Don\'t change',
+ 'doc' : '''SKIP''',
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'socket_dir',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Where the controlsocket will live, /var/run/pdns-recursor when unset and not chrooted',
+ 'doc' : '''
+Where to store the control socket and pidfile.
+The default depends on ``LOCALSTATEDIR`` or the ``--with-socketdir`` setting when building (usually ``/var/run`` or ``/run``).
+
+When using :ref:`setting-chroot` the default becomes ``/``.
+The default value is overruled by the ``RUNTIME_DIRECTORY`` environment variable when that variable has a value (e.g. under systemd).
+ ''',
+ },
+ {
+ 'name' : 'socket_group',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Group of socket',
+ 'doc' : '''
+Group and mode of the controlsocket.
+Owner and group can be specified by name, mode is in octal.
+'''
+ },
+ {
+ 'name' : 'socket_mode',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Permissions for socket',
+ 'doc' : '''
+Mode of the controlsocket.
+Owner and group can be specified by name, mode is in octal.
+ '''
+ },
+ {
+ 'name' : 'socket_owner',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Owner of socket',
+ 'doc' : '''
+Owner of the controlsocket.
+Owner and group can be specified by name, mode is in octal.
+ '''
+ },
+ {
+ 'name' : 'spoof_nearmiss_max',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '1',
+ 'help' : 'If non-zero, assume spoofing after this many near misses',
+ 'doc' : '''
+If set to non-zero, PowerDNS will assume it is being spoofed after seeing this many answers with the wrong id.
+ ''',
+ 'versionchanged': ('4.5.0', 'Older versions used 20 as the default value.')
+ },
+ {
+ 'name' : 'stack_cache_size',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '100',
+ 'help' : 'Size of the stack cache, per mthread',
+ 'doc' : '''
+Maximum number of mthread stacks that can be cached for later reuse, per thread. Caching these stacks reduces the CPU load at the cost of a slightly higher memory usage, each cached stack consuming `stack-size` bytes of memory.
+It makes no sense to cache more stacks than the value of `max-mthreads`, since there will never be more stacks than that in use at a given time.
+ ''',
+ 'versionadded': '4.9.0'
+ },
+ {
+ 'name' : 'stack_size',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '200000',
+ 'help' : 'stack size per mthread',
+ 'doc' : '''
+Size in bytes of the stack of each mthread.
+ ''',
+ },
+ {
+ 'name' : 'statistics_interval',
+ 'section' : 'logging',
+ 'type' : LType.Uint64,
+ 'default' : '1800',
+ 'help' : 'Number of seconds between printing of recursor statistics, 0 to disable',
+ 'doc' : '''
+Interval between logging statistical summary on recursor performance.
+Use 0 to disable.
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'stats_api_blacklist',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128',
+ 'help' : 'List of statistics that are disabled when retrieving the complete list of statistics via the API (deprecated)',
+ 'docdefault': '',
+ 'doc' : '',
+ 'versionadded': '4.2.0',
+ 'deprecated': ('4.5.0', 'Use :ref:`setting-stats-api-disabled-list`.'),
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'stats_api_disabled_list',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128',
+ 'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*',
+ 'help' : 'List of statistics that are disabled when retrieving the complete list of statistics via the API',
+ 'doc' : '''
+A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
+These statistics can still be retrieved individually by specifically asking for it.
+ ''',
+ 'doc-new' : '''
+A sequence of statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
+These statistics can still be retrieved individually by specifically asking for it.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'stats_carbon_blacklist',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+ 'docdefault': '',
+ 'help' : 'List of statistics that are prevented from being exported via Carbon (deprecated)',
+ 'doc' : '',
+ 'versionadded': '4.2.0',
+ 'deprecated': ('4.5.0', 'Use :ref:`setting-stats-carbon-disabled-list`.'),
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'stats_carbon_disabled_list',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+ 'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*',
+ 'help' : 'List of statistics that are prevented from being exported via Carbon',
+ 'doc' : '''
+A list of comma-separated statistic names, that are prevented from being exported via carbon for performance reasons.
+ ''',
+ 'doc-new' : '''
+A sequence of statistic names, that are prevented from being exported via carbon for performance reasons.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'stats_rec_control_blacklist',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+ 'docdefault': '',
+ 'help' : 'List of statistics that are prevented from being exported via rec_control get-all (deprecated)',
+ 'doc' : '',
+ 'versionadded': '4.2.0',
+ 'deprecated': ('4.5.0', 'Use :ref:`setting-stats-rec-control-disabled-list`.'),
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'stats_rec_control_disabled_list',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+ 'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*',
+ 'help' : 'List of statistics that are prevented from being exported via rec_control get-all',
+ 'doc' : '''
+A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
+These statistics can still be retrieved individually.
+ ''',
+ 'doc-new' : '''
+A sequence of statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
+These statistics can still be retrieved individually.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'stats_ringbuffer_entries',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '10000',
+ 'help' : 'maximum number of packets to store statistics for',
+ 'doc' : '''
+Number of entries in the remotes ringbuffer, which keeps statistics on who is querying your server.
+Can be read out using ``rec_control top-remotes``.
+ ''',
+ },
+ {
+ 'name' : 'stats_snmp_blacklist',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+ 'docdefault': '',
+ 'help' : 'List of statistics that are prevented from being exported via SNMP (deprecated)',
+ 'doc' : '',
+ 'versionadded': '4.2.0',
+ 'deprecated': ('4.5.0', 'Use :ref:`setting-stats-snmp-disabled-list`.'),
+ 'skip-yaml': True,
+ },
+ {
+ 'name' : 'stats_snmp_disabled_list',
+ 'section' : 'recursor',
+ 'type' : LType.ListStrings,
+ 'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+ 'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*',
+ 'help' : 'List of statistics that are prevented from being exported via SNMP',
+ 'doc' : '''
+A list of comma-separated statistic names, that are prevented from being exported via SNMP, for performance reasons.
+ ''',
+ 'doc-new' : '''
+A sequence of statistic names, that are prevented from being exported via SNMP, for performance reasons.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'structured_logging',
+ 'section' : 'logging',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Prefer structured logging',
+ 'doc' : '''
+Prefer structured logging when both an old style and a structured log messages is available.
+ ''',
+ 'versionadded': '4.6.0',
+ 'versionchanged': ('5.0.0', 'Disabling structured logging is deprecated'),
+ },
+ {
+ 'name' : 'structured_logging_backend',
+ 'section' : 'logging',
+ 'type' : LType.String,
+ 'default' : 'default',
+ 'help' : 'Structured logging backend',
+ 'doc' : '''
+The backend used for structured logging output.
+This setting must be set on the command line (``--structured-logging-backend=...``) to be effective.
+Available backends are:
+
+- ``default``: use the traditional logging system to output structured logging information.
+- ``systemd-journal``: use systemd-journal.
+ When using this backend, provide ``-o verbose`` or simular output option to ``journalctl`` to view the full information.
+- ``json``: JSON objects are written to the standard error stream.
+
+See :doc:`appendices/structuredlogging` for more details.
+ ''',
+ 'versionadded': '4.8.0',
+ 'versionchanged': ('5.1.0', 'The JSON backend was added')
+ },
+ {
+ 'name' : 'tcp_fast_open',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size',
+ 'doc' : '''
+Enable TCP Fast Open support, if available, on the listening sockets.
+The numerical value supplied is used as the queue size, 0 meaning disabled. See :ref:`tcp-fast-open-support`.
+ ''',
+ 'versionadded': '4.1.0'
+ },
+ {
+ 'name' : 'tcp_fast_open_connect',
+ 'section' : 'outgoing',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Enable TCP Fast Open support on outgoing sockets',
+ 'doc' : '''
+Enable TCP Fast Open Connect support, if available, on the outgoing connections to authoritative servers. See :ref:`tcp-fast-open-support`.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+ {
+ 'name' : 'tcp_max_idle_ms',
+ 'section' : 'outgoing',
+ 'oldname' : 'tcp-out-max-idle-ms',
+ 'type' : LType.Uint64,
+ 'default' : '10000',
+ 'help' : 'Time TCP/DoT connections are left idle in milliseconds or 0 if no limit',
+ 'doc' : '''
+Time outgoing TCP/DoT connections are left idle in milliseconds or 0 if no limit. After having been idle for this time, the connection is eligible for closing.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'tcp_max_idle_per_auth',
+ 'section' : 'outgoing',
+ 'oldname' : 'tcp-out-max-idle-per-auth',
+ 'type' : LType.Uint64,
+ 'default' : '10',
+ 'help' : 'Maximum number of idle TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open',
+ 'doc' : '''
+Maximum number of idle outgoing TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'tcp_max_queries',
+ 'section' : 'outgoing',
+ 'oldname' : 'tcp-out-max-queries',
+ 'type' : LType.Uint64,
+ 'default' : '0',
+ 'help' : 'Maximum total number of queries per TCP/DoT connection, 0 means no limit',
+ 'doc' : '''
+Maximum total number of queries per outgoing TCP/DoT connection, 0 means no limit. After this number of queries, the connection is
+closed and a new one will be created if needed.
+ ''',
+ },
+ {
+ 'name' : 'tcp_max_idle_per_thread',
+ 'section' : 'outgoing',
+ 'oldname' : 'tcp-out-max-idle-per-thread',
+ 'type' : LType.Uint64,
+ 'default' : '100',
+ 'help' : 'Maximum number of idle TCP/DoT connections per thread',
+ 'doc' : '''
+Maximum number of idle outgoing TCP/DoT connections per thread, 0 means do not keep idle connections open.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'threads',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '2',
+ 'help' : 'Launch this number of threads',
+ 'doc' : '''
+Spawn this number of threads on startup.
+ ''',
+ },
+ {
+ 'name' : 'tcp_threads',
+ 'section' : 'recursor',
+ 'type' : LType.Uint64,
+ 'default' : '1',
+ 'help' : 'Launch this number of threads listening for and processing TCP queries',
+ 'doc' : '''
+Spawn this number of TCP processing threads on startup.
+ ''',
+ 'versionadded': '5.0.0'
+ },
+ {
+ 'name' : 'trace',
+ 'section' : 'logging',
+ 'type' : LType.String,
+ 'default' : 'no',
+ 'help' : 'if we should output heaps of logging. set to \'fail\' to only log failing domains',
+ 'doc' : '''
+One of ``no``, ``yes`` or ``fail``.
+If turned on, output impressive heaps of logging.
+May destroy performance under load.
+To log only queries resulting in a ``ServFail`` answer from the resolving process, this value can be set to ``fail``, but note that the performance impact is still large.
+Also note that queries that do produce a result but with a failing DNSSEC validation are not written to the log
+ ''',
+ },
+ {
+ 'name' : 'udp_source_port_min',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '1024',
+ 'help' : 'Minimum UDP port to bind on',
+ 'doc' : '''
+This option sets the low limit of UDP port number to bind on.
+
+In combination with :ref:`setting-udp-source-port-max` it configures the UDP
+port range to use. Port numbers are randomized within this range on
+initialization, and exceptions can be configured with :ref:`setting-udp-source-port-avoid`
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'udp_source_port_max',
+ 'section' : 'outgoing',
+ 'type' : LType.Uint64,
+ 'default' : '65535',
+ 'help' : 'Maximum UDP port to bind on',
+ 'doc' : '''
+This option sets the maximum limit of UDP port number to bind on.
+
+See :ref:`setting-udp-source-port-min`.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'udp_source_port_avoid',
+ 'section' : 'outgoing',
+ 'type' : LType.ListStrings,
+ 'default' : '11211',
+ 'help' : 'List of comma separated UDP port number to avoid',
+ 'doc' : '''
+A list of comma-separated UDP port numbers to avoid when binding.
+Ex: `5300,11211`
+
+See :ref:`setting-udp-source-port-min`.
+ ''',
+ 'doc-new' : '''
+A sequence of UDP port numbers to avoid when binding. For example:
+
+.. code-block:: yaml
+
+ outgoing:
+ udp_source_port_avoid:
+ - 5300
+ - 11211
+
+See :ref:`setting-udp-source-port-min`.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'udp_truncation_threshold',
+ 'section' : 'incoming',
+ 'type' : LType.Uint64,
+ 'default' : '1232',
+ 'help' : 'Maximum UDP response size before we truncate',
+ 'doc' : '''
+EDNS0 allows for large UDP response datagrams, which can potentially raise performance.
+Large responses however also have downsides in terms of reflection attacks.
+This setting limits the accepted size.
+Maximum value is 65535, but values above 4096 should probably not be attempted.
+
+To know why 1232, see the note at :ref:`setting-edns-outgoing-bufsize`.
+ ''',
+ 'versionchanged': ('4.2.0', 'Before 4.2.0, the default was 1680.')
+ },
+ {
+ 'name' : 'unique_response_tracking',
+ 'section' : 'nod',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Track unique responses (tuple of query name, type and RR).',
+ 'doc' : '''
+Whether to track unique DNS responses, i.e. never seen before combinations
+of the triplet (query name, query type, RR[rrname, rrtype, rrdata]).
+This can be useful for tracking potentially suspicious domains and
+behaviour, e.g. DNS fast-flux.
+If protobuf is enabled and configured, then the Protobuf Response message
+will contain a flag with udr set to true for each RR that is considered
+unique, i.e. never seen before.
+This feature uses a probabilistic data structure (stable bloom filter) to
+track unique responses, which can have false positives as well as false
+negatives, thus it is a best-effort feature. Increasing the number of cells
+in the SBF using the unique-response-db-size setting can reduce FPs and FNs.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'unique_response_log',
+ 'section' : 'nod',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Log unique responses',
+ 'doc' : '''
+Whether to log when a unique response is detected. The log line
+looks something like:
+
+Oct 24 12:11:27 Unique response observed: qname=foo.com qtype=A rrtype=AAAA rrname=foo.com rrcontent=1.2.3.4
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'unique_response_db_size',
+ 'section' : 'nod',
+ 'type' : LType.Uint64,
+ 'default' : '67108864',
+ 'help' : 'Size of the DB used to track unique responses in terms of number of cells. Defaults to 67108864',
+ 'doc' : '''
+The default size of the stable bloom filter used to store previously
+observed responses is 67108864. To change the number of cells, use this
+setting. For each cell, the SBF uses 1 bit of memory, and one byte of
+disk for the persistent file.
+If there are already persistent files saved to disk, this setting will
+have no effect unless you remove the existing files.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'unique_response_history_dir',
+ 'section' : 'nod',
+ 'type' : LType.String,
+ 'default' : 'NODCACHEDIRUDR',
+ 'docdefault': 'Determined by distribution',
+ 'help' : 'Persist unique response tracking data here to persist between restarts',
+ 'doc' : '''
+This setting controls which directory is used to store the on-disk
+cache of previously observed responses.
+
+The default depends on ``LOCALSTATEDIR`` when building the software.
+Usually this comes down to ``/var/lib/pdns-recursor/udr`` or ``/usr/local/var/lib/pdns-recursor/udr``).
+
+The newly observed domain feature uses a stable bloom filter to store
+a history of previously observed responses. The data structure is
+synchronized to disk every 10 minutes, and is also initialized from
+disk on startup. This ensures that previously observed responses are
+preserved across recursor restarts. If you change the
+unique-response-db-size, you must remove any files from this directory.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'unique_response_pb_tag',
+ 'section' : 'nod',
+ 'type' : LType.String,
+ 'default' : 'pdns-udr',
+ 'help' : 'If protobuf is configured, the tag to use for messages containing unique DNS responses. Defaults to \'pdns-udr\'',
+ 'doc' : '''
+If protobuf is configured, then this tag will be added to all protobuf response messages when
+a unique DNS response is observed.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'use_incoming_edns_subnet',
+ 'section' : 'incoming',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Pass along received EDNS Client Subnet information',
+ 'doc' : '''
+Whether to process and pass along a received EDNS Client Subnet to authoritative servers.
+The ECS information will only be sent for netmasks and domains listed in :ref:`setting-edns-subnet-allow-list` and will be truncated if the received scope exceeds :ref:`setting-ecs-ipv4-bits` for IPv4 or :ref:`setting-ecs-ipv6-bits` for IPv6.
+ ''',
+ },
+ {
+ 'name' : 'version',
+ 'section' : 'commands',
+ 'type' : LType.Command,
+ 'default' : 'no',
+ 'help' : 'Print version string',
+ 'doc' : '''
+Print version of this binary. Useful for checking which version of the PowerDNS recursor is installed on a system.
+ ''',
+ },
+ {
+ 'name' : 'version_string',
+ 'section' : 'recursor',
+ 'type' : LType.String,
+ 'default' : RUNTIME,
+ 'help' : 'string reported on version.pdns or version.bind',
+ 'doc' : '''
+By default, PowerDNS replies to the 'version.bind' query with its version number.
+Security conscious users may wish to override the reply PowerDNS issues.
+ ''',
+ },
+ {
+ 'name' : 'webserver',
+ 'section' : 'webservice',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Start a webserver (for REST API)',
+ 'doc' : '''
+Start the webserver (for REST API).
+ ''',
+ },
+ {
+ 'name' : 'address',
+ 'section' : 'webservice',
+ 'oldname' : 'webserver-address',
+ 'type' : LType.String,
+ 'default' : '127.0.0.1',
+ 'help' : 'IP Address of webserver to listen on',
+ 'doc' : '''
+IP address for the webserver to listen on.
+ ''',
+ },
+ {
+ 'name' : 'allow_from',
+ 'section' : 'webservice',
+ 'oldname' : 'webserver-allow-from',
+ 'type' : LType.ListSubnets,
+ 'default' : '127.0.0.1, ::1',
+ 'help' : 'Webserver access is only allowed from these subnets',
+ 'doc' : '''
+These IPs and subnets are allowed to access the webserver. Note that
+specifying an IP address without a netmask uses an implicit netmask
+of /32 or /128.
+ ''',
+ 'versionchanged': ('4.1.0', 'Default is now 127.0.0.1,::1, was 0.0.0.0/0,::/0 before.')
+ },
+ {
+ 'name' : 'hash_plaintext_credentials',
+ 'section' : 'webservice',
+ 'oldname': 'webserver-hash-plaintext-credentials',
+ 'type' : LType.Bool,
+ 'default' : 'false',
+ 'help' : 'Whether to hash passwords and api keys supplied in plaintext, to prevent keeping the plaintext version in memory at runtime',
+ 'doc' : '''
+Whether passwords and API keys supplied in the configuration as plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials and is thus disabled by default.
+Note that this option only applies to credentials stored in the configuration as plaintext, but hashed credentials are supported without enabling this option.
+ ''',
+ 'versionadded': '4.6.0'
+ },
+ {
+ 'name' : 'loglevel',
+ 'section' : 'webservice',
+ 'oldname' : 'webserver-loglevel',
+ 'type' : LType.String,
+ 'default' : 'normal',
+ 'help' : 'Amount of logging in the webserver (none, normal, detailed)',
+ 'doc' : '''
+One of ``none``, ``normal``, ``detailed``.
+The amount of logging the webserver must do. 'none' means no useful webserver information will be logged.
+When set to 'normal', the webserver will log a line per request that should be familiar::
+
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 'GET /api/v1/servers/localhost/bla HTTP/1.1' 404 196
+
+When set to 'detailed', all information about the request and response are logged::
+
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Request Details:
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Headers:
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept-encoding: gzip, deflate
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept-language: en-US,en;q=0.5
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e connection: keep-alive
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e dnt: 1
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e host: 127.0.0.1:8081
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e upgrade-insecure-requests: 1
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e user-agent: Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e No body
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Response details:
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Headers:
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Connection: close
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Content-Length: 49
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Content-Type: text/html; charset=utf-8
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Server: PowerDNS/0.0.15896.0.gaba8bab3ab
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e Full body:
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e <!html><title>Not Found</title><h1>Not Found</h1>
+ [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 'GET /api/v1/servers/localhost/bla HTTP/1.1' 404 196
+
+The value between the hooks is a UUID that is generated for each request. This can be used to find all lines related to a single request.
+
+.. note::
+ The webserver logs these line on the NOTICE level. The :ref:`setting-loglevel` seting must be 5 or higher for these lines to end up in the log.
+ ''',
+ 'versionadded': '4.2.0'
+ },
+ {
+ 'name' : 'password',
+ 'section' : 'webservice',
+ 'oldname' : 'webserver-password',
+ 'type' : LType.String,
+ 'default' : '',
+ 'help' : 'Password required for accessing the webserver',
+ 'doc' : '''
+Password required to access the webserver. Since 4.6.0 the password can be hashed and salted using ``rec_control hash-password`` instead of being present in the configuration in plaintext, but the plaintext version is still supported.
+ ''',
+ 'versionchanged': ('4.6.0', 'This setting now accepts a hashed and salted version.')
+ },
+ {
+ 'name' : 'port',
+ 'section' : 'webservice',
+ 'type' : LType.Uint64,
+ 'oldname': 'webserver-port',
+ 'default' : '8082',
+ 'help' : 'Port of webserver to listen on',
+ 'doc' : '''
+TCP port where the webserver should listen on.
+ ''',
+ },
+ {
+ 'name' : 'write_pid',
+ 'section' : 'recursor',
+ 'type' : LType.Bool,
+ 'default' : 'true',
+ 'help' : 'Write a PID file',
+ 'doc' : '''
+If a PID file should be written to :ref:`setting-socket-dir`
+ ''',
+ },
+ {
+ 'name' : 'x_dnssec_names',
+ 'section' : 'dnssec',
+ 'type' : LType.ListStrings,
+ 'default' : '',
+ 'help' : 'Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters',
+ 'doc' : '''
+List of names whose DNSSEC validation metrics will be counted in a separate set of metrics that start
+with ``x-dnssec-result-``.
+The names are suffix-matched.
+This can be used to not count known failing (test) name validations in the ordinary DNSSEC metrics.
+ ''',
+ 'versionadded': '4.5.0'
+ },
+]
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
+#include <utility>
+
#include "config.h"
#endif
class fails_t : public boost::noncopyable
{
public:
- typedef uint64_t counter_t;
+ using counter_t = uint64_t;
struct value_t
{
- value_t(const T& a) :
- key(a) {}
+ value_t(T arg) :
+ key(std::move(arg)) {}
T key;
mutable counter_t value{0};
time_t last{0};
};
- typedef multi_index_container<value_t,
- indexed_by<
- ordered_unique<tag<T>, member<value_t, T, &value_t::key>>,
- ordered_non_unique<tag<time_t>, member<value_t, time_t, &value_t::last>>>>
- cont_t;
+ using cont_t = multi_index_container<value_t,
+ indexed_by<
+ ordered_unique<tag<T>, member<value_t, T, &value_t::key>>,
+ ordered_non_unique<tag<time_t>, member<value_t, time_t, &value_t::last>>>>;
- cont_t getMapCopy() const
+ [[nodiscard]] cont_t getMapCopy() const
{
return d_cont;
}
- counter_t value(const T& t) const
+ [[nodiscard]] counter_t value(const T& arg) const
{
- auto i = d_cont.find(t);
+ auto iter = d_cont.find(arg);
- if (i == d_cont.end()) {
+ if (iter == d_cont.end()) {
return 0;
}
- return i->value;
+ return iter->value;
}
counter_t incr(const T& key, const struct timeval& now)
{
- auto i = d_cont.insert(key).first;
+ auto iter = d_cont.insert(key).first;
- if (i->value < std::numeric_limits<counter_t>::max()) {
- i->value++;
+ if (iter->value < std::numeric_limits<counter_t>::max()) {
+ iter->value++;
}
auto& ind = d_cont.template get<T>();
- time_t tm = now.tv_sec;
- ind.modify(i, [tm](value_t& val) { val.last = tm; });
- return i->value;
+ time_t nowSecs = now.tv_sec;
+ ind.modify(iter, [nowSecs](value_t& val) { val.last = nowSecs; });
+ return iter->value;
}
- void clear(const T& a)
+ void clear(const T& arg)
{
- d_cont.erase(a);
+ d_cont.erase(arg);
}
void clear()
d_cont.clear();
}
- size_t size() const
+ [[nodiscard]] size_t size() const
{
return d_cont.size();
}
}
else {
auto diff = makeFloat(last - now);
- auto factor = expf(diff) / 2.0f; // might be '0.5', or 0.0001
- d_val = (1.0f - factor) * val + factor * d_val;
+ auto factor = expf(diff) / 2.0F; // might be '0.5', or 0.0001
+ d_val = (1.0F - factor) * val + factor * d_val;
}
}
return d_val *= factor;
}
- float peek(void) const
+ [[nodiscard]] float peek() const
{
return d_val;
}
- int last(void) const
+ [[nodiscard]] int last() const
{
return d_last;
}
};
public:
- DecayingEwmaCollection(const DNSName& name, const struct timeval ts = {0, 0}) :
- d_name(name), d_lastget(ts)
+ DecayingEwmaCollection(DNSName name, const struct timeval val = {0, 0}) :
+ d_name(std::move(name)), d_lastget(val)
{
}
float getFactor(const struct timeval& now) const
{
float diff = makeFloat(d_lastget - now);
- return expf(diff / 60.0f); // is 1.0 or less
+ return expf(diff / 60.0F); // is 1.0 or less
}
bool stale(time_t limit) const
// d_collection is the modifyable part of the record, we index on DNSName and timeval, and DNSName never changes
mutable std::map<ComboAddress, DecayingEwma> d_collection;
- const DNSName d_name;
+ DNSName d_name;
struct timeval d_lastget;
};
public:
const auto& find_or_enter(const DNSName& name, const struct timeval& now)
{
- const auto it = insert(DecayingEwmaCollection{name, now}).first;
- return *it;
+ const auto iter = insert(DecayingEwmaCollection{name, now}).first;
+ return *iter;
}
const auto& find_or_enter(const DNSName& name)
{
- const auto it = insert(DecayingEwmaCollection{name}).first;
- return *it;
+ const auto iter = insert(DecayingEwmaCollection{name}).first;
+ return *iter;
}
float fastest(const DNSName& name, const struct timeval& now)
{
auto& ind = get<DNSName>();
- auto it = insert(DecayingEwmaCollection{name, now}).first;
- if (it->d_collection.empty()) {
+ auto iter = insert(DecayingEwmaCollection{name, now}).first;
+ if (iter->d_collection.empty()) {
return 0;
}
// This could happen if find(DNSName) entered an entry; it's used only by test code
- if (it->d_lastget.tv_sec == 0 && it->d_lastget.tv_usec == 0) {
- ind.modify(it, [&](DecayingEwmaCollection& d) { d.d_lastget = now; });
+ if (iter->d_lastget.tv_sec == 0 && iter->d_lastget.tv_usec == 0) {
+ ind.modify(iter, [&](DecayingEwmaCollection& dec) { dec.d_lastget = now; });
}
float ret = std::numeric_limits<float>::max();
- const float factor = it->getFactor(now);
- for (auto& entry : it->d_collection) {
+ const float factor = iter->getFactor(now);
+ for (auto& entry : iter->d_collection) {
if (float tmp = entry.second.get(factor); tmp < ret) {
ret = tmp;
}
}
- ind.modify(it, [&](DecayingEwmaCollection& d) { d.d_lastget = now; });
+ ind.modify(iter, [&](DecayingEwmaCollection& dec) { dec.d_lastget = now; });
return ret;
}
};
time_t ttd;
mutable unsigned int count;
};
- typedef multi_index_container<entry_t,
- indexed_by<
- ordered_unique<tag<Thing>, member<entry_t, Thing, &entry_t::thing>>,
- ordered_non_unique<tag<time_t>, member<entry_t, time_t, &entry_t::ttd>>>>
- cont_t;
+ using cont_t = multi_index_container<entry_t,
+ indexed_by<
+ ordered_unique<tag<Thing>, member<entry_t, Thing, &entry_t::thing>>,
+ ordered_non_unique<tag<time_t>, member<entry_t, time_t, &entry_t::ttd>>>>;
- bool shouldThrottle(time_t now, const Thing& t)
+ bool shouldThrottle(time_t now, const Thing& arg)
{
- auto i = d_cont.find(t);
- if (i == d_cont.end()) {
+ auto iter = d_cont.find(arg);
+ if (iter == d_cont.end()) {
return false;
}
- if (now > i->ttd || i->count == 0) {
- d_cont.erase(i);
+ if (now > iter->ttd || iter->count == 0) {
+ d_cont.erase(iter);
return false;
}
- i->count--;
+ iter->count--;
return true; // still listed, still blocked
}
- void throttle(time_t now, const Thing& t, time_t ttl, unsigned int count)
+ void throttle(time_t now, const Thing& arg, time_t ttl, unsigned int count)
{
- auto i = d_cont.find(t);
+ auto iter = d_cont.find(arg);
time_t ttd = now + ttl;
- if (i == d_cont.end()) {
- d_cont.emplace(t, ttd, count);
+ if (iter == d_cont.end()) {
+ d_cont.emplace(arg, ttd, count);
}
- else if (ttd > i->ttd || count > i->count) {
- ttd = std::max(i->ttd, ttd);
- count = std::max(i->count, count);
+ else if (ttd > iter->ttd || count > iter->count) {
+ ttd = std::max(iter->ttd, ttd);
+ count = std::max(iter->count, count);
auto& ind = d_cont.template get<Thing>();
- ind.modify(i, [ttd, count](entry_t& e) { e.ttd = ttd; e.count = count; });
+ ind.modify(iter, [ttd, count](entry_t& entry) { entry.ttd = ttd; entry.count = count; });
}
}
- size_t size() const
+ [[nodiscard]] size_t size() const
{
return d_cont.size();
}
- cont_t getThrottleMap() const
+ [[nodiscard]] cont_t getThrottleMap() const
{
return d_cont;
}
d_cont.clear();
}
+ void clear(const Thing& thing)
+ {
+ d_cont.erase(thing);
+ }
void prune(time_t now)
{
auto& ind = d_cont.template get<time_t>();
struct SavedParentEntry
{
- SavedParentEntry(const DNSName& name, map<DNSName, vector<ComboAddress>>&& nsAddresses, time_t ttd) :
- d_domain(name), d_nsAddresses(nsAddresses), d_ttd(ttd)
+ SavedParentEntry(DNSName name, map<DNSName, vector<ComboAddress>>&& nsAddresses, time_t ttd) :
+ d_domain(std::move(name)), d_nsAddresses(std::move(nsAddresses)), d_ttd(ttd)
{
}
DNSName d_domain;
mutable uint64_t d_count{0};
};
-typedef multi_index_container<
+using SavedParentNSSetBase = multi_index_container<
SavedParentEntry,
indexed_by<ordered_unique<tag<DNSName>, member<SavedParentEntry, DNSName, &SavedParentEntry::d_domain>>,
- ordered_non_unique<tag<time_t>, member<SavedParentEntry, time_t, &SavedParentEntry::d_ttd>>>>
- SavedParentNSSetBase;
+ ordered_non_unique<tag<time_t>, member<SavedParentEntry, time_t, &SavedParentEntry::d_ttd>>>>;
class SavedParentNSSet : public SavedParentNSSetBase
{
}
void inc(const DNSName& name)
{
- auto it = find(name);
- if (it != end()) {
- ++(*it).d_count;
+ auto iter = find(name);
+ if (iter != end()) {
+ ++(*iter).d_count;
}
}
- SavedParentNSSet getMapCopy() const
+ [[nodiscard]] SavedParentNSSet getMapCopy() const
{
return *this;
}
struct DoTStatus
{
- DoTStatus(const ComboAddress& ip, const DNSName& auth, time_t ttd) :
- d_address(ip), d_auth(auth), d_ttd(ttd)
+ DoTStatus(const ComboAddress& address, DNSName auth, time_t ttd) :
+ d_address(address), d_auth(std::move(auth)), d_ttd(ttd)
{
}
enum Status : uint8_t
Bad,
Good
};
- const ComboAddress d_address;
- const DNSName d_auth;
+ ComboAddress d_address;
+ DNSName d_auth;
time_t d_ttd;
mutable uint64_t d_count{0};
mutable Status d_status{Unknown};
std::string toString() const
{
- const std::array<std::string, 4> n{"Unknown", "Busy", "Bad", "Good"};
- unsigned int v = static_cast<unsigned int>(d_status);
- return v >= n.size() ? "?" : n[v];
+ const std::array<std::string, 4> names{"Unknown", "Busy", "Bad", "Good"};
+ auto val = static_cast<unsigned int>(d_status);
+ return val >= names.size() ? "?" : names.at(val);
}
};
static LockGuarded<DoTMap> s_dotMap;
-static const time_t dotFailWait = 24 * 3600;
-static const time_t dotSuccessWait = 3 * 24 * 3600;
+static const time_t dotFailWait = static_cast<time_t>(24) * 3600;
+static const time_t dotSuccessWait = static_cast<time_t>(3) * 24 * 3600;
static bool shouldDoDoT(ComboAddress address, time_t now);
unsigned int SyncRes::s_maxnegttl;
unsigned int SyncRes::s_packetcachenegativettl;
unsigned int SyncRes::s_serverdownmaxfails;
unsigned int SyncRes::s_serverdownthrottletime;
+unsigned int SyncRes::s_unthrottle_n;
unsigned int SyncRes::s_nonresolvingnsmaxfails;
unsigned int SyncRes::s_nonresolvingnsthrottletime;
unsigned int SyncRes::s_ecscachelimitttl;
+unsigned int SyncRes::s_maxvalidationsperq;
+unsigned int SyncRes::s_maxnsec3iterationsperq;
pdns::stat_t SyncRes::s_ecsqueries;
pdns::stat_t SyncRes::s_ecsresponses;
std::map<uint8_t, pdns::stat_t> SyncRes::s_ecsResponsesBySubnetSize4;
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;
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define LOG(x) \
if (d_lm == Log) { \
g_log << Logger::Warning << x; \
{
OptLog ret;
if (d_lm == Log) {
- ret = {prefix, d_fixednow, &g_log};
+ ret = {prefix, d_fixednow, g_log};
}
else if (d_lm == Store) {
- ret = {prefix, d_fixednow, &d_trace};
+ ret = {prefix, d_fixednow, d_trace};
}
return ret;
}
+static bool pushResolveIfNotInNegCache(const DNSName& qname, QType qtype, const struct timeval& now)
+{
+ NegCache::NegCacheEntry negEntry;
+ bool inNegCache = g_negCache->get(qname, qtype, now, negEntry, false);
+ if (!inNegCache) {
+ // There are a few cases where an answer is neither stored in the record cache nor in the neg cache.
+ // An example is a SOA-less NODATA response. Rate limiting will kick in if those tasks are pushed too often.
+ // We might want to fix these cases (and always either store positive or negative) some day.
+ pushResolveTask(qname, qtype, now.tv_sec, now.tv_sec + 60, false);
+ }
+ return !inNegCache;
+}
+
// A helper function to print a double with specific printf format.
// Not using boost::format since it is not thread safe while calling
// into locale handling code according to tsan.
// This allocates a string, but that's nothing compared to what
// boost::format is doing and may even be optimized away anyway.
-static inline std::string fmtfloat(double f)
+static inline std::string fmtfloat(double value)
{
- char buf[20];
- int ret = snprintf(buf, sizeof(buf), "%0.2f", f);
- if (ret < 0 || ret >= static_cast<int>(sizeof(buf))) {
+ std::array<char, 20> buf{};
+ int ret = snprintf(buf.data(), buf.size(), "%0.2f", value);
+ if (ret < 0 || ret >= static_cast<int>(buf.size())) {
return "?";
}
- return std::string(buf, ret);
+ return {buf.data(), static_cast<size_t>(ret)};
}
static inline void accountAuthLatency(uint64_t usec, int family)
SyncRes::SyncRes(const struct timeval& now) :
d_authzonequeries(0), d_outqueries(0), d_tcpoutqueries(0), d_dotoutqueries(0), d_throttledqueries(0), d_timeouts(0), d_unreachables(0), d_totUsec(0), d_fixednow(now), d_now(now), d_cacheonly(false), d_doDNSSEC(false), d_doEDNS0(false), d_qNameMinimization(s_qnameminimization), d_lm(s_lm)
-
{
+ d_validationContext.d_nsec3IterationsRemainingQuota = s_maxnsec3iterationsperq > 0 ? s_maxnsec3iterationsperq : std::numeric_limits<decltype(d_validationContext.d_nsec3IterationsRemainingQuota)>::max();
}
static void allowAdditionalEntry(std::unordered_set<DNSName>& allowedAdditionals, const DNSRecord& rec);
set<GetBestNSAnswer> beenthere;
int res = doResolve(qname, qtype, addRecords, depth, beenthere, context);
setCacheOnly(oldCacheOnly);
- if (res == 0 && addRecords.size() > 0) {
+ if (res == 0 && !addRecords.empty()) {
// We're conservative here. We do not add Bogus records in any circumstance, we add Indeterminates only if no
// validation is required.
if (vStateIsBogus(context.state)) {
}
}
// Not found in cache, check negcache and push task if also not in negcache
- NegCache::NegCacheEntry ne;
- bool inNegCache = g_negCache->get(qname, qtype, d_now, ne, false);
- if (!inNegCache) {
- // There are a few cases where an answer is neither stored in the record cache nor in the neg cache.
- // An example is a SOA-less NODATA response. Rate limiting will kick in if those tasks are pushed too often.
- // We might want to fix these cases (and always either store positive or negative) some day.
- pushResolveTask(qname, qtype, d_now.tv_sec, d_now.tv_sec + 60);
+ if (pushResolveIfNotInNegCache(qname, qtype, d_now)) {
additionalsNotInCache = true;
}
break;
}
auto luaLocal = g_luaconfs.getLocal();
- const auto it = luaLocal->allowAdditionalQTypes.find(qtype);
- if (it == luaLocal->allowAdditionalQTypes.end()) {
+ const auto iter = luaLocal->allowAdditionalQTypes.find(qtype);
+ if (iter == luaLocal->allowAdditionalQTypes.end()) {
return;
}
std::unordered_set<DNSName> addnames;
// - uniqueResults makes sure we never add the same qname/qytype RRSet to the result twice,
// but note that that set might contain multiple elements.
- auto mode = it->second.second;
- for (const auto& targettype : it->second.first) {
+ auto mode = iter->second.second;
+ for (const auto& targettype : iter->second.first) {
for (const auto& addname : addnames) {
std::vector<DNSRecord> records;
bool inserted = uniqueCalls.emplace(addname, targettype).second;
resolveAdditionals(addname, targettype, mode, records, depth, additionalsNotInCache);
}
if (!records.empty()) {
- for (auto r = records.begin(); r != records.end();) {
+ for (auto record = records.begin(); record != records.end();) {
QType covered = QType::ENT;
- if (r->d_type == QType::RRSIG) {
- if (auto rsig = getRR<RRSIGRecordContent>(*r); rsig != nullptr) {
+ if (record->d_type == QType::RRSIG) {
+ if (auto rsig = getRR<RRSIGRecordContent>(*record); rsig != nullptr) {
covered = rsig->d_type;
}
}
- if (uniqueResults.count(std::tuple(r->d_name, QType(r->d_type), covered)) > 0) {
+ if (uniqueResults.count(std::tuple(record->d_name, QType(record->d_type), covered)) > 0) {
// A bit expensive for vectors, but they are small
- r = records.erase(r);
+ record = records.erase(record);
}
else {
- ++r;
+ ++record;
}
}
- for (const auto& r : records) {
- additionals.push_back(r);
+ for (const auto& record : records) {
+ additionals.push_back(record);
QType covered = QType::ENT;
- if (r.d_type == QType::RRSIG) {
- if (auto rsig = getRR<RRSIGRecordContent>(r); rsig != nullptr) {
+ if (record.d_type == QType::RRSIG) {
+ if (auto rsig = getRR<RRSIGRecordContent>(record); rsig != nullptr) {
covered = rsig->d_type;
}
}
- uniqueResults.emplace(r.d_name, r.d_type, covered);
+ uniqueResults.emplace(record.d_name, record.d_type, covered);
}
addAdditionals(targettype, records, additionals, uniqueCalls, uniqueResults, depth, additionaldepth + 1, additionalsNotInCache);
}
return -1;
}
- if (qclass == QClass::ANY)
+ if (qclass == QClass::ANY) {
qclass = QClass::IN;
- else if (qclass != QClass::IN)
+ }
+ else if (qclass != QClass::IN) {
return -1;
+ }
if (qtype == QType::DS) {
d_externalDSQuery = qname;
*/
bool SyncRes::doSpecialNamesResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret)
{
- static const DNSName arpa("1.0.0.127.in-addr.arpa."), ip6_arpa("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."),
- localhost("localhost."), versionbind("version.bind."), idserver("id.server."), versionpdns("version.pdns."), trustanchorserver("trustanchor.server."),
- negativetrustanchorserver("negativetrustanchor.server.");
+ static const DNSName arpa("1.0.0.127.in-addr.arpa.");
+ static const DNSName ip6_arpa("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.");
+ static const DNSName localhost("localhost.");
+ static const DNSName versionbind("version.bind.");
+ static const DNSName idserver("id.server.");
+ static const DNSName versionpdns("version.pdns.");
+ static const DNSName trustanchorserver("trustanchor.server.");
+ static const DNSName negativetrustanchorserver("negativetrustanchor.server.");
bool handled = false;
vector<pair<QType::typeenum, string>> answers;
if ((qname == arpa || qname == ip6_arpa) && qclass == QClass::IN) {
handled = true;
- if (qtype == QType::PTR || qtype == QType::ANY)
+ if (qtype == QType::PTR || qtype == QType::ANY) {
answers.emplace_back(QType::PTR, "localhost.");
+ }
}
if (qname.isPartOf(localhost) && qclass == QClass::IN) {
handled = true;
- if (qtype == QType::A || qtype == QType::ANY)
+ if (qtype == QType::A || qtype == QType::ANY) {
answers.emplace_back(QType::A, "127.0.0.1");
- if (qtype == QType::AAAA || qtype == QType::ANY)
+ }
+ if (qtype == QType::AAAA || qtype == QType::ANY) {
answers.emplace_back(QType::AAAA, "::1");
+ }
}
if ((qname == versionbind || qname == idserver || qname == versionpdns) && qclass == QClass::CHAOS) {
handled = true;
if (qtype == QType::TXT || qtype == QType::ANY) {
- if (qname == versionbind || qname == versionpdns)
+ if (qname == versionbind || qname == versionpdns) {
answers.emplace_back(QType::TXT, "\"" + ::arg()["version-string"] + "\"");
- else if (s_serverID != "disabled")
+ }
+ else if (s_serverID != "disabled") {
answers.emplace_back(QType::TXT, "\"" + s_serverID + "\"");
+ }
}
}
ostringstream ans;
ans << "\"";
ans << negAnchor.first.toString(); // Explicit toString to have a trailing dot
- if (negAnchor.second.length())
+ if (negAnchor.second.length() != 0) {
ans << " " << negAnchor.second;
+ }
ans << "\"";
answers.emplace_back(QType::TXT, ans.str());
}
ret.clear();
d_wasOutOfBand = true;
- DNSRecord dr;
- dr.d_name = qname;
- dr.d_place = DNSResourceRecord::ANSWER;
- dr.d_class = qclass;
- dr.d_ttl = 86400;
+ DNSRecord dnsRecord;
+ dnsRecord.d_name = qname;
+ dnsRecord.d_place = DNSResourceRecord::ANSWER;
+ dnsRecord.d_class = qclass;
+ dnsRecord.d_ttl = 86400;
for (const auto& ans : answers) {
- dr.d_type = ans.first;
- dr.setContent(DNSRecordContent::mastermake(ans.first, qclass, ans.second));
- ret.push_back(dr);
+ dnsRecord.d_type = ans.first;
+ dnsRecord.setContent(DNSRecordContent::make(ans.first, qclass, ans.second));
+ ret.push_back(dnsRecord);
}
}
//! This is the 'out of band resolver', in other words, the authoritative server
void SyncRes::AuthDomain::addSOA(std::vector<DNSRecord>& records) const
{
- SyncRes::AuthDomain::records_t::const_iterator ziter = d_records.find(std::make_tuple(getName(), QType::SOA));
+ SyncRes::AuthDomain::records_t::const_iterator ziter = d_records.find(std::tuple(getName(), QType::SOA));
if (ziter != d_records.end()) {
- DNSRecord dr = *ziter;
- dr.d_place = DNSResourceRecord::AUTHORITY;
- records.push_back(dr);
+ DNSRecord dnsRecord = *ziter;
+ dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+ records.push_back(dnsRecord);
}
}
[[nodiscard]] std::string SyncRes::AuthDomain::print(const std::string& indent,
const std::string& indentLevel) const
{
- std::stringstream s;
- s << indent << "DNSName = " << d_name << std::endl;
- s << indent << "rdForward = " << d_rdForward << std::endl;
- s << indent << "Records {" << std::endl;
+ std::stringstream outputsStream;
+ outputsStream << indent << "DNSName = " << d_name << std::endl;
+ outputsStream << indent << "rdForward = " << d_rdForward << std::endl;
+ outputsStream << indent << "Records {" << std::endl;
auto recordContentIndentation = indent;
recordContentIndentation += indentLevel;
recordContentIndentation += indentLevel;
for (const auto& record : d_records) {
- s << indent << indentLevel << "Record `" << record.d_name << "` {" << std::endl;
- s << record.print(recordContentIndentation);
- s << indent << indentLevel << "}" << std::endl;
+ outputsStream << indent << indentLevel << "Record `" << record.d_name << "` {" << std::endl;
+ outputsStream << record.print(recordContentIndentation);
+ outputsStream << indent << indentLevel << "}" << std::endl;
}
- s << indent << "}" << std::endl;
- s << indent << "Servers {" << std::endl;
+ outputsStream << indent << "}" << std::endl;
+ outputsStream << indent << "Servers {" << std::endl;
for (const auto& server : d_servers) {
- s << indent << indentLevel << server.toString() << std::endl;
+ outputsStream << indent << indentLevel << server.toString() << std::endl;
}
- s << indent << "}" << std::endl;
- return s.str();
+ outputsStream << indent << "}" << std::endl;
+ return outputsStream.str();
}
int SyncRes::AuthDomain::getRecords(const DNSName& qname, const QType qtype, std::vector<DNSRecord>& records) const
}
else if (ziter->d_type == QType::NS && ziter->d_name.countLabels() > getName().countLabels()) {
// we hit a delegation point!
- DNSRecord dr = *ziter;
- dr.d_place = DNSResourceRecord::AUTHORITY;
- records.push_back(dr);
+ DNSRecord dnsRecord = *ziter;
+ dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+ records.push_back(dnsRecord);
}
}
DNSName wcarddomain(qname);
while (wcarddomain != getName() && wcarddomain.chopOff()) {
- range = d_records.equal_range(std::make_tuple(g_wildcarddnsname + wcarddomain));
- if (range.first == range.second)
+ range = d_records.equal_range(std::tuple(g_wildcarddnsname + wcarddomain));
+ if (range.first == range.second) {
continue;
-
+ }
for (ziter = range.first; ziter != range.second; ++ziter) {
- DNSRecord dr = *ziter;
+ DNSRecord dnsRecord = *ziter;
// if we hit a CNAME, just answer that - rest of recursor will do the needful & follow
- if (dr.d_type == qtype || qtype == QType::ANY || dr.d_type == QType::CNAME) {
- dr.d_name = qname;
- dr.d_place = DNSResourceRecord::ANSWER;
- records.push_back(dr);
+ if (dnsRecord.d_type == qtype || qtype == QType::ANY || dnsRecord.d_type == QType::CNAME) {
+ dnsRecord.d_name = qname;
+ dnsRecord.d_place = DNSResourceRecord::ANSWER;
+ records.push_back(dnsRecord);
}
}
/* Nothing for this name, no wildcard, let's see if there is some NS */
DNSName nsdomain(qname);
while (nsdomain.chopOff() && nsdomain != getName()) {
- range = d_records.equal_range(std::make_tuple(nsdomain, QType::NS));
- if (range.first == range.second)
+ range = d_records.equal_range(std::tuple(nsdomain, QType::NS));
+ if (range.first == range.second) {
continue;
-
+ }
for (ziter = range.first; ziter != range.second; ++ziter) {
- DNSRecord dr = *ziter;
- dr.d_place = DNSResourceRecord::AUTHORITY;
- records.push_back(dr);
+ DNSRecord dnsRecord = *ziter;
+ dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+ records.push_back(dnsRecord);
}
}
bool SyncRes::doOOBResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int /* depth */, const string& prefix, int& res)
{
DNSName authdomain(qname);
- domainmap_t::const_iterator iter = getBestAuthZone(&authdomain);
+ const auto iter = getBestAuthZone(&authdomain);
if (iter == t_sstorage.domainmap->end() || !iter->second.isAuth()) {
LOG(prefix << qname << ": Auth storage has no zone for this query!" << endl);
return false;
return doOOBResolve(iter->second, qname, qtype, ret, res);
}
-bool SyncRes::isRecursiveForwardOrAuth(const DNSName& qname) const
+bool SyncRes::isRecursiveForwardOrAuth(const DNSName& qname)
{
DNSName authname(qname);
- domainmap_t::const_iterator iter = getBestAuthZone(&authname);
+ const auto iter = getBestAuthZone(&authname);
return iter != t_sstorage.domainmap->end() && (iter->second.isAuth() || iter->second.shouldRecurse());
}
-bool SyncRes::isForwardOrAuth(const DNSName& qname) const
+bool SyncRes::isForwardOrAuth(const DNSName& qname)
{
DNSName authname(qname);
- domainmap_t::const_iterator iter = getBestAuthZone(&authname);
+ const auto iter = getBestAuthZone(&authname);
return iter != t_sstorage.domainmap->end();
}
-const char* isoDateTimeMillis(const struct timeval& tv, char* buf, size_t sz)
+const char* isoDateTimeMillis(const struct timeval& tval, timebuf_t& buf)
{
const std::string s_timestampFormat = "%Y-%m-%dT%T";
- struct tm tm;
- size_t len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&tv.tv_sec, &tm));
+ struct tm tmval
+ {
+ };
+ size_t len = strftime(buf.data(), buf.size(), s_timestampFormat.c_str(), localtime_r(&tval.tv_sec, &tmval));
if (len == 0) {
- int ret = snprintf(buf, sz, "%lld", static_cast<long long>(tv.tv_sec));
- if (ret < 0 || static_cast<size_t>(ret) >= sz) {
- if (sz > 0) {
- buf[0] = '\0';
- }
- return buf;
+ int ret = snprintf(buf.data(), buf.size(), "%lld", static_cast<long long>(tval.tv_sec));
+ if (ret < 0 || static_cast<size_t>(ret) >= buf.size()) {
+ buf[0] = '\0';
+ return buf.data();
}
len = ret;
}
- if (sz > len + 4) {
- snprintf(buf + len, sz - len, ".%03ld", static_cast<long>(tv.tv_usec) / 1000);
+ if (buf.size() > len + 4) {
+ snprintf(&buf.at(len), buf.size() - len, ".%03ld", static_cast<long>(tval.tv_usec) / 1000);
}
- return buf;
+ return buf.data();
}
-static const char* timestamp(time_t t, char* buf, size_t sz)
+static const char* timestamp(time_t arg, timebuf_t& buf)
{
const std::string s_timestampFormat = "%Y-%m-%dT%T";
- struct tm tm;
- size_t len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&t, &tm));
+ struct tm tmval
+ {
+ };
+ size_t len = strftime(buf.data(), buf.size(), s_timestampFormat.c_str(), localtime_r(&arg, &tmval));
if (len == 0) {
- int ret = snprintf(buf, sz, "%lld", static_cast<long long>(t));
- if (ret < 0 || static_cast<size_t>(ret) >= sz) {
- if (sz > 0) {
- buf[0] = '\0';
- }
+ int ret = snprintf(buf.data(), buf.size(), "%lld", static_cast<long long>(arg));
+ if (ret < 0 || static_cast<size_t>(ret) >= buf.size()) {
+ buf[0] = '\0';
}
}
- return buf;
+ return buf.data();
}
struct ednsstatus_t : public multi_index_container<SyncRes::EDNSStatus,
ordered_non_unique<tag<time_t>, member<SyncRes::EDNSStatus, time_t, &SyncRes::EDNSStatus::ttd>>>>
{
// Get a copy
- ednsstatus_t getMap() const
+ [[nodiscard]] ednsstatus_t getMap() const
{
return *this;
}
- void setMode(index<ComboAddress>::type& ind, iterator it, SyncRes::EDNSStatus::EDNSMode mode, time_t ts)
+ static void setMode(index<ComboAddress>::type& ind, iterator iter, SyncRes::EDNSStatus::EDNSMode mode, time_t theTime)
{
- if (it->mode != mode || it->ttd == 0) {
- ind.modify(it, [=](SyncRes::EDNSStatus& s) { s.mode = mode; s.ttd = ts + Expire; });
+ if (iter->mode != mode || iter->ttd == 0) {
+ ind.modify(iter, [=](SyncRes::EDNSStatus& status) { status.mode = mode; status.ttd = theTime + Expire; });
}
}
SyncRes::EDNSStatus::EDNSMode SyncRes::getEDNSStatus(const ComboAddress& server)
{
auto lock = s_ednsstatus.lock();
- const auto& it = lock->find(server);
- if (it == lock->end()) {
+ const auto& iter = lock->find(server);
+ if (iter == lock->end()) {
return EDNSStatus::EDNSOK;
}
- return it->mode;
+ return iter->mode;
}
uint64_t SyncRes::getEDNSStatusesSize()
s_ednsstatus.lock()->prune(cutoff);
}
-uint64_t SyncRes::doEDNSDump(int fd)
+uint64_t SyncRes::doEDNSDump(int fileDesc)
{
- int newfd = dup(fd);
+ int newfd = dup(fileDesc);
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
close(newfd);
return 0;
}
uint64_t count = 0;
- fprintf(fp.get(), "; edns dump follows\n; ip\tstatus\tttd\n");
+ fprintf(filePtr.get(), "; edns dump follows\n; ip\tstatus\tttd\n");
const auto copy = s_ednsstatus.lock()->getMap();
for (const auto& eds : copy) {
count++;
- char tmp[26];
- fprintf(fp.get(), "%s\t%s\t%s\n", eds.address.toString().c_str(), eds.toString().c_str(), timestamp(eds.ttd, tmp, sizeof(tmp)));
+ timebuf_t tmp;
+ fprintf(filePtr.get(), "%s\t%s\t%s\n", eds.address.toString().c_str(), eds.toString().c_str(), timestamp(eds.ttd, tmp));
}
return count;
}
return s_nsSpeeds.lock()->size();
}
-void SyncRes::submitNSSpeed(const DNSName& server, const ComboAddress& ca, uint32_t usec, const struct timeval& now)
+void SyncRes::submitNSSpeed(const DNSName& server, const ComboAddress& address, int usec, const struct timeval& now)
{
auto lock = s_nsSpeeds.lock();
- lock->find_or_enter(server, now).submit(ca, usec, now);
+ lock->find_or_enter(server, now).submit(address, usec, now);
}
void SyncRes::clearNSSpeeds()
s_nsSpeeds.lock()->clear();
}
-float SyncRes::getNSSpeed(const DNSName& server, const ComboAddress& ca)
+float SyncRes::getNSSpeed(const DNSName& server, const ComboAddress& address)
{
auto lock = s_nsSpeeds.lock();
- return lock->find_or_enter(server).d_collection[ca].peek();
+ return lock->find_or_enter(server).d_collection[address].peek();
}
-uint64_t SyncRes::doDumpNSSpeeds(int fd)
+uint64_t SyncRes::doDumpNSSpeeds(int fileDesc)
{
- int newfd = dup(fd);
+ int newfd = dup(fileDesc);
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
close(newfd);
return 0;
}
- fprintf(fp.get(), "; nsspeed dump follows\n; nsname\ttimestamp\t[ip/decaying-ms/last-ms...]\n");
+ fprintf(filePtr.get(), "; nsspeed dump follows\n; nsname\ttimestamp\t[ip/decaying-ms/last-ms...]\n");
uint64_t count = 0;
// Create a copy to avoid holding the lock while doing I/O
- for (const auto& i : *s_nsSpeeds.lock()) {
+ for (const auto& iter : *s_nsSpeeds.lock()) {
count++;
// an <empty> can appear hear in case of authoritative (hosted) zones
- char tmp[26];
- fprintf(fp.get(), "%s\t%s\t", i.d_name.toLogString().c_str(), isoDateTimeMillis(i.d_lastget, tmp, sizeof(tmp)));
+ timebuf_t tmp;
+ fprintf(filePtr.get(), "%s\t%s\t", iter.d_name.toLogString().c_str(), isoDateTimeMillis(iter.d_lastget, tmp));
bool first = true;
- for (const auto& j : i.d_collection) {
- fprintf(fp.get(), "%s%s/%.3f/%.3f", first ? "" : "\t", j.first.toStringWithPortExcept(53).c_str(), j.second.peek() / 1000.0f, j.second.last() / 1000.0f);
+ for (const auto& line : iter.d_collection) {
+ fprintf(filePtr.get(), "%s%s/%.3f/%.3f", first ? "" : "\t", line.first.toStringWithPortExcept(53).c_str(), line.second.peek() / 1000.0F, static_cast<float>(line.second.last()) / 1000.0F);
first = false;
}
- fprintf(fp.get(), "\n");
+ fprintf(filePtr.get(), "\n");
}
return count;
}
bool SyncRes::isThrottled(time_t now, const ComboAddress& server, const DNSName& target, QType qtype)
{
- return s_throttle.lock()->shouldThrottle(now, std::make_tuple(server, target, qtype));
+ return s_throttle.lock()->shouldThrottle(now, std::tuple(server, target, qtype));
}
bool SyncRes::isThrottled(time_t now, const ComboAddress& server)
{
- return s_throttle.lock()->shouldThrottle(now, std::make_tuple(server, g_rootdnsname, 0));
+ auto throttled = s_throttle.lock()->shouldThrottle(now, std::tuple(server, g_rootdnsname, 0));
+ if (throttled) {
+ // Give fully throttled servers a chance to be used, to avoid having one bad zone spoil the NS
+ // record for others using the same NS. If the NS answers, it will be unThrottled immediately
+ if (s_unthrottle_n > 0 && dns_random(s_unthrottle_n) == 0) {
+ throttled = false;
+ }
+ }
+ return throttled;
+}
+
+void SyncRes::unThrottle(const ComboAddress& server, const DNSName& name, QType qtype)
+{
+ s_throttle.lock()->clear(std::tuple(server, g_rootdnsname, 0));
+ s_throttle.lock()->clear(std::tuple(server, name, qtype));
}
void SyncRes::doThrottle(time_t now, const ComboAddress& server, time_t duration, unsigned int tries)
{
- s_throttle.lock()->throttle(now, std::make_tuple(server, g_rootdnsname, 0), duration, tries);
+ s_throttle.lock()->throttle(now, std::tuple(server, g_rootdnsname, 0), duration, tries);
}
void SyncRes::doThrottle(time_t now, const ComboAddress& server, const DNSName& name, QType qtype, time_t duration, unsigned int tries)
{
- s_throttle.lock()->throttle(now, std::make_tuple(server, name, qtype), duration, tries);
+ s_throttle.lock()->throttle(now, std::tuple(server, name, qtype), duration, tries);
}
-uint64_t SyncRes::doDumpThrottleMap(int fd)
+uint64_t SyncRes::doDumpThrottleMap(int fileDesc)
{
- int newfd = dup(fd);
+ int newfd = dup(fileDesc);
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
close(newfd);
return 0;
}
- fprintf(fp.get(), "; throttle map dump follows\n");
- fprintf(fp.get(), "; remote IP\tqname\tqtype\tcount\tttd\n");
+ fprintf(filePtr.get(), "; throttle map dump follows\n");
+ fprintf(filePtr.get(), "; remote IP\tqname\tqtype\tcount\tttd\n");
uint64_t count = 0;
// Get a copy to avoid holding the lock while doing I/O
const auto throttleMap = s_throttle.lock()->getThrottleMap();
- for (const auto& i : throttleMap) {
+ for (const auto& iter : throttleMap) {
count++;
- char tmp[26];
+ timebuf_t tmp;
// remote IP, dns name, qtype, count, ttd
- fprintf(fp.get(), "%s\t%s\t%s\t%u\t%s\n", std::get<0>(i.thing).toString().c_str(), std::get<1>(i.thing).toLogString().c_str(), std::get<2>(i.thing).toString().c_str(), i.count, timestamp(i.ttd, tmp, sizeof(tmp)));
+ fprintf(filePtr.get(), "%s\t%s\t%s\t%u\t%s\n", std::get<0>(iter.thing).toString().c_str(), std::get<1>(iter.thing).toLogString().c_str(), std::get<2>(iter.thing).toString().c_str(), iter.count, timestamp(iter.ttd, tmp));
}
return count;
return s_fails.lock()->value(server);
}
-uint64_t SyncRes::doDumpFailedServers(int fd)
+uint64_t SyncRes::doDumpFailedServers(int fileDesc)
{
- int newfd = dup(fd);
+ int newfd = dup(fileDesc);
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
close(newfd);
return 0;
}
- fprintf(fp.get(), "; failed servers dump follows\n");
- fprintf(fp.get(), "; remote IP\tcount\ttimestamp\n");
+ fprintf(filePtr.get(), "; failed servers dump follows\n");
+ fprintf(filePtr.get(), "; remote IP\tcount\ttimestamp\n");
uint64_t count = 0;
// We get a copy, so the I/O does not need to happen while holding the lock
- for (const auto& i : s_fails.lock()->getMapCopy()) {
+ for (const auto& iter : s_fails.lock()->getMapCopy()) {
count++;
- char tmp[26];
- fprintf(fp.get(), "%s\t%" PRIu64 "\t%s\n", i.key.toString().c_str(), i.value, timestamp(i.last, tmp, sizeof(tmp)));
+ timebuf_t tmp;
+ fprintf(filePtr.get(), "%s\t%" PRIu64 "\t%s\n", iter.key.toString().c_str(), iter.value, timestamp(iter.last, tmp));
}
return count;
s_nonresolving.lock()->prune(cutoff);
}
-uint64_t SyncRes::doDumpNonResolvingNS(int fd)
+uint64_t SyncRes::doDumpNonResolvingNS(int fileDesc)
{
- int newfd = dup(fd);
+ int newfd = dup(fileDesc);
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
close(newfd);
return 0;
}
- fprintf(fp.get(), "; non-resolving nameserver dump follows\n");
- fprintf(fp.get(), "; name\tcount\ttimestamp\n");
+ fprintf(filePtr.get(), "; non-resolving nameserver dump follows\n");
+ fprintf(filePtr.get(), "; name\tcount\ttimestamp\n");
uint64_t count = 0;
// We get a copy, so the I/O does not need to happen while holding the lock
- for (const auto& i : s_nonresolving.lock()->getMapCopy()) {
+ for (const auto& iter : s_nonresolving.lock()->getMapCopy()) {
count++;
- char tmp[26];
- fprintf(fp.get(), "%s\t%" PRIu64 "\t%s\n", i.key.toString().c_str(), i.value, timestamp(i.last, tmp, sizeof(tmp)));
+ timebuf_t tmp;
+ fprintf(filePtr.get(), "%s\t%" PRIu64 "\t%s\n", iter.key.toString().c_str(), iter.value, timestamp(iter.last, tmp));
}
return count;
s_savedParentNSSet.lock()->prune(now);
}
-uint64_t SyncRes::doDumpSavedParentNSSets(int fd)
+uint64_t SyncRes::doDumpSavedParentNSSets(int fileDesc)
{
- int newfd = dup(fd);
+ int newfd = dup(fileDesc);
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
close(newfd);
return 0;
}
- fprintf(fp.get(), "; dump of saved parent nameserver sets succesfully used follows\n");
- fprintf(fp.get(), "; total entries: %zu\n", s_savedParentNSSet.lock()->size());
- fprintf(fp.get(), "; domain\tsuccess\tttd\n");
+ fprintf(filePtr.get(), "; dump of saved parent nameserver sets succesfully used follows\n");
+ fprintf(filePtr.get(), "; total entries: %zu\n", s_savedParentNSSet.lock()->size());
+ fprintf(filePtr.get(), "; domain\tsuccess\tttd\n");
uint64_t count = 0;
// We get a copy, so the I/O does not need to happen while holding the lock
- for (const auto& i : s_savedParentNSSet.lock()->getMapCopy()) {
- if (i.d_count == 0) {
+ for (const auto& iter : s_savedParentNSSet.lock()->getMapCopy()) {
+ if (iter.d_count == 0) {
continue;
}
count++;
- char tmp[26];
- fprintf(fp.get(), "%s\t%" PRIu64 "\t%s\n", i.d_domain.toString().c_str(), i.d_count, timestamp(i.d_ttd, tmp, sizeof(tmp)));
+ timebuf_t tmp;
+ fprintf(filePtr.get(), "%s\t%" PRIu64 "\t%s\n", iter.d_domain.toString().c_str(), iter.d_count, timestamp(iter.d_ttd, tmp));
}
return count;
}
}
}
-uint64_t SyncRes::doDumpDoTProbeMap(int fd)
+uint64_t SyncRes::doDumpDoTProbeMap(int fileDesc)
{
- int newfd = dup(fd);
+ int newfd = dup(fileDesc);
if (newfd == -1) {
return 0;
}
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+ if (!filePtr) {
close(newfd);
return 0;
}
- fprintf(fp.get(), "; DoT probing map follows\n");
- fprintf(fp.get(), "; ip\tdomain\tcount\tstatus\tttd\n");
+ fprintf(filePtr.get(), "; DoT probing map follows\n");
+ fprintf(filePtr.get(), "; ip\tdomain\tcount\tstatus\tttd\n");
uint64_t count = 0;
// We get a copy, so the I/O does not need to happen while holding the lock
{
copy = *s_dotMap.lock();
}
- fprintf(fp.get(), "; %" PRIu64 " Busy entries\n", copy.d_numBusy);
- for (const auto& i : copy.d_map) {
+ fprintf(filePtr.get(), "; %" PRIu64 " Busy entries\n", copy.d_numBusy);
+ for (const auto& iter : copy.d_map) {
count++;
- char tmp[26];
- fprintf(fp.get(), "%s\t%s\t%" PRIu64 "\t%s\t%s\n", i.d_address.toString().c_str(), i.d_auth.toString().c_str(), i.d_count, i.toString().c_str(), timestamp(i.d_ttd, tmp, sizeof(tmp)));
+ timebuf_t tmp;
+ fprintf(filePtr.get(), "%s\t%s\t%" PRIu64 "\t%s\t%s\n", iter.d_address.toString().c_str(), iter.d_auth.toString().c_str(), iter.d_count, iter.toString().c_str(), timestamp(iter.d_ttd, tmp));
}
return count;
}
For now this means we can't be clever, but will turn off DNSSEC if you reply with FormError or gibberish.
*/
-LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const
+LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& address, bool ednsMANDATORY, const DNSName& domain, [[maybe_unused]] const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const
{
/* what is your QUEST?
the goal is to get as many remotes as possible on the best level of EDNS support
SyncRes::EDNSStatus::EDNSMode mode = EDNSStatus::EDNSOK;
{
auto lock = s_ednsstatus.lock();
- auto ednsstatus = lock->find(ip); // does this include port? YES
+ auto ednsstatus = lock->find(address); // does this include port? YES
if (ednsstatus != lock->end()) {
- if (ednsstatus->ttd && ednsstatus->ttd < d_now.tv_sec) {
+ if (ednsstatus->ttd != 0 && ednsstatus->ttd < d_now.tv_sec) {
lock->erase(ednsstatus);
}
else {
int EDNSLevel = 0;
auto luaconfsLocal = g_luaconfs.getLocal();
- ResolveContext ctx;
- ctx.d_initialRequestId = d_initialRequestId;
- ctx.d_nsName = nsName;
+ ResolveContext ctx(d_initialRequestId, nsName);
#ifdef HAVE_FSTRM
ctx.d_auth = auth;
#endif
- LWResult::Result ret;
+ LWResult::Result ret{};
for (int tries = 0; tries < 2; ++tries) {
}
if (d_asyncResolve) {
- ret = d_asyncResolve(ip, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, res, chained);
+ ret = d_asyncResolve(address, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, res, chained);
}
else {
- ret = asyncresolve(ip, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, d_outgoingProtobufServers, d_frameStreamServers, luaconfsLocal->outgoingProtobufExportConfig.exportTypes, res, chained);
+ ret = asyncresolve(address, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, d_outgoingProtobufServers, d_frameStreamServers, luaconfsLocal->outgoingProtobufExportConfig.exportTypes, res, chained);
}
if (ret == LWResult::Result::PermanentError || ret == LWResult::Result::OSLimitError || ret == LWResult::Result::Spoofed) {
// Determine new mode
if (res->d_validpacket && !res->d_haveEDNS && res->d_rcode == RCode::FormErr) {
mode = EDNSStatus::NOEDNS;
- auto ednsstatus = lock->insert(ip).first;
+ auto ednsstatus = lock->insert(address).first;
auto& ind = lock->get<ComboAddress>();
lock->setMode(ind, ednsstatus, mode, d_now.tv_sec);
// This is the only path that re-iterates the loop
continue;
}
- else if (!res->d_haveEDNS) {
- auto ednsstatus = lock->insert(ip).first;
+ if (!res->d_haveEDNS) {
+ auto ednsstatus = lock->insert(address).first;
auto& ind = lock->get<ComboAddress>();
lock->setMode(ind, ednsstatus, EDNSStatus::EDNSIGNORANT, d_now.tv_sec);
}
else {
// New status is EDNSOK
- lock->erase(ip);
+ lock->erase(address);
}
}
}
/* The parameters from rfc9156. */
-/* maximum number of QNAME minimisation iterations */
-static const unsigned int s_max_minimise_count = 10;
-/* number of queries that should only have one label appended */
-static const unsigned int s_minimise_one_lab = 4;
+/* maximum number of QNAME minimization iterations */
+unsigned int SyncRes::s_max_minimize_count; // default is 10
+/* number of iterations that should only have one label appended */
+unsigned int SyncRes::s_minimize_one_label; // default is 4
-static unsigned int qmStepLen(unsigned int labels, unsigned int qnamelen, unsigned int i)
+static unsigned int qmStepLen(unsigned int labels, unsigned int qnamelen, unsigned int qmIteration)
{
- unsigned int step;
+ unsigned int step{};
- if (i < s_minimise_one_lab) {
+ if (qmIteration < SyncRes::s_minimize_one_label) {
step = 1;
}
- else if (i < s_max_minimise_count) {
- step = std::max(1U, (qnamelen - labels) / (10 - i));
+ else if (qmIteration < SyncRes::s_max_minimize_count) {
+ step = std::max(1U, (qnamelen - labels) / (SyncRes::s_max_minimize_count - qmIteration));
}
else {
step = qnamelen - labels;
return targetlen;
}
-int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context)
+static string resToString(int res)
+{
+ return res >= 0 ? RCode::to_s(res) : std::to_string(res);
+}
+
+int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context) // NOLINT(readability-function-cognitive-complexity)
{
auto prefix = getPrefix(depth);
auto luaconfsLocal = g_luaconfs.getLocal();
LOG(prefix << qname << ": Step0 qname is in a forwarded domain " << fwdomain << endl);
}
- for (unsigned int i = 0; i <= qnamelen;) {
+ for (unsigned int i = 0; i <= qnamelen; i++) {
// Step 1
vector<DNSRecord> bestns;
}
}
- if (bestns.size() == 0) {
+ if (bestns.empty()) {
if (!forwarded) {
// Something terrible is wrong
LOG(prefix << qname << ": Step1 No ancestor found return ServFail" << endl);
if (child == qname) {
LOG(prefix << qname << ": Step3 Going to do final resolve" << endl);
res = doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, context);
- LOG(prefix << qname << ": Step3 Final resolve: " << RCode::to_s(res) << "/" << ret.size() << endl);
+ LOG(prefix << qname << ": Step3 Final resolve: " << resToString(res) << "/" << ret.size() << endl);
return res;
}
- // If we have seen this child during resolution already; just skip it. We tried to QM it already or otherwise broken.
- bool skipStep4 = false;
+ // If we have seen this child during resolution already; we tried to QM it already or otherwise broken.
+ // fall back to no-QM
+ bool qmLoopDetected = false;
for (const auto& visitedNS : beenthere) {
if (visitedNS.qname == child) {
- skipStep4 = true;
+ qmLoopDetected = true;
break;
}
}
- if (skipStep4) {
- LOG(prefix << ": Step4 Being skipped as visited this child name already" << endl);
- continue;
+ if (qmLoopDetected) {
+ LOG(prefix << qname << ": Step4 loop detected as visited this child name already, fallback to no QM" << endl);
+ res = doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, context);
+ LOG(prefix << qname << ": Step4 Final resolve: " << resToString(res) << "/" << ret.size() << endl);
+ return res;
}
// Step 4
else {
// as doResolveNoQNameMinimization clears the EDE, we put it back here, it is relevant but might not be set by the last effort attempt
if (!context.extendedError) {
- context.extendedError = oldEDE;
+ context.extendedError = std::move(oldEDE);
}
}
- LOG(prefix << qname << ": Step5 End resolve: " << RCode::to_s(res) << "/" << ret.size() << endl);
+ LOG(prefix << qname << ": Step5 End resolve: " << resToString(res) << "/" << ret.size() << endl);
return res;
}
}
* \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);
LOG(prefix << qname << ": Wants " << (d_doDNSSEC ? "" : "NO ") << "DNSSEC processing, " << (d_requireAuthData ? "" : "NO ") << "auth data required by query for " << qtype << endl);
+ d_maxdepth = std::max(d_maxdepth, depth);
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);
+ throw ImmediateServFailException(std::move(msg));
}
}
for (int loop = 0; loop < iterations; loop++) {
d_serveStale = loop == 1;
-
+ if (d_serveStale) {
+ LOG(prefix << qname << ": Restart, with serve-stale enabled" << endl);
+ }
// This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
- if (!(d_updatingRootNS && qtype.getCode() == QType::NS && qname.isRoot())) {
+ if (!d_updatingRootNS || qtype.getCode() != QType::NS || !qname.isRoot()) {
DNSName authname(qname);
const auto iter = getBestAuthZone(&authname);
/* When we are looking for a DS, we want to the non-CNAME cache check first
because we can actually have a DS (from the parent zone) AND a CNAME (from
the child zone), and what we really want is the DS */
- if (qtype != QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
+ if (qtype != QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse, loop == 1)) { // will reroute us if needed
d_wasOutOfBand = wasAuthZone;
// Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
// are in QM Step0) we might have a CNAME but not the corresponding target.
// RPZ rules will not be evaluated anymore (we already matched).
const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
- if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
+ if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
*fromCache = true;
}
/* Apply Post filtering policies */
mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
bool done = false;
handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
- if (done && fromCache) {
+ if (done && fromCache != nullptr) {
*fromCache = true;
}
}
if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, prefix, res, context)) {
// we done
d_wasOutOfBand = wasAuthZone;
- if (fromCache) {
+ if (fromCache != nullptr) {
*fromCache = true;
}
}
/* if we have not found a cached DS (or denial of), now is the time to look for a CNAME */
- if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
+ if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse, loop == 1)) { // will reroute us if needed
d_wasOutOfBand = wasAuthZone;
// Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
// are in QM Step0) we might have a CNAME but not the corresponding target.
// RPZ rules will not be evaluated anymore (we already matched).
const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
- if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
+ if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
*fromCache = true;
}
/* Apply Post filtering policies */
mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
bool done = false;
handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
- if (done && fromCache) {
+ if (done && fromCache != nullptr) {
*fromCache = true;
}
}
LOG(prefix << qname << ": No cache hit for '" << qname << "|" << qtype << "', trying to find an appropriate NS record" << endl);
DNSName subdomain(qname);
- if (qtype == QType::DS)
+ if (qtype == QType::DS) {
subdomain.chopOff();
+ }
NsSet nsset;
bool flawedNSSet = false;
{
auto lock = s_savedParentNSSet.lock();
auto domainData = lock->find(subdomain);
- if (domainData != lock->end() && domainData->d_nsAddresses.size() > 0) {
+ if (domainData != lock->end() && !domainData->d_nsAddresses.empty()) {
nsset.clear();
// Build the nsset arg and fallBack data for the fallback doResolveAt() attempt
// Take a copy to be able to release the lock, NsSet is actually a map, go figure
- for (const auto& ns : domainData->d_nsAddresses) {
- nsset.emplace(ns.first, pair(std::vector<ComboAddress>(), false));
- fallBack.emplace(ns.first, ns.second);
+ for (const auto& nsAddress : domainData->d_nsAddresses) {
+ nsset.emplace(nsAddress.first, pair(std::vector<ComboAddress>(), false));
+ fallBack.emplace(nsAddress.first, nsAddress.second);
}
}
}
- if (fallBack.size() > 0) {
+ if (!fallBack.empty()) {
LOG(prefix << qname << ": Failure, but we have a saved parent NS set, trying that one" << endl);
res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, prefix, beenthere, context, stopAtDelegation, &fallBack);
if (res == 0) {
}
}
- if (!res) {
+ if (res == 0) {
return 0;
}
{
speedOrderCA(std::map<ComboAddress, float>& speeds) :
d_speeds(speeds) {}
- bool operator()(const ComboAddress& a, const ComboAddress& b) const
+ bool operator()(const ComboAddress& lhs, const ComboAddress& rhs) const
{
- return d_speeds[a] < d_speeds[b];
+ return d_speeds[lhs] < d_speeds[rhs];
}
- std::map<ComboAddress, float>& d_speeds;
+ std::map<ComboAddress, float>& d_speeds; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members): nothing wrong afaiks
};
+void SyncRes::selectNSOnSpeed(const DNSName& qname, const string& prefix, vector<ComboAddress>& ret)
+{
+ /* we need to remove from the nsSpeeds collection the existing IPs
+ for this nameserver that are no longer in the set, even if there
+ is only one or none at all in the current set.
+ */
+ map<ComboAddress, float> speeds;
+ {
+ auto lock = s_nsSpeeds.lock();
+ const auto& collection = lock->find_or_enter(qname, d_now);
+ float factor = collection.getFactor(d_now);
+ for (const auto& val : ret) {
+ speeds[val] = collection.d_collection[val].get(factor);
+ }
+ collection.purge(speeds);
+ }
+
+ if (ret.size() > 1) {
+ shuffle(ret.begin(), ret.end(), pdns::dns_random_engine());
+ speedOrderCA speedOrder(speeds);
+ stable_sort(ret.begin(), ret.end(), speedOrder);
+ }
+
+ if (doLog()) {
+ LOG(prefix << qname << ": Nameserver " << qname << " IPs: ");
+ bool first = true;
+ for (const auto& addr : ret) {
+ if (first) {
+ first = false;
+ }
+ else {
+ LOG(", ");
+ }
+ LOG((addr.toString()) << "(" << fmtfloat(speeds[addr] / 1000.0) << "ms)");
+ }
+ LOG(endl);
+ }
+}
+
+template <typename T>
+static bool collectAddresses(const vector<DNSRecord>& cset, vector<ComboAddress>& ret)
+{
+ bool pushed = false;
+ for (const auto& record : cset) {
+ if (auto rec = getRR<T>(record)) {
+ ret.push_back(rec->getCA(53));
+ pushed = true;
+ }
+ }
+ return pushed;
+}
+
/** This function explicitly goes out for A or AAAA addresses
*/
vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS)
const unsigned int startqueries = d_outqueries;
d_requireAuthData = false;
d_DNSSECValidationRequested = false;
- d_followCNAME = true;
+ d_followCNAME = false;
MemRecursorCache::Flags flags = MemRecursorCache::None;
if (d_serveStale) {
// First look for both A and AAAA in the cache
res_t cset;
if (s_doIPv4 && g_recCache->get(d_now.tv_sec, qname, QType::A, flags, &cset, d_cacheRemote, d_routingTag) > 0) {
- for (const auto& i : cset) {
- if (auto rec = getRR<ARecordContent>(i)) {
- ret.push_back(rec->getCA(53));
- }
- }
+ collectAddresses<ARecordContent>(cset, ret);
}
if (s_doIPv6 && g_recCache->get(d_now.tv_sec, qname, QType::AAAA, flags, &cset, d_cacheRemote, d_routingTag) > 0) {
- for (const auto& i : cset) {
- if (auto rec = getRR<AAAARecordContent>(i)) {
- seenV6 = true;
- ret.push_back(rec->getCA(53));
- }
+ if (collectAddresses<AAAARecordContent>(cset, ret)) {
+ seenV6 = true;
}
}
if (ret.empty()) {
Context newContext1;
cset.clear();
// Go out to get A's
- if (s_doIPv4 && doResolve(qname, QType::A, cset, depth + 1, beenthere, newContext1) == 0) { // this consults cache, OR goes out
- for (auto const& i : cset) {
- if (i.d_type == QType::A) {
- if (auto rec = getRR<ARecordContent>(i)) {
- ret.push_back(rec->getCA(53));
- }
- }
- }
+ if (s_doIPv4 && doResolveNoQNameMinimization(qname, QType::A, cset, depth + 1, beenthere, newContext1) == 0) { // this consults cache, OR goes out
+ collectAddresses<ARecordContent>(cset, ret);
}
if (s_doIPv6) { // s_doIPv6 **IMPLIES** pdns::isQueryLocalAddressFamilyEnabled(AF_INET6) returned true
if (ret.empty()) {
// We only go out immediately to find IPv6 records if we did not find any IPv4 ones.
Context newContext2;
- if (doResolve(qname, QType::AAAA, cset, depth + 1, beenthere, newContext2) == 0) { // this consults cache, OR goes out
- for (const auto& i : cset) {
- if (i.d_type == QType::AAAA) {
- if (auto rec = getRR<AAAARecordContent>(i)) {
- seenV6 = true;
- ret.push_back(rec->getCA(53));
- }
- }
+ if (doResolveNoQNameMinimization(qname, QType::AAAA, cset, depth + 1, beenthere, newContext2) == 0) { // this consults cache, OR goes out
+ if (collectAddresses<AAAARecordContent>(cset, ret)) {
+ seenV6 = true;
}
}
}
// We have some IPv4 records, consult the cache, we might have encountered some IPv6 glue
cset.clear();
if (g_recCache->get(d_now.tv_sec, qname, QType::AAAA, flags, &cset, d_cacheRemote, d_routingTag) > 0) {
- for (const auto& i : cset) {
- if (auto rec = getRR<AAAARecordContent>(i)) {
- seenV6 = true;
- ret.push_back(rec->getCA(53));
- }
+ if (collectAddresses<AAAARecordContent>(cset, ret)) {
+ seenV6 = true;
}
}
}
if (s_doIPv6 && !seenV6 && !cacheOnly) {
// No IPv6 records in cache, check negcache and submit async task if negache does not have the data
// so that the next time the cache or the negcache will have data
- NegCache::NegCacheEntry ne;
- bool inNegCache = g_negCache->get(qname, QType::AAAA, d_now, ne, false);
- if (!inNegCache) {
- pushResolveTask(qname, QType::AAAA, d_now.tv_sec, d_now.tv_sec + 60);
- }
+ pushResolveIfNotInNegCache(qname, QType::AAAA, d_now);
}
}
catch (const PolicyHitException&) {
}
}
}
- /* we need to remove from the nsSpeeds collection the existing IPs
- for this nameserver that are no longer in the set, even if there
- is only one or none at all in the current set.
- */
- map<ComboAddress, float> speeds;
- {
- auto lock = s_nsSpeeds.lock();
- auto& collection = lock->find_or_enter(qname, d_now);
- float factor = collection.getFactor(d_now);
- for (const auto& val : ret) {
- speeds[val] = collection.d_collection[val].get(factor);
- }
- collection.purge(speeds);
- }
-
- if (ret.size() > 1) {
- shuffle(ret.begin(), ret.end(), pdns::dns_random_engine());
- speedOrderCA so(speeds);
- stable_sort(ret.begin(), ret.end(), so);
- }
-
- if (doLog()) {
- LOG(prefix << qname << ": Nameserver " << qname << " IPs: ");
- bool first = true;
- for (const auto& addr : ret) {
- if (first) {
- first = false;
- }
- else {
- LOG(", ");
- }
- LOG((addr.toString()) << "(" << fmtfloat(speeds[addr] / 1000.0) << "ms)");
- }
- LOG(endl);
- }
-
+ selectNSOnSpeed(qname, prefix, ret);
return ret;
}
-void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain)
+void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain) // NOLINT(readability-function-cognitive-complexity)
{
DNSName subdomain(qname);
bestns.clear();
- bool brokeloop;
+ bool brokeloop = false;
MemRecursorCache::Flags flags = MemRecursorCache::None;
if (d_serveStale) {
flags |= MemRecursorCache::ServeStale;
}
brokeloop = false;
LOG(prefix << qname << ": Checking if we have NS in cache for '" << subdomain << "'" << endl);
- vector<DNSRecord> ns;
+ vector<DNSRecord> nsVector;
*flawedNSSet = false;
- if (g_recCache->get(d_now.tv_sec, subdomain, QType::NS, flags, &ns, d_cacheRemote, d_routingTag) > 0) {
- if (s_maxnsperresolve > 0 && ns.size() > s_maxnsperresolve) {
+ if (bool isAuth = false; g_recCache->get(d_now.tv_sec, subdomain, QType::NS, flags, &nsVector, d_cacheRemote, d_routingTag, nullptr, nullptr, nullptr, nullptr, &isAuth) > 0) {
+ if (s_maxnsperresolve > 0 && nsVector.size() > s_maxnsperresolve) {
vector<DNSRecord> selected;
selected.reserve(s_maxnsperresolve);
- std::sample(ns.cbegin(), ns.cend(), std::back_inserter(selected), s_maxnsperresolve, pdns::dns_random_engine());
- ns = selected;
+ std::sample(nsVector.cbegin(), nsVector.cend(), std::back_inserter(selected), s_maxnsperresolve, pdns::dns_random_engine());
+ nsVector = std::move(selected);
}
- bestns.reserve(ns.size());
+ bestns.reserve(nsVector.size());
- for (auto k = ns.cbegin(); k != ns.cend(); ++k) {
- if (k->d_ttl > (unsigned int)d_now.tv_sec) {
+ vector<DNSName> missing;
+ for (const auto& nsRecord : nsVector) {
+ if (nsRecord.d_ttl > (unsigned int)d_now.tv_sec) {
vector<DNSRecord> aset;
QType nsqt{QType::ADDR};
if (s_doIPv4 && !s_doIPv6) {
nsqt = QType::AAAA;
}
- const DNSRecord& dr = *k;
- auto nrr = getRR<NSRecordContent>(dr);
- if (nrr && (!nrr->getNS().isPartOf(subdomain) || g_recCache->get(d_now.tv_sec, nrr->getNS(), nsqt, flags, doLog() ? &aset : 0, d_cacheRemote, d_routingTag) > 0)) {
- bestns.push_back(dr);
+ auto nrr = getRR<NSRecordContent>(nsRecord);
+ if (nrr && (!nrr->getNS().isPartOf(subdomain) || g_recCache->get(d_now.tv_sec, nrr->getNS(), nsqt, flags, doLog() ? &aset : nullptr, d_cacheRemote, d_routingTag) > 0)) {
+ bestns.push_back(nsRecord);
LOG(prefix << qname << ": NS (with ip, or non-glue) in cache for '" << subdomain << "' -> '" << nrr->getNS() << "'");
LOG(", within bailiwick: " << nrr->getNS().isPartOf(subdomain));
if (!aset.empty()) {
LOG(", not in cache / did not look at cache" << endl);
}
}
- else {
+ else if (nrr != nullptr) {
*flawedNSSet = true;
LOG(prefix << qname << ": NS in cache for '" << subdomain << "', but needs glue (" << nrr->getNS() << ") which we miss or is expired" << endl);
+ missing.emplace_back(nrr->getNS());
+ }
+ }
+ }
+ if (*flawedNSSet && bestns.empty() && isAuth) {
+ // The authoritative (child) NS records did not produce any usable addresses, wipe them, so
+ // these useless records do not prevent parent records to be inserted into the cache
+ LOG(prefix << qname << ": Wiping flawed authoritative NS records for " << subdomain << endl);
+ g_recCache->doWipeCache(subdomain, false, QType::NS);
+ }
+ if (!missing.empty() && missing.size() < nsVector.size()) {
+ // We miss glue, but we have a chance to resolve it, since we do have address(es) for at least one NS
+ for (const auto& name : missing) {
+ if (s_doIPv4 && pushResolveIfNotInNegCache(name, QType::A, d_now)) {
+ LOG(prefix << qname << ": A glue for " << subdomain << " NS " << name << " missing, pushed task to resolve" << endl);
+ }
+ if (s_doIPv6 && pushResolveIfNotInNegCache(name, QType::AAAA, d_now)) {
+ LOG(prefix << qname << ": AAAA glue for " << subdomain << " NS " << name << " missing, pushed task to resolve" << endl);
}
}
}
GetBestNSAnswer answer;
answer.qname = qname;
answer.qtype = qtype.getCode();
- for (const auto& dr : bestns) {
- if (auto nsContent = getRR<NSRecordContent>(dr)) {
- answer.bestns.emplace(dr.d_name, nsContent->getNS());
+ for (const auto& bestNSRecord : bestns) {
+ if (auto nsContent = getRR<NSRecordContent>(bestNSRecord)) {
+ answer.bestns.emplace(bestNSRecord.d_name, nsContent->getNS());
}
}
auto insertionPair = beenthere.insert(std::move(answer));
if (!insertionPair.second) {
brokeloop = true;
- LOG(prefix << qname << ": We have NS in cache for '" << subdomain << "' but part of LOOP (already seen " << answer.qname << ")! Trying less specific NS" << endl);
+ LOG(prefix << qname << ": We have NS in cache for '" << subdomain << "' but part of LOOP (already seen " << insertionPair.first->qname << ")! Trying less specific NS" << endl);
;
- if (doLog())
- for (set<GetBestNSAnswer>::const_iterator j = beenthere.begin(); j != beenthere.end(); ++j) {
+ if (doLog()) {
+ for (auto j = beenthere.begin(); j != beenthere.end(); ++j) {
bool neo = (j == insertionPair.first);
LOG(prefix << qname << ": Beenthere" << (neo ? "*" : "") << ": " << j->qname << "|" << DNSRecordContent::NumberToType(j->qtype) << " (" << (unsigned int)j->bestns.size() << ")" << endl);
}
+ }
bestns.clear();
}
else {
} while (subdomain.chopOff());
}
-SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname) const
+SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname)
{
if (t_sstorage.domainmap->empty()) {
return t_sstorage.domainmap->end();
SyncRes::domainmap_t::const_iterator ret;
do {
ret = t_sstorage.domainmap->find(*qname);
- if (ret != t_sstorage.domainmap->end())
+ if (ret != t_sstorage.domainmap->end()) {
break;
+ }
} while (qname->chopOff());
return ret;
}
{
DNSName authOrForwDomain(qname);
- domainmap_t::const_iterator iter = getBestAuthZone(&authOrForwDomain);
+ auto iter = getBestAuthZone(&authOrForwDomain);
// We have an auth, forwarder of forwarder-recurse
if (iter != t_sstorage.domainmap->end()) {
if (iter->second.isAuth()) {
nsset.insert({DNSName(), {{}, false}});
return authOrForwDomain;
}
- else {
- if (iter->second.shouldRecurse()) {
- // Again, picked up in doResolveAt. An empty DNSName, combined with a
- // non-empty vector of ComboAddresses means 'this is a forwarded domain'
- // This is actually picked up in retrieveAddressesForNS called from doResolveAt.
- nsset.insert({DNSName(), {iter->second.d_servers, true}});
- return authOrForwDomain;
- }
+ if (iter->second.shouldRecurse()) {
+ // Again, picked up in doResolveAt. An empty DNSName, combined with a
+ // non-empty vector of ComboAddresses means 'this is a forwarded domain'
+ // This is actually picked up in retrieveAddressesForNS called from doResolveAt.
+ nsset.insert({DNSName(), {iter->second.d_servers, true}});
+ return authOrForwDomain;
}
}
getBestNSFromCache(qname, qtype, bestns, flawedNSSet, depth, prefix, beenthere);
// Pick up the auth domain
- for (const auto& k : bestns) {
- const auto nsContent = getRR<NSRecordContent>(k);
+ for (const auto& nsRecord : bestns) {
+ const auto nsContent = getRR<NSRecordContent>(nsRecord);
if (nsContent) {
- nsFromCacheDomain = k.d_name;
+ nsFromCacheDomain = nsRecord.d_name;
break;
}
}
nsset.insert({DNSName(), {iter->second.d_servers, false}});
return authOrForwDomain;
}
- else {
- if (doLog()) {
- LOG(prefix << qname << ": Using NS from cache" << endl);
- }
+ if (doLog()) {
+ LOG(prefix << qname << ": Using NS from cache" << endl);
}
}
- for (auto k = bestns.cbegin(); k != bestns.cend(); ++k) {
+ for (const auto& bestn : bestns) {
// The actual resolver code will not even look at the ComboAddress or bool
- const auto nsContent = getRR<NSRecordContent>(*k);
+ const auto nsContent = getRR<NSRecordContent>(bestn);
if (nsContent) {
nsset.insert({nsContent->getNS(), {{}, false}});
}
return nsFromCacheDomain;
}
-void SyncRes::updateValidationStatusInCache(const DNSName& qname, const QType qt, bool aa, vState newState) const
+void SyncRes::updateValidationStatusInCache(const DNSName& qname, const QType qtype, bool aaFlag, vState newState) const
{
- if (qt == QType::ANY || qt == QType::ADDR) {
+ if (qtype == QType::ANY || qtype == QType::ADDR) {
// not doing that
return;
}
if (vStateIsBogus(newState)) {
- g_recCache->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, d_routingTag, aa, newState, s_maxbogusttl + d_now.tv_sec);
+ g_recCache->updateValidationStatus(d_now.tv_sec, qname, qtype, d_cacheRemote, d_routingTag, aaFlag, newState, s_maxbogusttl + d_now.tv_sec);
}
else {
- g_recCache->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, d_routingTag, aa, newState, boost::none);
+ g_recCache->updateValidationStatus(d_now.tv_sec, qname, qtype, d_cacheRemote, d_routingTag, aaFlag, newState, boost::none);
}
}
-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, bool checkForDups) // 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;
- bool wasAuth;
+ bool wasAuth = false;
uint32_t capTTL = std::numeric_limits<uint32_t>::max();
DNSName foundName;
DNSName authZone;
LOG(prefix << qname << ": Found cache " << foundQT.toString() << " hit for '" << foundName << "|" << foundQT.toString() << "' to '" << record.getContent()->getZoneRepresentation() << "', validation state is " << context.state << endl);
- DNSRecord dr = record;
- dr.d_ttl -= d_now.tv_sec;
- dr.d_ttl = std::min(dr.d_ttl, capTTL);
- const uint32_t ttl = dr.d_ttl;
- ret.reserve(ret.size() + 2 + signatures.size() + authorityRecs.size());
- ret.push_back(dr);
+ DNSRecord dnsRecord = record;
+ auto alreadyPresent = false;
- for (const auto& signature : signatures) {
- DNSRecord sigdr;
- sigdr.d_type = QType::RRSIG;
- sigdr.d_name = foundName;
- sigdr.d_ttl = ttl;
- sigdr.setContent(signature);
- sigdr.d_place = DNSResourceRecord::ANSWER;
- sigdr.d_class = QClass::IN;
- ret.push_back(sigdr);
+ if (checkForDups) {
+ // This can happen on the 2nd iteration of the servestale loop, where the first iteration
+ // added a C/DNAME record, but the target resolve failed
+ for (const auto& dnsrec : ret) {
+ if (dnsrec.d_type == foundQT && dnsrec.d_name == record.d_name) {
+ alreadyPresent = true;
+ break;
+ }
+ }
}
+ dnsRecord.d_ttl -= d_now.tv_sec;
+ dnsRecord.d_ttl = std::min(dnsRecord.d_ttl, capTTL);
+ const uint32_t ttl = dnsRecord.d_ttl;
+ if (!alreadyPresent) {
+ ret.reserve(ret.size() + 2 + signatures.size() + authorityRecs.size());
+ ret.push_back(dnsRecord);
+
+ for (const auto& signature : signatures) {
+ DNSRecord sigdr;
+ sigdr.d_type = QType::RRSIG;
+ sigdr.d_name = foundName;
+ sigdr.d_ttl = ttl;
+ sigdr.setContent(signature);
+ sigdr.d_place = DNSResourceRecord::ANSWER;
+ sigdr.d_class = QClass::IN;
+ ret.push_back(sigdr);
+ }
- for (const auto& rec : authorityRecs) {
- DNSRecord authDR(*rec);
- authDR.d_ttl = ttl;
- ret.push_back(authDR);
+ for (const auto& rec : authorityRecs) {
+ DNSRecord authDR(*rec);
+ authDR.d_ttl = ttl;
+ ret.push_back(authDR);
+ }
}
DNSName newTarget;
const auto& dnameSuffix = dnameRR->getTarget();
DNSName targetPrefix = qname.makeRelative(foundName);
try {
- dr.d_type = QType::CNAME;
- dr.d_name = targetPrefix + foundName;
+ dnsRecord.d_type = QType::CNAME;
+ dnsRecord.d_name = targetPrefix + foundName;
newTarget = targetPrefix + dnameSuffix;
- dr.setContent(std::make_shared<CNAMERecordContent>(CNAMERecordContent(newTarget)));
- ret.push_back(dr);
+ dnsRecord.setContent(std::make_shared<CNAMERecordContent>(CNAMERecordContent(newTarget)));
+ ret.push_back(dnsRecord);
}
catch (const std::exception& e) {
// We should probably catch an std::range_error here and set the rcode to YXDOMAIN (RFC 6672, section 2.2)
throw ImmediateServFailException("Unable to perform DNAME substitution(DNAME owner: '" + foundName.toLogString() + "', DNAME target: '" + dnameSuffix.toLogString() + "', substituted name: '" + targetPrefix.toLogString() + "." + dnameSuffix.toLogString() + "' : " + e.what());
}
- LOG(prefix << qname << ": Synthesized " << dr.d_name << "|CNAME " << newTarget << endl);
+ LOG(prefix << qname << ": Synthesized " << dnsRecord.d_name << "|CNAME " << newTarget << endl);
}
if (qtype == QType::CNAME) { // perhaps they really wanted a CNAME!
if (qname == newTarget) {
string msg = "Got a CNAME referral (from cache) to self";
LOG(prefix << qname << ": " << msg << endl);
- throw ImmediateServFailException(msg);
+ throw ImmediateServFailException(std::move(msg));
}
if (newTarget.isPartOf(qname)) {
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);
+ throw ImmediateServFailException(std::move(msg));
+ }
+ if (numCNAMEs > s_max_CNAMES_followed) {
+ string msg = "max number of CNAMEs exceeded";
+ LOG(prefix << qname << ": Status=" << msg << endl);
+ throw ImmediateServFailException(std::move(msg));
}
set<GetBestNSAnswer> beenthere;
{
vector<DNSRecord> records;
vector<shared_ptr<const RRSIGRecordContent>> signatures;
+ time_t d_ttl_time{0};
uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
};
struct CacheKey
}
}
-static bool negativeCacheEntryHasSOA(const NegCache::NegCacheEntry& ne)
+static bool negativeCacheEntryHasSOA(const NegCache::NegCacheEntry& negEntry)
{
- return !ne.authoritySOA.records.empty();
+ return !negEntry.authoritySOA.records.empty();
}
static void reapRecordsForValidation(std::map<QType, CacheEntry>& entries, const vector<DNSRecord>& records)
}
}
-void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, const QType qtype, const int res, vState& state, unsigned int depth, const string& prefix)
+void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& negEntry, const DNSName& qname, const QType qtype, const int res, vState& state, unsigned int depth, const string& prefix)
{
tcache_t tcache;
- reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.records);
- reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.signatures);
- reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.records);
- reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.signatures);
+ reapRecordsFromNegCacheEntryForValidation(tcache, negEntry.authoritySOA.records);
+ reapRecordsFromNegCacheEntryForValidation(tcache, negEntry.authoritySOA.signatures);
+ reapRecordsFromNegCacheEntryForValidation(tcache, negEntry.DNSSECRecords.records);
+ reapRecordsFromNegCacheEntryForValidation(tcache, negEntry.DNSSECRecords.signatures);
for (const auto& entry : tcache) {
// this happens when we did store signatures, but passed on the records themselves
}
if (state == vState::Secure) {
- vState neValidationState = ne.d_validationState;
+ vState neValidationState = negEntry.d_validationState;
dState expectedState = res == RCode::NXDomain ? dState::NXDOMAIN : dState::NXQTYPE;
- dState denialState = getDenialValidationState(ne, expectedState, false, prefix);
- updateDenialValidationState(qname, neValidationState, ne.d_name, state, denialState, expectedState, qtype == QType::DS, depth, prefix);
+ dState denialState = getDenialValidationState(negEntry, expectedState, false, prefix);
+ updateDenialValidationState(qname, neValidationState, negEntry.d_name, state, denialState, expectedState, qtype == QType::DS, depth, prefix);
}
if (state != vState::Indeterminate) {
/* validation succeeded, let's update the cache entry so we don't have to validate again */
if (vStateIsBogus(state)) {
capTTD = d_now.tv_sec + s_maxbogusttl;
}
- g_negCache->updateValidationStatus(ne.d_name, ne.d_qtype, state, capTTD);
+ g_negCache->updateValidationStatus(negEntry.d_name, negEntry.d_qtype, state, capTTD);
}
}
-bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context)
+bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context) // NOLINT(readability-function-cognitive-complexity)
{
bool giveNegative = false;
QType sqt(qtype);
uint32_t sttl = 0;
// cout<<"Lookup for '"<<qname<<"|"<<qtype.toString()<<"' -> "<<getLastLabel(qname)<<endl;
- vState cachedState;
- NegCache::NegCacheEntry ne;
+ vState cachedState{};
+ NegCache::NegCacheEntry negEntry;
- if (s_rootNXTrust && g_negCache->getRootNXTrust(qname, d_now, ne, d_serveStale, d_refresh) && ne.d_auth.isRoot() && !(wasForwardedOrAuthZone && !authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
- sttl = ne.d_ttd - d_now.tv_sec;
- LOG(prefix << qname << ": Entire name '" << qname << "', is negatively cached via '" << ne.d_auth << "' & '" << ne.d_name << "' for another " << sttl << " seconds" << endl);
+ if (s_rootNXTrust && g_negCache->getRootNXTrust(qname, d_now, negEntry, d_serveStale, d_refresh) && negEntry.d_auth.isRoot() && (!wasForwardedOrAuthZone || authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
+ sttl = negEntry.d_ttd - d_now.tv_sec;
+ LOG(prefix << qname << ": Entire name '" << qname << "', is negatively cached via '" << negEntry.d_auth << "' & '" << negEntry.d_name << "' for another " << sttl << " seconds" << endl);
res = RCode::NXDomain;
giveNegative = true;
- cachedState = ne.d_validationState;
+ cachedState = negEntry.d_validationState;
if (s_addExtendedResolutionDNSErrors) {
context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result synthesized by root-nx-trust"};
}
}
- else if (g_negCache->get(qname, qtype, d_now, ne, false, d_serveStale, d_refresh)) {
+ else if (g_negCache->get(qname, qtype, d_now, negEntry, false, d_serveStale, d_refresh)) {
/* If we are looking for a DS, discard NXD if auth == qname
and ask for a specific denial instead */
- if (qtype != QType::DS || ne.d_qtype.getCode() || ne.d_auth != qname || g_negCache->get(qname, qtype, d_now, ne, true, d_serveStale, d_refresh)) {
+ if (qtype != QType::DS || negEntry.d_qtype.getCode() != 0 || negEntry.d_auth != qname || g_negCache->get(qname, qtype, d_now, negEntry, true, d_serveStale, d_refresh)) {
/* Careful! If the client is asking for a DS that does not exist, we need to provide the SOA along with the NSEC(3) proof
and we might not have it if we picked up the proof from a delegation, in which case we need to keep on to do the actual DS
query. */
- if (qtype == QType::DS && ne.d_qtype.getCode() && !d_externalDSQuery.empty() && qname == d_externalDSQuery && !negativeCacheEntryHasSOA(ne)) {
+ if (qtype == QType::DS && negEntry.d_qtype.getCode() != 0 && !d_externalDSQuery.empty() && qname == d_externalDSQuery && !negativeCacheEntryHasSOA(negEntry)) {
giveNegative = false;
}
else {
res = RCode::NXDomain;
- sttl = ne.d_ttd - d_now.tv_sec;
+ sttl = negEntry.d_ttd - d_now.tv_sec;
giveNegative = true;
- cachedState = ne.d_validationState;
- if (ne.d_qtype.getCode()) {
- LOG(prefix << qname << "|" << qtype << ": Is negatively cached via '" << ne.d_auth << "' for another " << sttl << " seconds" << endl);
+ cachedState = negEntry.d_validationState;
+ if (negEntry.d_qtype.getCode() != 0) {
+ LOG(prefix << qname << "|" << qtype << ": Is negatively cached via '" << negEntry.d_auth << "' for another " << sttl << " seconds" << endl);
res = RCode::NoError;
if (s_addExtendedResolutionDNSErrors) {
context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result from negative cache"};
}
}
else {
- LOG(prefix << qname << ": Entire name '" << qname << "' is negatively cached via '" << ne.d_auth << "' for another " << sttl << " seconds" << endl);
+ LOG(prefix << qname << ": Entire name '" << qname << "' is negatively cached via '" << negEntry.d_auth << "' for another " << sttl << " seconds" << endl);
if (s_addExtendedResolutionDNSErrors) {
context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result from negative cache for entire name"};
}
negCacheName.prependRawLabel(labels.back());
labels.pop_back();
while (!labels.empty()) {
- if (g_negCache->get(negCacheName, QType::ENT, d_now, ne, true, d_serveStale, d_refresh)) {
- if (ne.d_validationState == vState::Indeterminate && validationEnabled()) {
+ if (g_negCache->get(negCacheName, QType::ENT, d_now, negEntry, true, d_serveStale, d_refresh)) {
+ if (negEntry.d_validationState == vState::Indeterminate && validationEnabled()) {
// LOG(prefix << negCacheName << " negatively cached and vState::Indeterminate, trying to validate NXDOMAIN" << endl);
// ...
// And get the updated ne struct
// t_sstorage.negcache.get(negCacheName, QType(0), d_now, ne, true);
}
- if ((s_hardenNXD == HardenNXD::Yes && !vStateIsBogus(ne.d_validationState)) || ne.d_validationState == vState::Secure) {
+ if ((s_hardenNXD == HardenNXD::Yes && !vStateIsBogus(negEntry.d_validationState)) || negEntry.d_validationState == vState::Secure) {
res = RCode::NXDomain;
- sttl = ne.d_ttd - d_now.tv_sec;
+ sttl = negEntry.d_ttd - d_now.tv_sec;
giveNegative = true;
- cachedState = ne.d_validationState;
- LOG(prefix << qname << ": Name '" << negCacheName << "' and below, is negatively cached via '" << ne.d_auth << "' for another " << sttl << " seconds" << endl);
+ cachedState = negEntry.d_validationState;
+ LOG(prefix << qname << ": Name '" << negCacheName << "' and below, is negatively cached via '" << negEntry.d_auth << "' for another " << sttl << " seconds" << endl);
if (s_addExtendedResolutionDNSErrors) {
context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result synthesized by nothing-below-nxdomain (RFC8020)"};
}
if (!wasAuthZone && shouldValidate() && context.state == vState::Indeterminate) {
LOG(prefix << qname << ": Got vState::Indeterminate state for records retrieved from the negative cache, validating.." << endl);
- computeNegCacheValidationStatus(ne, qname, qtype, res, context.state, depth, prefix);
+ computeNegCacheValidationStatus(negEntry, qname, qtype, res, context.state, depth, prefix);
if (context.state != cachedState && vStateIsBogus(context.state)) {
sttl = std::min(sttl, s_maxbogusttl);
}
// Transplant SOA to the returned packet
- addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
+ addTTLModifiedRecords(negEntry.authoritySOA.records, sttl, ret);
if (d_doDNSSEC) {
- addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
- addTTLModifiedRecords(ne.DNSSECRecords.records, sttl, ret);
- addTTLModifiedRecords(ne.DNSSECRecords.signatures, sttl, ret);
+ addTTLModifiedRecords(negEntry.authoritySOA.signatures, sttl, ret);
+ addTTLModifiedRecords(negEntry.DNSSECRecords.records, sttl, ret);
+ addTTLModifiedRecords(negEntry.DNSSECRecords.signatures, sttl, ret);
}
LOG(prefix << qname << ": Updating validation state with negative cache content for " << qname << " to " << context.state << endl);
}
vector<DNSRecord> cset;
- bool found = false, expired = false;
+ bool found = false;
+ bool expired = false;
vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
vector<std::shared_ptr<DNSRecord>> authorityRecs;
uint32_t ttl = 0;
uint32_t capTTL = std::numeric_limits<uint32_t>::max();
- bool wasCachedAuth;
+ bool wasCachedAuth{};
MemRecursorCache::Flags flags = MemRecursorCache::None;
if (!wasForwardRecurse && d_requireAuthData) {
flags |= MemRecursorCache::RequireAuth;
reapSignaturesForValidation(types, signatures);
for (const auto& type : types) {
- vState cachedRecordState;
+ vState cachedRecordState{};
if (type.first == QType::DNSKEY && sqname == getSigner(type.second.signatures)) {
cachedRecordState = validateDNSKeys(sqname, type.second.records, type.second.signatures, depth, prefix);
}
}
if (j->d_ttl > (unsigned int)d_now.tv_sec) {
- DNSRecord dr = *j;
- dr.d_ttl -= d_now.tv_sec;
- dr.d_ttl = std::min(dr.d_ttl, capTTL);
- ttl = dr.d_ttl;
- ret.push_back(dr);
- LOG("[ttl=" << dr.d_ttl << "] ");
+ DNSRecord dnsRecord = *j;
+ dnsRecord.d_ttl -= d_now.tv_sec;
+ dnsRecord.d_ttl = std::min(dnsRecord.d_ttl, capTTL);
+ ttl = dnsRecord.d_ttl;
+ ret.push_back(dnsRecord);
+ LOG("[ttl=" << dnsRecord.d_ttl << "] ");
found = true;
}
else {
ret.reserve(ret.size() + signatures.size() + authorityRecs.size());
for (const auto& signature : signatures) {
- DNSRecord dr;
- dr.d_type = QType::RRSIG;
- dr.d_name = sqname;
- dr.d_ttl = ttl;
- dr.setContent(signature);
- dr.d_place = DNSResourceRecord::ANSWER;
- dr.d_class = QClass::IN;
- ret.push_back(dr);
+ DNSRecord dnsRecord;
+ dnsRecord.d_type = QType::RRSIG;
+ dnsRecord.d_name = sqname;
+ dnsRecord.d_ttl = ttl;
+ dnsRecord.setContent(signature);
+ dnsRecord.d_place = DNSResourceRecord::ANSWER;
+ dnsRecord.d_class = QClass::IN;
+ ret.push_back(dnsRecord);
}
for (const auto& rec : authorityRecs) {
- DNSRecord dr(*rec);
- dr.d_ttl = ttl;
- ret.push_back(dr);
+ DNSRecord dnsRecord(*rec);
+ dnsRecord.d_ttl = ttl;
+ ret.push_back(dnsRecord);
}
LOG(endl);
if (found && !expired) {
- if (!giveNegative)
+ if (!giveNegative) {
res = 0;
+ }
LOG(prefix << qname << ": Updating validation state with cache content for " << qname << " to " << cachedState << endl);
context.state = cachedState;
return true;
}
- else
- LOG(prefix << qname << ": Cache had only stale entries" << endl);
+ LOG(prefix << qname << ": Cache had only stale entries" << endl);
}
/* let's check if we have a NSEC covering that record */
if (g_aggressiveNSECCache && !wasForwardedOrAuthZone) {
- if (g_aggressiveNSECCache->getDenial(d_now.tv_sec, qname, qtype, ret, res, d_cacheRemote, d_routingTag, d_doDNSSEC, LogObject(prefix))) {
+ if (g_aggressiveNSECCache->getDenial(d_now.tv_sec, qname, qtype, ret, res, d_cacheRemote, d_routingTag, d_doDNSSEC, d_validationContext, LogObject(prefix))) {
context.state = vState::Secure;
if (s_addExtendedResolutionDNSErrors) {
context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result synthesized from aggressive NSEC cache (RFC8198)"};
return false;
}
-bool SyncRes::moreSpecificThan(const DNSName& a, const DNSName& b) const
+bool SyncRes::moreSpecificThan(const DNSName& lhs, const DNSName& rhs)
{
- return (a.isPartOf(b) && a.countLabels() > b.countLabels());
+ return (lhs.isPartOf(rhs) && lhs.countLabels() > rhs.countLabels());
}
struct speedOrder
{
- bool operator()(const std::pair<DNSName, float>& a, const std::pair<DNSName, float>& b) const
+ bool operator()(const std::pair<DNSName, float>& lhs, const std::pair<DNSName, float>& rhs) const
{
- return a.second < b.second;
+ return lhs.second < rhs.second;
}
};
for (const auto& tns : tnameservers) {
float speed = s_nsSpeeds.lock()->fastest(tns.first, d_now);
rnameservers.emplace_back(tns.first, speed);
- if (tns.first.empty()) // this was an authoritative OOB zone, don't pollute the nsSpeeds with that
+ if (tns.first.empty()) { // this was an authoritative OOB zone, don't pollute the nsSpeeds with that
return rnameservers;
+ }
}
shuffle(rnameservers.begin(), rnameservers.end(), pdns::dns_random_engine());
- speedOrder so;
- stable_sort(rnameservers.begin(), rnameservers.end(), so);
+ speedOrder speedCompare;
+ stable_sort(rnameservers.begin(), rnameservers.end(), speedCompare);
if (doLog()) {
LOG(prefix << qname << ": Nameservers: ");
for (auto i = rnameservers.begin(); i != rnameservers.end(); ++i) {
if (i != rnameservers.begin()) {
LOG(", ");
- if (!((i - rnameservers.begin()) % 3)) {
+ if (((i - rnameservers.begin()) % 3) == 0) {
LOG(endl
<< prefix << " ");
}
speeds[val] = speed;
}
shuffle(nameservers.begin(), nameservers.end(), pdns::dns_random_engine());
- speedOrderCA so(speeds);
- stable_sort(nameservers.begin(), nameservers.end(), so);
+ speedOrderCA speedCompare(speeds);
+ stable_sort(nameservers.begin(), nameservers.end(), speedCompare);
if (doLog()) {
LOG(prefix << qname << ": Nameservers: ");
- for (vector<ComboAddress>::const_iterator i = nameservers.cbegin(); i != nameservers.cend(); ++i) {
+ for (auto i = nameservers.cbegin(); i != nameservers.cend(); ++i) {
if (i != nameservers.cbegin()) {
LOG(", ");
- if (!((i - nameservers.cbegin()) % 3)) {
+ if (((i - nameservers.cbegin()) % 3) == 0) {
LOG(endl
<< prefix << " ");
}
{
uint32_t res = 0;
if (now < rrsig->d_sigexpire) {
+ // coverity[store_truncates_time_t]
res = static_cast<uint32_t>(rrsig->d_sigexpire) - now;
}
return res;
* \param records The records to parse for the authority SOA and NSEC(3) records
* \param ne The NegCacheEntry to be filled out (will not be cleared, only appended to
*/
-static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne, const time_t now, uint32_t* lowestTTL)
+static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& negEntry, const time_t now, uint32_t* lowestTTL)
{
for (const auto& rec : records) {
if (rec.d_place != DNSResourceRecord::AUTHORITY) {
auto rrsig = getRR<RRSIGRecordContent>(rec);
if (rrsig) {
if (rrsig->d_type == QType::SOA) {
- ne.authoritySOA.signatures.push_back(rec);
- if (lowestTTL && isRRSIGNotExpired(now, *rrsig)) {
+ negEntry.authoritySOA.signatures.push_back(rec);
+ if (lowestTTL != nullptr && isRRSIGNotExpired(now, *rrsig)) {
*lowestTTL = min(*lowestTTL, rec.d_ttl);
*lowestTTL = min(*lowestTTL, getRRSIGTTL(now, rrsig));
}
}
- if (nsecTypes.count(rrsig->d_type)) {
- ne.DNSSECRecords.signatures.push_back(rec);
- if (lowestTTL && isRRSIGNotExpired(now, *rrsig)) {
+ if (nsecTypes.count(rrsig->d_type) != 0) {
+ negEntry.DNSSECRecords.signatures.push_back(rec);
+ if (lowestTTL != nullptr && isRRSIGNotExpired(now, *rrsig)) {
*lowestTTL = min(*lowestTTL, rec.d_ttl);
*lowestTTL = min(*lowestTTL, getRRSIGTTL(now, rrsig));
}
continue;
}
if (rec.d_type == QType::SOA) {
- ne.authoritySOA.records.push_back(rec);
- if (lowestTTL) {
+ negEntry.authoritySOA.records.push_back(rec);
+ if (lowestTTL != nullptr) {
*lowestTTL = min(*lowestTTL, rec.d_ttl);
}
continue;
}
- if (nsecTypes.count(rec.d_type)) {
- ne.DNSSECRecords.records.push_back(rec);
- if (lowestTTL) {
+ if (nsecTypes.count(rec.d_type) != 0) {
+ negEntry.DNSSECRecords.records.push_back(rec);
+ if (lowestTTL != nullptr) {
*lowestTTL = min(*lowestTTL, rec.d_ttl);
}
continue;
}
}
-static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& ne)
+static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& negEntry)
{
cspmap_t cspmap;
- for (const auto& rec : ne.DNSSECRecords.signatures) {
+ for (const auto& rec : negEntry.DNSSECRecords.signatures) {
if (rec.d_type == QType::RRSIG) {
auto rrc = getRR<RRSIGRecordContent>(rec);
if (rrc) {
}
}
}
- for (const auto& rec : ne.DNSSECRecords.records) {
+ for (const auto& rec : negEntry.DNSSECRecords.records) {
cspmap[{rec.d_name, rec.d_type}].records.insert(rec.getContent());
}
return cspmap;
// Adds the RRSIG for the SOA and the NSEC(3) + RRSIGs to ret
static void addNXNSECS(vector<DNSRecord>& ret, const vector<DNSRecord>& records)
{
- NegCache::NegCacheEntry ne;
- harvestNXRecords(records, ne, 0, nullptr);
- ret.insert(ret.end(), ne.authoritySOA.signatures.begin(), ne.authoritySOA.signatures.end());
- ret.insert(ret.end(), ne.DNSSECRecords.records.begin(), ne.DNSSECRecords.records.end());
- ret.insert(ret.end(), ne.DNSSECRecords.signatures.begin(), ne.DNSSECRecords.signatures.end());
+ NegCache::NegCacheEntry negEntry;
+ harvestNXRecords(records, negEntry, 0, nullptr);
+ ret.insert(ret.end(), negEntry.authoritySOA.signatures.begin(), negEntry.authoritySOA.signatures.end());
+ ret.insert(ret.end(), negEntry.DNSSECRecords.records.begin(), negEntry.DNSSECRecords.records.end());
+ ret.insert(ret.end(), negEntry.DNSSECRecords.signatures.begin(), negEntry.DNSSECRecords.signatures.end());
}
static bool rpzHitShouldReplaceContent(const DNSName& qname, const QType qtype, const std::vector<DNSRecord>& records)
return true;
}
- for (const auto& record : records) {
+ for (const auto& record : records) { // NOLINT(readability-use-anyofallof): don't agree
if (record.d_type == QType::CNAME) {
if (auto content = getRR<CNAMERecordContent>(record)) {
if (qname == content->getTarget()) {
case DNSFilterEngine::PolicyKind::NXDOMAIN:
ret.clear();
+ d_appliedPolicy.addSOAtoRPZResult(ret);
rcode = RCode::NXDomain;
done = true;
return;
case DNSFilterEngine::PolicyKind::NODATA:
ret.clear();
+ d_appliedPolicy.addSOAtoRPZResult(ret);
rcode = RCode::NoError;
done = true;
return;
if (!d_queryReceivedOverTCP) {
ret.clear();
rcode = RCode::NoError;
+ // Exception handling code in pdns_recursor clears ret as well, so no use to
+ // fill it here.
throw SendTruncatedAnswerException();
}
return;
rcode = RCode::NoError;
done = true;
auto spoofed = d_appliedPolicy.getCustomRecords(qname, qtype.getCode());
- for (auto& dr : spoofed) {
- removeConflictingRecord(ret, dr.d_name, dr.d_type);
+ for (auto& dnsRecord : spoofed) {
+ removeConflictingRecord(ret, dnsRecord.d_name, dnsRecord.d_type);
}
- for (auto& dr : spoofed) {
- ret.push_back(dr);
+ for (auto& dnsRecord : spoofed) {
+ ret.push_back(dnsRecord);
- if (dr.d_name == qname && dr.d_type == QType::CNAME && qtype != QType::CNAME) {
- if (auto content = getRR<CNAMERecordContent>(dr)) {
+ if (dnsRecord.d_name == qname && dnsRecord.d_type == QType::CNAME && qtype != QType::CNAME) {
+ if (auto content = getRR<CNAMERecordContent>(dnsRecord)) {
vState newTargetState = vState::Indeterminate;
handleNewTarget(prefix, qname, content->getTarget(), qtype.getCode(), ret, rcode, depth, {}, newTargetState);
}
}
}
+ d_appliedPolicy.addSOAtoRPZResult(ret);
}
}
}
process any further RPZ rules. Except that we need to process rules of higher priority..
*/
if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
- for (auto const& ns : nameservers) {
- bool match = dfe.getProcessingPolicy(ns.first, d_discardedPolicies, d_appliedPolicy);
+ for (auto const& nameserver : nameservers) {
+ bool match = dfe.getProcessingPolicy(nameserver.first, d_discardedPolicies, d_appliedPolicy);
if (match) {
mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
- LOG(", however nameserver " << ns.first << " was blocked by RPZ policy '" << d_appliedPolicy.getName() << "'" << endl);
+ LOG(", however nameserver " << nameserver.first << " was blocked by RPZ policy '" << d_appliedPolicy.getName() << "'" << endl);
return true;
}
}
// Traverse all IP addresses for this NS to see if they have an RPN NSIP policy
- for (auto const& address : ns.second.first) {
+ for (auto const& address : nameserver.second.first) {
match = dfe.getProcessingPolicy(address, d_discardedPolicies, d_appliedPolicy);
if (match) {
mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
- LOG(", however nameserver " << ns.first << " IP address " << address.toString() << " was blocked by RPZ policy '" << d_appliedPolicy.getName() << "'" << endl);
+ LOG(", however nameserver " << nameserver.first << " IP address " << address.toString() << " was blocked by RPZ policy '" << d_appliedPolicy.getName() << "'" << endl);
return true;
}
}
return false;
}
+static bool shouldNotThrottle(const DNSName* name, const ComboAddress* address)
+{
+ if (name != nullptr) {
+ auto dontThrottleNames = g_dontThrottleNames.getLocal();
+ if (dontThrottleNames->check(*name)) {
+ return true;
+ }
+ }
+ if (address != nullptr) {
+ auto dontThrottleNetmasks = g_dontThrottleNetmasks.getLocal();
+ if (dontThrottleNetmasks->match(*address)) {
+ return true;
+ }
+ }
+ return false;
+}
+
vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, std::vector<std::pair<DNSName, float>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& /* flawedNSSet */, bool cacheOnly, unsigned int& nretrieveAddressesForNS)
{
vector<ComboAddress> result;
// Other exceptions should likely not throttle...
catch (const ImmediateServFailException& ex) {
if (s_nonresolvingnsmaxfails > 0 && d_outqueries > oldOutQueries) {
- auto dontThrottleNames = g_dontThrottleNames.getLocal();
- if (!dontThrottleNames->check(tns->first)) {
+ if (!shouldNotThrottle(&tns->first, nullptr)) {
s_nonresolving.lock()->incr(tns->first, d_now);
}
}
}
if (s_nonresolvingnsmaxfails > 0 && d_outqueries > oldOutQueries) {
if (result.empty()) {
- auto dontThrottleNames = g_dontThrottleNames.getLocal();
- if (!dontThrottleNames->check(tns->first)) {
+ if (!shouldNotThrottle(&tns->first, nullptr)) {
s_nonresolving.lock()->incr(tns->first, d_now);
}
}
d_throttledqueries++;
return true;
}
- else if (isThrottled(d_now.tv_sec, remoteIP, qname, qtype)) {
+ if (isThrottled(d_now.tv_sec, remoteIP, qname, qtype)) {
LOG(prefix << qname << ": Query throttled " << remoteIP.toString() << ", " << qname << "; " << qtype << endl);
t_Counters.at(rec::Counter::throttledqueries)++;
d_throttledqueries++;
return true;
}
- else if (!pierceDontQuery && s_dontQuery && s_dontQuery->match(&remoteIP)) {
+ if (!pierceDontQuery && s_dontQuery && s_dontQuery->match(&remoteIP)) {
// We could have retrieved an NS from the cache in a forwarding domain
// Even in the case of !pierceDontQuery we still want to allow that NS
DNSName forwardCandidate(qname);
- auto it = getBestAuthZone(&forwardCandidate);
- if (it == t_sstorage.domainmap->end()) {
+ auto iter = getBestAuthZone(&forwardCandidate);
+ if (iter == t_sstorage.domainmap->end()) {
LOG(prefix << qname << ": Not sending query to " << remoteIP.toString() << ", blocked by 'dont-query' setting" << endl);
t_Counters.at(rec::Counter::dontqueries)++;
return true;
}
- else {
- // The name (from the cache) is forwarded, but is it forwarded to an IP in known forwarders?
- const auto& ips = it->second.d_servers;
- if (std::find(ips.cbegin(), ips.cend(), remoteIP) == ips.cend()) {
- LOG(prefix << qname << ": Not sending query to " << remoteIP.toString() << ", blocked by 'dont-query' setting" << endl);
- t_Counters.at(rec::Counter::dontqueries)++;
- return true;
- }
- else {
- LOG(prefix << qname << ": Sending query to " << remoteIP.toString() << ", blocked by 'dont-query' but a forwarding/auth case" << endl);
- }
+ // The name (from the cache) is forwarded, but is it forwarded to an IP in known forwarders?
+ const auto& ips = iter->second.d_servers;
+ if (std::find(ips.cbegin(), ips.cend(), remoteIP) == ips.cend()) {
+ LOG(prefix << qname << ": Not sending query to " << remoteIP.toString() << ", blocked by 'dont-query' setting" << endl);
+ t_Counters.at(rec::Counter::dontqueries)++;
+ return true;
}
+ LOG(prefix << qname << ": Sending query to " << remoteIP.toString() << ", blocked by 'dont-query' but a forwarding/auth case" << endl);
}
return false;
}
-bool SyncRes::validationEnabled() const
+bool SyncRes::validationEnabled()
{
return g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate;
}
LOG(", validation state is now " << state << endl);
}
-vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds, const string& prefix)
+vState SyncRes::getTA(const DNSName& zone, dsmap_t& dsMap, const string& prefix)
{
auto luaLocal = g_luaconfs.getLocal();
return vState::NTA;
}
- if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
+ if (getTrustAnchor(luaLocal->dsAnchors, zone, dsMap)) {
if (!zone.isRoot()) {
LOG(prefix << zone << ": Got TA" << endl);
}
{
size_t count = 0;
- for (const auto& ds : dsmap) {
- if (isSupportedDS(ds, LogObject(prefix))) {
+ for (const auto& dsRecordContent : dsmap) {
+ if (isSupportedDS(dsRecordContent, LogObject(prefix))) {
count++;
}
}
{
DNSName zone(from);
do {
- dsmap_t ds;
- vState result = getTA(zone, ds, prefix);
+ dsmap_t dsMap;
+ vState result = getTA(zone, dsMap, prefix);
if (result != vState::Indeterminate) {
if (result == vState::TA) {
- if (countSupportedDS(ds, prefix) == 0) {
- ds.clear();
+ if (countSupportedDS(dsMap, prefix) == 0) {
+ dsMap.clear();
result = vState::Insecure;
}
else {
} while (zone.chopOff());
}
-vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsigned int depth, const string& prefix, bool bogusOnNXD, bool* foundCut)
+vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& dsMap, bool onlyTA, unsigned int depth, const string& prefix, bool bogusOnNXD, bool* foundCut)
{
- vState result = getTA(zone, ds, prefix);
+ vState result = getTA(zone, dsMap, prefix);
- if (result != vState::Indeterminate || taOnly) {
- if (foundCut) {
+ if (result != vState::Indeterminate || onlyTA) {
+ if (foundCut != nullptr) {
*foundCut = (result != vState::Indeterminate);
}
if (result == vState::TA) {
- if (countSupportedDS(ds, prefix) == 0) {
- ds.clear();
+ if (countSupportedDS(dsMap, prefix) == 0) {
+ dsMap.clear();
result = vState::Insecure;
}
else {
throw ImmediateServFailException("Server Failure while retrieving DS records for " + zone.toLogString());
}
- if (rcode == RCode::NoError || (rcode == RCode::NXDomain && !bogusOnNXD)) {
- uint8_t bestDigestType = 0;
+ if (rcode != RCode::NoError && (rcode != RCode::NXDomain || bogusOnNXD)) {
+ LOG(prefix << zone << ": Returning Bogus state from " << static_cast<const char*>(__func__) << "(" << zone << ")" << endl);
+ return vState::BogusUnableToGetDSs;
+ }
+
+ uint8_t bestDigestType = 0;
- bool gotCNAME = false;
- for (const auto& record : dsrecords) {
- if (record.d_type == QType::DS) {
- const auto dscontent = getRR<DSRecordContent>(record);
- if (dscontent && isSupportedDS(*dscontent, LogObject(prefix))) {
- // Make GOST a lower prio than SHA256
- if (dscontent->d_digesttype == DNSSECKeeper::DIGEST_GOST && bestDigestType == DNSSECKeeper::DIGEST_SHA256) {
- continue;
- }
- if (dscontent->d_digesttype > bestDigestType || (bestDigestType == DNSSECKeeper::DIGEST_GOST && dscontent->d_digesttype == DNSSECKeeper::DIGEST_SHA256)) {
- bestDigestType = dscontent->d_digesttype;
- }
- ds.insert(*dscontent);
+ bool gotCNAME = false;
+ for (const auto& record : dsrecords) {
+ if (record.d_type == QType::DS) {
+ const auto dscontent = getRR<DSRecordContent>(record);
+ if (dscontent && isSupportedDS(*dscontent, LogObject(prefix))) {
+ // Make GOST a lower prio than SHA256
+ if (dscontent->d_digesttype == DNSSECKeeper::DIGEST_GOST && bestDigestType == DNSSECKeeper::DIGEST_SHA256) {
+ continue;
}
- }
- else if (record.d_type == QType::CNAME && record.d_name == zone) {
- gotCNAME = true;
+ if (dscontent->d_digesttype > bestDigestType || (bestDigestType == DNSSECKeeper::DIGEST_GOST && dscontent->d_digesttype == DNSSECKeeper::DIGEST_SHA256)) {
+ bestDigestType = dscontent->d_digesttype;
+ }
+ dsMap.insert(*dscontent);
}
}
-
- /* RFC 4509 section 3: "Validator implementations SHOULD ignore DS RRs containing SHA-1
- * digests if DS RRs with SHA-256 digests are present in the DS RRset."
- * We interpret that as: do not use SHA-1 if SHA-256 or SHA-384 is available
- */
- for (auto dsrec = ds.begin(); dsrec != ds.end();) {
- if (dsrec->d_digesttype == DNSSECKeeper::DIGEST_SHA1 && dsrec->d_digesttype != bestDigestType) {
- dsrec = ds.erase(dsrec);
- }
- else {
- ++dsrec;
- }
+ else if (record.d_type == QType::CNAME && record.d_name == zone) {
+ gotCNAME = true;
}
+ }
- if (rcode == RCode::NoError) {
- if (ds.empty()) {
- /* we have no DS, it's either:
- - a delegation to a non-DNSSEC signed zone
- - no delegation, we stay in the same zone
- */
- if (gotCNAME || denialProvesNoDelegation(zone, dsrecords)) {
- /* we are still inside the same zone */
+ /* RFC 4509 section 3: "Validator implementations SHOULD ignore DS RRs containing SHA-1
+ * digests if DS RRs with SHA-256 digests are present in the DS RRset."
+ * We interpret that as: do not use SHA-1 if SHA-256 or SHA-384 is available
+ */
+ for (auto dsrec = dsMap.begin(); dsrec != dsMap.end();) {
+ if (dsrec->d_digesttype == DNSSECKeeper::DIGEST_SHA1 && dsrec->d_digesttype != bestDigestType) {
+ dsrec = dsMap.erase(dsrec);
+ }
+ else {
+ ++dsrec;
+ }
+ }
- if (foundCut) {
- *foundCut = false;
- }
- return context.state;
- }
+ if (rcode == RCode::NoError) {
+ if (dsMap.empty()) {
+ /* we have no DS, it's either:
+ - a delegation to a non-DNSSEC signed zone
+ - no delegation, we stay in the same zone
+ */
+ if (gotCNAME || denialProvesNoDelegation(zone, dsrecords, d_validationContext)) {
+ /* we are still inside the same zone */
- d_cutStates[zone] = context.state == vState::Secure ? vState::Insecure : context.state;
- /* delegation with no DS, might be Secure -> Insecure */
- if (foundCut) {
- *foundCut = true;
+ if (foundCut != nullptr) {
+ *foundCut = false;
}
-
- /* a delegation with no DS is either:
- - a signed zone (Secure) to an unsigned one (Insecure)
- - an unsigned zone to another unsigned one (Insecure stays Insecure, Bogus stays Bogus)
- */
- return context.state == vState::Secure ? vState::Insecure : context.state;
+ return context.state;
}
- else {
- /* we have a DS */
- d_cutStates[zone] = context.state;
- if (foundCut) {
- *foundCut = true;
- }
+
+ d_cutStates[zone] = context.state == vState::Secure ? vState::Insecure : context.state;
+ /* delegation with no DS, might be Secure -> Insecure */
+ if (foundCut != nullptr) {
+ *foundCut = true;
}
- }
- return context.state;
+ /* a delegation with no DS is either:
+ - a signed zone (Secure) to an unsigned one (Insecure)
+ - an unsigned zone to another unsigned one (Insecure stays Insecure, Bogus stays Bogus)
+ */
+ return context.state == vState::Secure ? vState::Insecure : context.state;
+ }
+ /* we have a DS */
+ d_cutStates[zone] = context.state;
+ if (foundCut != nullptr) {
+ *foundCut = true;
+ }
}
- LOG(prefix << zone << ": Returning Bogus state from " << __func__ << "(" << zone << ")" << endl);
- return vState::BogusUnableToGetDSs;
+ return context.state;
}
vState SyncRes::getValidationStatus(const DNSName& name, bool wouldBeValid, bool typeIsDS, unsigned int depth, const string& prefix)
}
{
- const auto& it = d_cutStates.find(subdomain);
- if (it != d_cutStates.cend()) {
- LOG(prefix << name << ": Got status " << it->second << " for name " << subdomain << endl);
- return it->second;
+ const auto& iter = d_cutStates.find(subdomain);
+ if (iter != d_cutStates.cend()) {
+ LOG(prefix << name << ": Got status " << iter->second << " for name " << subdomain << endl);
+ return iter->second;
}
}
/* look for the best match we have */
DNSName best(subdomain);
while (best.chopOff()) {
- const auto& it = d_cutStates.find(best);
- if (it != d_cutStates.cend()) {
- result = it->second;
+ const auto& iter = d_cutStates.find(best);
+ if (iter != d_cutStates.cend()) {
+ result = iter->second;
if (vStateIsBogus(result) || result == vState::Insecure) {
LOG(prefix << name << ": Got status " << result << " for name " << best << endl);
return result;
if (!wouldBeValid && best != subdomain) {
/* no signatures or Bogus, we likely missed a cut, let's try to find it */
LOG(prefix << name << ": No or invalid signature/proof for " << name << ", we likely missed a cut between " << best << " and " << subdomain << ", looking for it" << endl);
- DNSName ds(best);
- std::vector<string> labelsToAdd = subdomain.makeRelative(ds).getRawLabels();
+ DNSName dsName(best);
+ std::vector<string> labelsToAdd = subdomain.makeRelative(dsName).getRawLabels();
while (!labelsToAdd.empty()) {
- ds.prependRawLabel(labelsToAdd.back());
+ dsName.prependRawLabel(labelsToAdd.back());
labelsToAdd.pop_back();
- LOG(prefix << name << ": - Looking for a DS at " << ds << endl);
+ LOG(prefix << name << ": - Looking for a DS at " << dsName << endl);
bool foundCut = false;
dsmap_t results;
- vState dsState = getDSRecords(ds, results, false, depth, prefix, false, &foundCut);
+ vState dsState = getDSRecords(dsName, results, false, depth, prefix, false, &foundCut);
if (foundCut) {
- LOG(prefix << name << ": - Found cut at " << ds << endl);
- LOG(prefix << name << ": New state for " << ds << " is " << dsState << endl);
- d_cutStates[ds] = dsState;
+ LOG(prefix << name << ": - Found cut at " << dsName << endl);
+ LOG(prefix << name << ": New state for " << dsName << " is " << dsState << endl);
+ d_cutStates[dsName] = dsState;
if (dsState != vState::Secure) {
return dsState;
vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, unsigned int depth, const string& prefix)
{
- dsmap_t ds;
+ dsmap_t dsMap;
if (signatures.empty()) {
LOG(prefix << zone << ": We have " << std::to_string(dnskeys.size()) << " DNSKEYs but no signature, going Bogus!" << endl);
return vState::BogusNoRRSIG;
DNSName signer = getSigner(signatures);
if (!signer.empty() && zone.isPartOf(signer)) {
- vState state = getDSRecords(signer, ds, false, depth, prefix);
+ vState state = getDSRecords(signer, dsMap, false, depth, prefix);
if (state != vState::Secure) {
return state;
LOG(prefix << zone << ": After checking the zone cuts again, we still have " << std::to_string(dnskeys.size()) << " DNSKEYs and the zone (" << zone << ") is still not part of the signer (" << signer << "), going Bogus!" << endl);
return vState::BogusNoValidRRSIG;
}
- else {
- return zState;
- }
+ return zState;
}
skeyset_t tentativeKeys;
}
}
- LOG(prefix << zone << ": Trying to validate " << std::to_string(tentativeKeys.size()) << " DNSKEYs with " << std::to_string(ds.size()) << " DS" << endl);
+ LOG(prefix << zone << ": Trying to validate " << std::to_string(tentativeKeys.size()) << " DNSKEYs with " << std::to_string(dsMap.size()) << " DS" << endl);
skeyset_t validatedKeys;
- auto state = validateDNSKeysAgainstDS(d_now.tv_sec, zone, ds, tentativeKeys, toSign, signatures, validatedKeys, LogObject(prefix));
+ auto state = validateDNSKeysAgainstDS(d_now.tv_sec, zone, dsMap, tentativeKeys, toSign, signatures, validatedKeys, LogObject(prefix), d_validationContext);
+
+ if (s_maxvalidationsperq != 0 && d_validationContext.d_validationsCounter > s_maxvalidationsperq) {
+ throw ImmediateServFailException("Server Failure while validating DNSKEYs, too many signature validations for this query");
+ }
LOG(prefix << zone << ": We now have " << std::to_string(validatedKeys.size()) << " DNSKEYs" << endl);
we haven't found at least one DNSKEY and a matching RRSIG
covering this set, this looks Bogus. */
if (validatedKeys.size() != tentativeKeys.size()) {
- LOG(prefix << zone << ": Let's check whether we missed a zone cut before returning a Bogus state from " << __func__ << "(" << zone << ")" << endl);
+ LOG(prefix << zone << ": Let's check whether we missed a zone cut before returning a Bogus state from " << static_cast<const char*>(__func__) << "(" << zone << ")" << endl);
/* try again to get the missed cuts, harder this time */
auto zState = getValidationStatus(zone, false, false, depth, prefix);
if (zState == vState::Secure) {
/* too bad */
- LOG(prefix << zone << ": After checking the zone cuts we are still in a Secure zone, returning Bogus state from " << __func__ << "(" << zone << ")" << endl);
+ LOG(prefix << zone << ": After checking the zone cuts we are still in a Secure zone, returning Bogus state from " << static_cast<const char*>(__func__) << "(" << zone << ")" << endl);
return state;
}
- else {
- return zState;
- }
+ return zState;
}
return state;
return context.state;
}
- LOG(prefix << signer << ": Returning Bogus state from " << __func__ << "(" << signer << ")" << endl);
+ LOG(prefix << signer << ": Returning Bogus state from " << static_cast<const char*>(__func__) << "(" << signer << ")" << endl);
return vState::BogusUnableToGetDNSKEYs;
}
LOG(prefix << signer << ": We are still in a Secure zone, returning " << vStateToString(state) << endl);
return state;
}
- else {
- return zState;
- }
+ return zState;
}
}
}
LOG(prefix << name << ": Going to validate " << recordcontents.size() << " record contents with " << signatures.size() << " sigs and " << keys.size() << " keys for " << name << "|" << type.toString() << endl);
- vState state = validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, LogObject(prefix), false);
+ vState state = validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, LogObject(prefix), d_validationContext, false);
+ if (s_maxvalidationsperq != 0 && d_validationContext.d_validationsCounter > s_maxvalidationsperq) {
+ throw ImmediateServFailException("Server Failure while validating records, too many signature validations for this query");
+ }
+
if (state == vState::Secure) {
LOG(prefix << name << ": Secure!" << endl);
return vState::Secure;
LOG(prefix << name << ": We are still in a Secure zone, returning " << vStateToString(state) << endl);
return state;
}
- else {
- return zState;
- }
+ return zState;
}
/* This function will check whether the answer should have the AA bit set, and will set if it should be set and isn't.
if (!(lwr.d_aabit || wasForwardRecurse) && rec->d_place == DNSResourceRecord::ANSWER) {
/* for now we allow a CNAME for the exact qname in ANSWER with AA=0, because Amazon DNS servers
are sending such responses */
- if (!(rec->d_type == QType::CNAME && qname == rec->d_name)) {
+ if (rec->d_type != QType::CNAME || qname != rec->d_name) {
LOG(prefix << qname << ": Removing record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the answer section without the AA bit set received from " << auth << endl);
rec = lwr.d_records.erase(rec);
continue;
}
if (!(lwr.d_aabit || wasForwardRecurse)) {
- LOG(prefix << qname << ": Removing irrelevant record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the AUTHORITY section received from " << auth << endl);
+ LOG(prefix << qname << ": Removing irrelevant record (AA not set) '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the AUTHORITY section received from " << auth << endl);
rec = lwr.d_records.erase(rec);
continue;
}
}
set<DNSName> authSet;
- for (const auto& ns : newRecords) {
- auto content = getRR<NSRecordContent>(ns);
+ for (const auto& dnsRecord : newRecords) {
+ auto content = getRR<NSRecordContent>(dnsRecord);
authSet.insert(content->getNS());
}
// The glue IPs could also differ, but we're not checking that yet, we're only looking for parent NS records not
// in the child set
bool shouldSave = false;
- for (const auto& ns : existing) {
- auto content = getRR<NSRecordContent>(ns);
+ for (const auto& dnsRecord : existing) {
+ auto content = getRR<NSRecordContent>(dnsRecord);
if (authSet.count(content->getNS()) == 0) {
LOG(prefix << domain << ": At least one parent-side NS was not in the child-side NS set, remembering parent NS set and cached IPs" << endl);
shouldSave = true;
if (shouldSave) {
map<DNSName, vector<ComboAddress>> entries;
- for (const auto& ns : existing) {
- auto content = getRR<NSRecordContent>(ns);
+ for (const auto& dnsRecord : existing) {
+ auto content = getRR<NSRecordContent>(dnsRecord);
const DNSName& name = content->getNS();
set<GetBestNSAnswer> beenthereIgnored;
- unsigned int nretrieveAddressesForNSIgnored;
+ unsigned int nretrieveAddressesForNSIgnored{};
auto addresses = getAddrs(name, depth, prefix, beenthereIgnored, true, nretrieveAddressesForNSIgnored);
entries.emplace(name, addresses);
}
}
}
-RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool rdQuery, const ComboAddress& remoteIP)
+RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>& ednsmask, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool rdQuery, const ComboAddress& remoteIP) // NOLINT(readability-function-cognitive-complexity)
{
bool wasForwardRecurse = wasForwarded && rdQuery;
tcache_t tcache;
sanitizeRecords(prefix, lwr, qname, qtype, auth, wasForwarded, rdQuery);
std::vector<std::shared_ptr<DNSRecord>> authorityRecs;
- const unsigned int labelCount = qname.countLabels();
bool isCNAMEAnswer = false;
bool isDNAMEAnswer = false;
DNSName seenAuth;
+ // names that might be expanded from a wildcard, and thus require denial of existence proof
+ // this is the queried name and any part of the CNAME chain from the queried name
+ // the key is the name itself, the value is initially false and is set to true once we have
+ // confirmed it was actually expanded from a wildcard
+ std::map<DNSName, bool> wildcardCandidates{{qname, false}};
+
+ if (rdQuery) {
+ std::unordered_map<DNSName, DNSName> cnames;
+ for (const auto& rec : lwr.d_records) {
+ if (rec.d_type != QType::CNAME || rec.d_class != QClass::IN) {
+ continue;
+ }
+ if (auto content = getRR<CNAMERecordContent>(rec)) {
+ cnames[rec.d_name] = DNSName(content->getTarget());
+ }
+ }
+ auto initial = qname;
+ while (true) {
+ auto cnameIt = cnames.find(initial);
+ if (cnameIt == cnames.end()) {
+ break;
+ }
+ initial = cnameIt->second;
+ wildcardCandidates.emplace(initial, false);
+ }
+ }
+
for (auto& rec : lwr.d_records) {
if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
continue;
seenAuth = rec.d_name;
}
+ const auto labelCount = rec.d_name.countLabels();
if (rec.d_type == QType::RRSIG) {
auto rrsig = getRR<RRSIGRecordContent>(rec);
if (rrsig) {
count can be lower than the name's label count if it was
synthesized from the wildcard. Note that the difference might
be > 1. */
- if (rec.d_name == qname && isWildcardExpanded(labelCount, *rrsig)) {
+ if (auto wcIt = wildcardCandidates.find(rec.d_name); wcIt != wildcardCandidates.end() && isWildcardExpanded(labelCount, *rrsig)) {
+ wcIt->second = true;
gatherWildcardProof = true;
if (!isWildcardExpandedOntoItself(rec.d_name, labelCount, *rrsig)) {
/* if we have a wildcard expanded onto itself, we don't need to prove
continue;
}
- if (nsecTypes.count(rec.d_type)) {
+ if (nsecTypes.count(rec.d_type) != 0) {
authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
}
else if (rec.d_type == QType::RRSIG) {
auto rrsig = getRR<RRSIGRecordContent>(rec);
- if (rrsig && nsecTypes.count(rrsig->d_type)) {
+ if (rrsig && nsecTypes.count(rrsig->d_type) != 0) {
authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
}
}
LOG("NO! - we are authoritative for the zone " << auth_domain_iter->first << endl);
continue;
}
+ LOG("YES! - This answer was ");
+ if (!wasForwarded) {
+ LOG("retrieved from the local auth store.");
+ }
else {
- LOG("YES! - This answer was ");
- if (!wasForwarded) {
- LOG("retrieved from the local auth store.");
- }
- else {
- LOG("received from a server we forward to.");
- }
- haveLogged = true;
- LOG(endl);
+ LOG("received from a server we forward to.");
}
+ haveLogged = true;
+ LOG(endl);
}
}
if (!haveLogged) {
rec.d_ttl = min(s_maxcachettl, rec.d_ttl);
- DNSRecord dr(rec);
- dr.d_ttl += d_now.tv_sec;
- dr.d_place = DNSResourceRecord::ANSWER;
- tcache[{rec.d_name, rec.d_type, rec.d_place}].records.push_back(dr);
+ DNSRecord dnsRecord(rec);
+ tcache[{rec.d_name, rec.d_type, rec.d_place}].d_ttl_time = d_now.tv_sec;
+ dnsRecord.d_ttl += d_now.tv_sec;
+ dnsRecord.d_place = DNSResourceRecord::ANSWER;
+ tcache[{rec.d_name, rec.d_type, rec.d_place}].records.push_back(dnsRecord);
}
}
else
}
}
- for (tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
+ for (auto tCacheEntry = tcache.begin(); tCacheEntry != tcache.end(); ++tCacheEntry) {
- if (i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
+ if (tCacheEntry->second.records.empty()) { // this happens when we did store signatures, but passed on the records themselves
continue;
+ }
/* Even if the AA bit is set, additional data cannot be considered
as authoritative. This is especially important during validation
dropping the RRSIG RRs. If this happens, the name server MUST NOT
set the TC bit solely because these RRSIG RRs didn't fit."
*/
- bool isAA = lwr.d_aabit && i->first.place != DNSResourceRecord::ADDITIONAL;
+ bool isAA = lwr.d_aabit && tCacheEntry->first.place != DNSResourceRecord::ADDITIONAL;
/* if we forwarded the query to a recursor, we can expect the answer to be signed,
even if the answer is not AA. Of course that's not only true inside a Secure
zone, but we check that below. */
- bool expectSignature = i->first.place == DNSResourceRecord::ANSWER || ((lwr.d_aabit || wasForwardRecurse) && i->first.place != DNSResourceRecord::ADDITIONAL);
+ bool expectSignature = tCacheEntry->first.place == DNSResourceRecord::ANSWER || ((lwr.d_aabit || wasForwardRecurse) && tCacheEntry->first.place != DNSResourceRecord::ADDITIONAL);
/* in a non authoritative answer, we only care about the DS record (or lack of) */
- if (!isAA && (i->first.type == QType::DS || i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && i->first.place == DNSResourceRecord::AUTHORITY) {
+ if (!isAA && (tCacheEntry->first.type == QType::DS || tCacheEntry->first.type == QType::NSEC || tCacheEntry->first.type == QType::NSEC3) && tCacheEntry->first.place == DNSResourceRecord::AUTHORITY) {
expectSignature = true;
}
- if (isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME || i->first.name != qname)) {
+ if (isCNAMEAnswer && (tCacheEntry->first.place != DNSResourceRecord::ANSWER || tCacheEntry->first.type != QType::CNAME || tCacheEntry->first.name != qname)) {
/*
rfc2181 states:
Note that the answer section of an authoritative answer normally
isAA = false;
expectSignature = false;
}
- else if (isDNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::DNAME || !qname.isPartOf(i->first.name))) {
+ if (isDNAMEAnswer && (tCacheEntry->first.place != DNSResourceRecord::ANSWER || tCacheEntry->first.type != QType::DNAME || !qname.isPartOf(tCacheEntry->first.name))) {
/* see above */
isAA = false;
expectSignature = false;
}
- if ((isCNAMEAnswer || isDNAMEAnswer) && i->first.place == DNSResourceRecord::AUTHORITY && i->first.type == QType::NS && auth == i->first.name) {
+ if ((isCNAMEAnswer || isDNAMEAnswer) && tCacheEntry->first.place == DNSResourceRecord::AUTHORITY && tCacheEntry->first.type == QType::NS && auth == tCacheEntry->first.name) {
/* These NS can't be authoritative since we have a CNAME/DNAME answer for which (see above) only the
record describing that alias is necessarily authoritative.
But if we allow the current auth, which might be serving the child zone, to raise the TTL
even after the delegation is gone from the parent.
So let's just do nothing with them, we can fetch them directly if we need them.
*/
- LOG(prefix << qname << ": Skipping authority NS from '" << auth << "' nameservers in CNAME/DNAME answer " << i->first.name << "|" << DNSRecordContent::NumberToType(i->first.type) << endl);
+ LOG(prefix << qname << ": Skipping authority NS from '" << auth << "' nameservers in CNAME/DNAME answer " << tCacheEntry->first.name << "|" << DNSRecordContent::NumberToType(tCacheEntry->first.type) << endl);
continue;
}
* We do the synthesis check in processRecords, here we make sure we
* don't validate the CNAME.
*/
- if (isDNAMEAnswer && i->first.type == QType::CNAME) {
+ if (isDNAMEAnswer && tCacheEntry->first.type == QType::CNAME) {
expectSignature = false;
}
vState recordState = vState::Indeterminate;
if (expectSignature && shouldValidate()) {
- vState initialState = getValidationStatus(i->first.name, !i->second.signatures.empty(), i->first.type == QType::DS, depth, prefix);
- LOG(prefix << qname << ": Got initial zone status " << initialState << " for record " << i->first.name << "|" << DNSRecordContent::NumberToType(i->first.type) << endl);
+ vState initialState = getValidationStatus(tCacheEntry->first.name, !tCacheEntry->second.signatures.empty(), tCacheEntry->first.type == QType::DS, depth, prefix);
+ LOG(prefix << qname << ": Got initial zone status " << initialState << " for record " << tCacheEntry->first.name << "|" << DNSRecordContent::NumberToType(tCacheEntry->first.type) << endl);
if (initialState == vState::Secure) {
- if (i->first.type == QType::DNSKEY && i->first.place == DNSResourceRecord::ANSWER && i->first.name == getSigner(i->second.signatures)) {
- LOG(prefix << qname << ": Validating DNSKEY for " << i->first.name << endl);
- recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth, prefix);
+ if (tCacheEntry->first.type == QType::DNSKEY && tCacheEntry->first.place == DNSResourceRecord::ANSWER && tCacheEntry->first.name == getSigner(tCacheEntry->second.signatures)) {
+ LOG(prefix << qname << ": Validating DNSKEY for " << tCacheEntry->first.name << endl);
+ recordState = validateDNSKeys(tCacheEntry->first.name, tCacheEntry->second.records, tCacheEntry->second.signatures, depth, prefix);
}
else {
- LOG(prefix << qname << ": Validating non-additional " << QType(i->first.type).toString() << " record for " << i->first.name << endl);
- recordState = validateRecordsWithSigs(depth, prefix, qname, qtype, i->first.name, QType(i->first.type), i->second.records, i->second.signatures);
+ LOG(prefix << qname << ": Validating non-additional " << QType(tCacheEntry->first.type).toString() << " record for " << tCacheEntry->first.name << endl);
+ recordState = validateRecordsWithSigs(depth, prefix, qname, qtype, tCacheEntry->first.name, QType(tCacheEntry->first.type), tCacheEntry->second.records, tCacheEntry->second.signatures);
}
}
else {
if (vStateIsBogus(recordState)) {
/* this is a TTD by now, be careful */
- for (auto& record : i->second.records) {
- record.d_ttl = std::min(record.d_ttl, static_cast<uint32_t>(s_maxbogusttl + d_now.tv_sec));
+ for (auto& record : tCacheEntry->second.records) {
+ auto newval = std::min(record.d_ttl, static_cast<uint32_t>(s_maxbogusttl + d_now.tv_sec));
+ record.d_ttl = newval;
}
+ tCacheEntry->second.d_ttl_time = d_now.tv_sec;
}
/* We don't need to store NSEC3 records in the positive cache because:
- DS (special case)
- NS, A and AAAA (used for infra queries)
*/
- if (i->first.type != QType::NSEC3 && (i->first.type == QType::DS || i->first.type == QType::NS || i->first.type == QType::A || i->first.type == QType::AAAA || isAA || wasForwardRecurse)) {
+ if (tCacheEntry->first.type != QType::NSEC3 && (tCacheEntry->first.type == QType::DS || tCacheEntry->first.type == QType::NS || tCacheEntry->first.type == QType::A || tCacheEntry->first.type == QType::AAAA || isAA || wasForwardRecurse)) {
bool doCache = true;
- if (i->first.place == DNSResourceRecord::ANSWER && ednsmask) {
+ if (tCacheEntry->first.place == DNSResourceRecord::ANSWER && ednsmask) {
const bool isv4 = ednsmask->isIPv4();
if ((isv4 && s_ecsipv4nevercache) || (!isv4 && s_ecsipv6nevercache)) {
doCache = false;
if (manyMaskBits) {
uint32_t minttl = UINT32_MAX;
- for (const auto& it : i->second.records) {
- if (it.d_ttl < minttl)
- minttl = it.d_ttl;
+ for (const auto& iter : tCacheEntry->second.records) {
+ if (iter.d_ttl < minttl) {
+ minttl = iter.d_ttl;
+ }
}
bool ttlIsSmall = minttl < s_ecscachelimitttl + d_now.tv_sec;
if (ttlIsSmall) {
if (doCache) {
// Check if we are going to replace a non-auth (parent) NS recordset
- if (isAA && i->first.type == QType::NS && s_save_parent_ns_set) {
- rememberParentSetIfNeeded(i->first.name, i->second.records, depth, prefix);
+ if (isAA && tCacheEntry->first.type == QType::NS && s_save_parent_ns_set) {
+ rememberParentSetIfNeeded(tCacheEntry->first.name, tCacheEntry->second.records, depth, prefix);
+ }
+ bool thisRRNeedsWildcardProof = false;
+ if (gatherWildcardProof) {
+ if (auto wcIt = wildcardCandidates.find(tCacheEntry->first.name); wcIt != wildcardCandidates.end() && wcIt->second) {
+ thisRRNeedsWildcardProof = true;
+ }
}
- g_recCache->replace(d_now.tv_sec, i->first.name, i->first.type, i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, auth, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP, d_refresh);
+ g_recCache->replace(d_now.tv_sec, tCacheEntry->first.name, tCacheEntry->first.type, tCacheEntry->second.records, tCacheEntry->second.signatures, thisRRNeedsWildcardProof ? authorityRecs : std::vector<std::shared_ptr<DNSRecord>>(), tCacheEntry->first.type == QType::DS ? true : isAA, auth, tCacheEntry->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP, d_refresh, tCacheEntry->second.d_ttl_time);
// Delete potential negcache entry. When a record recovers with serve-stale the negcache entry can cause the wrong entry to
// be served, as negcache entries are checked before record cache entries
if (NegCache::s_maxServedStaleExtensions > 0) {
- g_negCache->wipeTyped(i->first.name, i->first.type);
+ g_negCache->wipeTyped(tCacheEntry->first.name, tCacheEntry->first.type);
}
- if (g_aggressiveNSECCache && needWildcardProof && recordState == vState::Secure && i->first.place == DNSResourceRecord::ANSWER && i->first.name == qname && !i->second.signatures.empty() && !d_routingTag && !ednsmask) {
+ if (g_aggressiveNSECCache && thisRRNeedsWildcardProof && recordState == vState::Secure && tCacheEntry->first.place == DNSResourceRecord::ANSWER && !tCacheEntry->second.signatures.empty() && !d_routingTag && !ednsmask) {
/* we have an answer synthesized from a wildcard and aggressive NSEC is enabled, we need to store the
wildcard in its non-expanded form in the cache to be able to synthesize wildcard answers later */
- const auto& rrsig = i->second.signatures.at(0);
+ const auto& rrsig = tCacheEntry->second.signatures.at(0);
+ const auto labelCount = tCacheEntry->first.name.countLabels();
- if (isWildcardExpanded(labelCount, *rrsig) && !isWildcardExpandedOntoItself(i->first.name, labelCount, *rrsig)) {
- DNSName realOwner = getNSECOwnerName(i->first.name, i->second.signatures);
+ if (isWildcardExpanded(labelCount, *rrsig) && !isWildcardExpandedOntoItself(tCacheEntry->first.name, labelCount, *rrsig)) {
+ DNSName realOwner = getNSECOwnerName(tCacheEntry->first.name, tCacheEntry->second.signatures);
std::vector<DNSRecord> content;
- content.reserve(i->second.records.size());
- for (const auto& record : i->second.records) {
+ content.reserve(tCacheEntry->second.records.size());
+ for (const auto& record : tCacheEntry->second.records) {
DNSRecord nonExpandedRecord(record);
nonExpandedRecord.d_name = realOwner;
content.push_back(std::move(nonExpandedRecord));
}
- g_recCache->replace(d_now.tv_sec, realOwner, QType(i->first.type), content, i->second.signatures, /* no additional records in that case */ {}, i->first.type == QType::DS ? true : isAA, auth, boost::none, boost::none, recordState, remoteIP, d_refresh);
+ g_recCache->replace(d_now.tv_sec, realOwner, QType(tCacheEntry->first.type), content, tCacheEntry->second.signatures, /* no additional records in that case */ {}, tCacheEntry->first.type == QType::DS ? true : isAA, auth, boost::none, boost::none, recordState, remoteIP, d_refresh, tCacheEntry->second.d_ttl_time);
}
}
}
}
- if (seenAuth.empty() && !i->second.signatures.empty()) {
- seenAuth = getSigner(i->second.signatures);
+ if (seenAuth.empty() && !tCacheEntry->second.signatures.empty()) {
+ seenAuth = getSigner(tCacheEntry->second.signatures);
}
- if (g_aggressiveNSECCache && (i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && recordState == vState::Secure && !seenAuth.empty()) {
+ if (g_aggressiveNSECCache && (tCacheEntry->first.type == QType::NSEC || tCacheEntry->first.type == QType::NSEC3) && recordState == vState::Secure && !seenAuth.empty()) {
// Good candidate for NSEC{,3} caching
- g_aggressiveNSECCache->insertNSEC(seenAuth, i->first.name, i->second.records.at(0), i->second.signatures, i->first.type == QType::NSEC3);
+ g_aggressiveNSECCache->insertNSEC(seenAuth, tCacheEntry->first.name, tCacheEntry->second.records.at(0), tCacheEntry->second.signatures, tCacheEntry->first.type == QType::NSEC3);
}
- if (i->first.place == DNSResourceRecord::ANSWER && ednsmask) {
+ if (tCacheEntry->first.place == DNSResourceRecord::ANSWER && ednsmask) {
d_wasVariable = true;
}
}
+ if (gatherWildcardProof) {
+ if (auto wcIt = wildcardCandidates.find(qname); wcIt != wildcardCandidates.end() && !wcIt->second) {
+ // the queried name was not expanded from a wildcard, a record in the CNAME chain was, so we don't need to gather wildcard proof now: we will do that when looking up the CNAME chain
+ gatherWildcardProof = false;
+ }
+ }
+
return RCode::NoError;
}
updateValidationState(qname, state, neValidationState, prefix);
}
-dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned, const string& prefix)
+dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& negEntry, const dState expectedState, bool referralToUnsigned, const string& prefix)
{
- cspmap_t csp = harvestCSPFromNE(ne);
- return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE, LogObject(prefix));
+ cspmap_t csp = harvestCSPFromNE(negEntry);
+ return getDenial(csp, negEntry.d_name, negEntry.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE, d_validationContext, LogObject(prefix));
}
-bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const bool gatherWildcardProof, const unsigned int wildcardLabelsCount, int& rcode, bool& negIndicHasSignatures, unsigned int depth)
+bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const bool gatherWildcardProof, const unsigned int wildcardLabelsCount, int& rcode, bool& negIndicHasSignatures, unsigned int depth) // // NOLINT(readability-function-cognitive-complexity)
{
bool done = false;
- DNSName dnameTarget, dnameOwner;
+ DNSName dnameTarget;
+ DNSName dnameOwner;
uint32_t dnameTTL = 0;
bool referralOnDS = false;
if (rec.d_place == DNSResourceRecord::ANSWER && !(lwr.d_aabit || sendRDQuery)) {
/* for now we allow a CNAME for the exact qname in ANSWER with AA=0, because Amazon DNS servers
are sending such responses */
- if (!(rec.d_type == QType::CNAME && rec.d_name == qname)) {
+ if (rec.d_type != QType::CNAME || rec.d_name != qname) {
continue;
}
}
ret.push_back(rec);
}
- NegCache::NegCacheEntry ne;
+ NegCache::NegCacheEntry negEntry;
uint32_t lowestTTL = rec.d_ttl;
/* if we get an NXDomain answer with a CNAME, the name
does exist but the target does not */
- ne.d_name = newtarget.empty() ? qname : newtarget;
- ne.d_qtype = QType::ENT; // this encodes 'whole record'
- ne.d_auth = rec.d_name;
- harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+ negEntry.d_name = newtarget.empty() ? qname : newtarget;
+ negEntry.d_qtype = QType::ENT; // this encodes 'whole record'
+ negEntry.d_auth = rec.d_name;
+ harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
if (vStateIsBogus(state)) {
- ne.d_validationState = state;
+ negEntry.d_validationState = state;
}
else {
/* here we need to get the validation status of the zone telling us that the domain does not
exist, ie the owner of the SOA */
- auto recordState = getValidationStatus(rec.d_name, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), false, depth, prefix);
+ auto recordState = getValidationStatus(rec.d_name, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), false, depth, prefix);
if (recordState == vState::Secure) {
- dState denialState = getDenialValidationState(ne, dState::NXDOMAIN, false, prefix);
- updateDenialValidationState(qname, ne.d_validationState, ne.d_name, state, denialState, dState::NXDOMAIN, false, depth, prefix);
+ dState denialState = getDenialValidationState(negEntry, dState::NXDOMAIN, false, prefix);
+ updateDenialValidationState(qname, negEntry.d_validationState, negEntry.d_name, state, denialState, dState::NXDOMAIN, false, depth, prefix);
}
else {
- ne.d_validationState = recordState;
- updateValidationState(qname, state, ne.d_validationState, prefix);
+ negEntry.d_validationState = recordState;
+ updateValidationState(qname, state, negEntry.d_validationState, prefix);
}
}
- if (vStateIsBogus(ne.d_validationState)) {
+ if (vStateIsBogus(negEntry.d_validationState)) {
lowestTTL = min(lowestTTL, s_maxbogusttl);
}
- ne.d_ttd = d_now.tv_sec + lowestTTL;
- ne.d_orig_ttl = lowestTTL;
+ negEntry.d_ttd = d_now.tv_sec + lowestTTL;
+ negEntry.d_orig_ttl = lowestTTL;
/* if we get an NXDomain answer with a CNAME, let's not cache the
target, even the server was authoritative for it,
and do an additional query for the CNAME target.
We have a regression test making sure we do exactly that.
*/
if (newtarget.empty() && putInNegCache) {
- g_negCache->add(ne);
+ g_negCache->add(negEntry);
// doCNAMECacheCheck() checks record cache and does not look into negcache. That means that an old record might be found if
// serve-stale is active. Avoid that by explicitly zapping that CNAME record.
if (qtype == QType::CNAME && MemRecursorCache::s_maxServedStaleExtensions > 0) {
g_recCache->doWipeCache(qname, false, qtype);
}
- if (s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot() && lwr.d_aabit) {
- ne.d_name = ne.d_name.getLastLabel();
- g_negCache->add(ne);
+ if (s_rootNXTrust && negEntry.d_auth.isRoot() && auth.isRoot() && lwr.d_aabit) {
+ negEntry.d_name = negEntry.d_name.getLastLabel();
+ g_negCache->add(negEntry);
}
}
- negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
+ negIndicHasSignatures = !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty();
negindic = true;
}
else if (rec.d_place == DNSResourceRecord::ANSWER && s_redirectionQTypes.count(rec.d_type) > 0 && // CNAME or DNAME answer
ret.erase(std::remove_if(
ret.begin(),
ret.end(),
- [&qname](DNSRecord& rr) {
- return (rr.d_place == DNSResourceRecord::ANSWER && rr.d_type == QType::CNAME && rr.d_name == qname);
+ [&qname](DNSRecord& dnsrecord) {
+ return (dnsrecord.d_place == DNSResourceRecord::ANSWER && dnsrecord.d_type == QType::CNAME && dnsrecord.d_name == qname);
}),
ret.end());
}
if (needWildcardProof) {
/* positive answer synthesized from a wildcard */
- NegCache::NegCacheEntry ne;
- ne.d_name = qname;
- ne.d_qtype = QType::ENT; // this encodes 'whole record'
+ NegCache::NegCacheEntry negEntry;
+ negEntry.d_name = qname;
+ negEntry.d_qtype = QType::ENT; // this encodes 'whole record'
uint32_t lowestTTL = rec.d_ttl;
- harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+ harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
if (vStateIsBogus(state)) {
- ne.d_validationState = state;
+ negEntry.d_validationState = state;
}
else {
- auto recordState = getValidationStatus(qname, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), false, depth, prefix);
+ auto recordState = getValidationStatus(qname, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), false, depth, prefix);
if (recordState == vState::Secure) {
/* We have a positive answer synthesized from a wildcard, we need to check that we have
proof that the exact name doesn't exist so the wildcard can be used,
as described in section 5.3.4 of RFC 4035 and 5.3 of RFC 7129.
*/
- cspmap_t csp = harvestCSPFromNE(ne);
- dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, LogObject(prefix), false, wildcardLabelsCount);
+ cspmap_t csp = harvestCSPFromNE(negEntry);
+ dState res = getDenial(csp, qname, negEntry.d_qtype.getCode(), false, false, d_validationContext, LogObject(prefix), false, wildcardLabelsCount);
if (res != dState::NXDOMAIN) {
- vState st = vState::BogusInvalidDenial;
+ vState tmpState = vState::BogusInvalidDenial;
if (res == dState::INSECURE || res == dState::OPTOUT) {
/* Some part could not be validated, for example a NSEC3 record with a too large number of iterations,
this is not enough to warrant a Bogus, but go Insecure. */
- st = vState::Insecure;
+ tmpState = vState::Insecure;
LOG(prefix << qname << ": Unable to validate denial in wildcard expanded positive response found for " << qname << ", returning Insecure, res=" << res << endl);
}
else {
rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
}
- updateValidationState(qname, state, st, prefix);
+ updateValidationState(qname, state, tmpState, prefix);
/* we already stored the record with a different validation status, let's fix it */
- updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
+ updateValidationStatusInCache(qname, qtype, lwr.d_aabit, tmpState);
}
}
}
}
else if (realreferral && rec.d_place == DNSResourceRecord::AUTHORITY && (rec.d_type == QType::NSEC || rec.d_type == QType::NSEC3) && newauth.isPartOf(auth)) {
/* we might have received a denial of the DS, let's check */
- NegCache::NegCacheEntry ne;
+ NegCache::NegCacheEntry negEntry;
uint32_t lowestTTL = rec.d_ttl;
- harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+ harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
if (!vStateIsBogus(state)) {
- auto recordState = getValidationStatus(newauth, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), true, depth, prefix);
+ auto recordState = getValidationStatus(newauth, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), true, depth, prefix);
if (recordState == vState::Secure) {
- ne.d_auth = auth;
- ne.d_name = newauth;
- ne.d_qtype = QType::DS;
+ negEntry.d_auth = auth;
+ negEntry.d_name = newauth;
+ negEntry.d_qtype = QType::DS;
rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
- dState denialState = getDenialValidationState(ne, dState::NXQTYPE, true, prefix);
+ dState denialState = getDenialValidationState(negEntry, dState::NXQTYPE, true, prefix);
if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
- ne.d_ttd = lowestTTL + d_now.tv_sec;
- ne.d_orig_ttl = lowestTTL;
- ne.d_validationState = vState::Secure;
+ negEntry.d_ttd = lowestTTL + d_now.tv_sec;
+ negEntry.d_orig_ttl = lowestTTL;
+ negEntry.d_validationState = vState::Secure;
if (denialState == dState::OPTOUT) {
- ne.d_validationState = vState::Insecure;
+ negEntry.d_validationState = vState::Insecure;
}
LOG(prefix << qname << ": Got negative indication of DS record for '" << newauth << "'" << endl);
- g_negCache->add(ne);
+ g_negCache->add(negEntry);
/* Careful! If the client is asking for a DS that does not exist, we need to provide the SOA along with the NSEC(3) proof
and we might not have it if we picked up the proof from a delegation, in which case we need to keep on to do the actual DS
if (qtype == QType::DS && qname == newauth && (d_externalDSQuery.empty() || qname != d_externalDSQuery)) {
/* we are actually done! */
negindic = true;
- negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
+ negIndicHasSignatures = !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty();
nsset.clear();
}
}
else {
rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
- NegCache::NegCacheEntry ne;
- ne.d_auth = rec.d_name;
+ NegCache::NegCacheEntry negEntry;
+ negEntry.d_auth = rec.d_name;
uint32_t lowestTTL = rec.d_ttl;
- ne.d_name = qname;
- ne.d_qtype = qtype;
- harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+ negEntry.d_name = qname;
+ negEntry.d_qtype = qtype;
+ harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
if (vStateIsBogus(state)) {
- ne.d_validationState = state;
+ negEntry.d_validationState = state;
}
else {
- auto recordState = getValidationStatus(qname, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), qtype == QType::DS, depth, prefix);
+ auto recordState = getValidationStatus(qname, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), qtype == QType::DS, depth, prefix);
if (recordState == vState::Secure) {
- dState denialState = getDenialValidationState(ne, dState::NXQTYPE, false, prefix);
- updateDenialValidationState(qname, ne.d_validationState, ne.d_name, state, denialState, dState::NXQTYPE, qtype == QType::DS, depth, prefix);
+ dState denialState = getDenialValidationState(negEntry, dState::NXQTYPE, false, prefix);
+ updateDenialValidationState(qname, negEntry.d_validationState, negEntry.d_name, state, denialState, dState::NXQTYPE, qtype == QType::DS, depth, prefix);
}
else {
- ne.d_validationState = recordState;
- updateValidationState(qname, state, ne.d_validationState, prefix);
+ negEntry.d_validationState = recordState;
+ updateValidationState(qname, state, negEntry.d_validationState, prefix);
}
}
- if (vStateIsBogus(ne.d_validationState)) {
+ if (vStateIsBogus(negEntry.d_validationState)) {
lowestTTL = min(lowestTTL, s_maxbogusttl);
rec.d_ttl = min(rec.d_ttl, s_maxbogusttl);
}
- ne.d_ttd = d_now.tv_sec + lowestTTL;
- ne.d_orig_ttl = lowestTTL;
- if (qtype.getCode()) { // prevents us from NXDOMAIN'ing a whole domain
- g_negCache->add(ne);
+ negEntry.d_ttd = d_now.tv_sec + lowestTTL;
+ negEntry.d_orig_ttl = lowestTTL;
+ if (qtype.getCode() != 0) { // prevents us from NXDOMAIN'ing a whole domain
+ // doCNAMECacheCheck() checks record cache and does not look into negcache. That means that an old record might be found if
+ // serve-stale is active. Avoid that by explicitly zapping that CNAME record.
+ if (qtype == QType::CNAME && MemRecursorCache::s_maxServedStaleExtensions > 0) {
+ g_recCache->doWipeCache(qname, false, qtype);
+ }
+ g_negCache->add(negEntry);
}
ret.push_back(rec);
negindic = true;
- negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
+ negIndicHasSignatures = !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty();
}
}
}
return done;
}
-static void submitTryDotTask(ComboAddress address, const DNSName& auth, const DNSName nsname, time_t now)
+static void submitTryDotTask(ComboAddress address, const DNSName& auth, const DNSName& nsname, time_t now)
{
if (address.getPort() == 853) {
return;
if (lock->d_numBusy >= SyncRes::s_max_busy_dot_probes) {
return;
}
- auto it = lock->d_map.emplace(DoTStatus{address, auth, now + dotFailWait}).first;
- if (it->d_status == DoTStatus::Busy) {
+ auto iter = lock->d_map.emplace(DoTStatus{address, auth, now + dotFailWait}).first;
+ if (iter->d_status == DoTStatus::Busy) {
return;
}
- if (it->d_ttd > now) {
- if (it->d_status == DoTStatus::Bad) {
+ if (iter->d_ttd > now) {
+ if (iter->d_status == DoTStatus::Bad) {
return;
}
- if (it->d_status == DoTStatus::Good) {
+ if (iter->d_status == DoTStatus::Good) {
return;
}
// We only want to probe auths that we have seen before, auth that only come around once are not interesting
- if (it->d_status == DoTStatus::Unknown && it->d_count == 0) {
+ if (iter->d_status == DoTStatus::Unknown && iter->d_count == 0) {
return;
}
}
- lock->d_map.modify(it, [=](DoTStatus& st) { st.d_ttd = now + dotFailWait; });
+ lock->d_map.modify(iter, [=](DoTStatus& status) { status.d_ttd = now + dotFailWait; });
bool pushed = pushTryDoTTask(auth, QType::SOA, address, std::numeric_limits<time_t>::max(), nsname);
if (pushed) {
- it->d_status = DoTStatus::Busy;
+ iter->d_status = DoTStatus::Busy;
++lock->d_numBusy;
}
}
{
address.setPort(853);
auto lock = s_dotMap.lock();
- auto it = lock->d_map.find(address);
- if (it == lock->d_map.end()) {
+ auto iter = lock->d_map.find(address);
+ if (iter == lock->d_map.end()) {
return false;
}
- it->d_count++;
- if (it->d_status == DoTStatus::Good && it->d_ttd > now) {
- return true;
- }
- return false;
+ iter->d_count++;
+ return iter->d_status == DoTStatus::Good && iter->d_ttd > now;
}
static void updateDoTStatus(ComboAddress address, DoTStatus::Status status, time_t time, bool updateBusy = false)
{
address.setPort(853);
auto lock = s_dotMap.lock();
- auto it = lock->d_map.find(address);
- if (it != lock->d_map.end()) {
- it->d_status = status;
- lock->d_map.modify(it, [=](DoTStatus& st) { st.d_ttd = time; });
+ auto iter = lock->d_map.find(address);
+ if (iter != lock->d_map.end()) {
+ iter->d_status = status;
+ lock->d_map.modify(iter, [=](DoTStatus& statusToModify) { statusToModify.d_ttd = time; });
if (updateBusy) {
--lock->d_numBusy;
}
log->error(Logr::Debug, msg, "Failed to probe DoT records, got an exception", "exception", Logging::Loggable(ename));
};
LWResult lwr;
- bool truncated;
- bool spoofed;
- boost::optional<Netmask> nm;
+ bool truncated{};
+ bool spoofed{};
+ boost::optional<Netmask> netmask;
address.setPort(853);
// We use the fact that qname equals auth
- bool ok = false;
+ bool isOK = false;
try {
boost::optional<EDNSExtendedError> extendedError;
- ok = doResolveAtThisIP("", qname, qtype, lwr, nm, qname, false, false, nsName, address, true, true, truncated, spoofed, extendedError, true);
- ok = ok && lwr.d_rcode == RCode::NoError && lwr.d_records.size() > 0;
+ isOK = doResolveAtThisIP("", qname, qtype, lwr, netmask, qname, false, false, nsName, address, true, true, truncated, spoofed, extendedError, true);
+ isOK = isOK && lwr.d_rcode == RCode::NoError && !lwr.d_records.empty();
}
catch (const PDNSException& e) {
logHelper2(e.reason, "PDNSException");
catch (...) {
logHelper1("other");
}
- updateDoTStatus(address, ok ? DoTStatus::Good : DoTStatus::Bad, now + (ok ? dotSuccessWait : dotFailWait), true);
- return ok;
+ updateDoTStatus(address, isOK ? DoTStatus::Good : DoTStatus::Bad, now + (isOK ? dotSuccessWait : dotFailWait), true);
+ return isOK;
}
-bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle)
+void SyncRes::ednsStats(boost::optional<Netmask>& ednsmask, const DNSName& qname, const string& prefix)
{
- bool chained = false;
- LWResult::Result resolveret = LWResult::Result::Success;
- t_Counters.at(rec::Counter::outqueries)++;
- d_outqueries++;
- checkMaxQperQ(qname);
+ if (!ednsmask) {
+ return;
+ }
+ s_ecsresponses++;
+ LOG(prefix << qname << ": Received EDNS Client Subnet Mask " << ednsmask->toString() << " on response" << endl);
- if (s_maxtotusec && d_totUsec > s_maxtotusec) {
- if (s_addExtendedResolutionDNSErrors) {
- extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::NoReachableAuthority), "Timeout waiting for answer(s)"};
+ if (ednsmask->getBits() > 0) {
+ if (ednsmask->isIPv4()) {
+ ++SyncRes::s_ecsResponsesBySubnetSize4.at(ednsmask->getBits() - 1);
+ }
+ else {
+ ++SyncRes::s_ecsResponsesBySubnetSize6.at(ednsmask->getBits() - 1);
}
- throw ImmediateServFailException("Too much time waiting for " + qname.toLogString() + "|" + qtype.toString() + ", timeouts: " + std::to_string(d_timeouts) + ", throttles: " + std::to_string(d_throttledqueries) + ", queries: " + std::to_string(d_outqueries) + ", " + std::to_string(d_totUsec / 1000) + " ms");
}
+}
+void SyncRes::updateQueryCounts(const string& prefix, const DNSName& qname, const ComboAddress& address, bool doTCP, bool doDoT)
+{
+ t_Counters.at(rec::Counter::outqueries)++;
+ d_outqueries++;
+ checkMaxQperQ(qname);
+ if (address.sin4.sin_family == AF_INET6) {
+ t_Counters.at(rec::Counter::ipv6queries)++;
+ }
if (doTCP) {
if (doDoT) {
- LOG(prefix << qname << ": Using DoT with " << remoteIP.toStringWithPort() << endl);
+ LOG(prefix << qname << ": Using DoT with " << address.toStringWithPort() << endl);
t_Counters.at(rec::Counter::dotoutqueries)++;
d_dotoutqueries++;
}
else {
- LOG(prefix << qname << ": Using TCP with " << remoteIP.toStringWithPort() << endl);
+ LOG(prefix << qname << ": Using TCP with " << address.toStringWithPort() << endl);
t_Counters.at(rec::Counter::tcpoutqueries)++;
d_tcpoutqueries++;
}
}
+}
+
+bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle)
+{
+ bool chained = false;
+ LWResult::Result resolveret = LWResult::Result::Success;
+
+ if (s_maxtotusec != 0 && d_totUsec > s_maxtotusec) {
+ if (s_addExtendedResolutionDNSErrors) {
+ extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::NoReachableAuthority), "Timeout waiting for answer(s)"};
+ }
+ throw ImmediateServFailException("Too much time waiting for " + qname.toLogString() + "|" + qtype.toString() + ", timeouts: " + std::to_string(d_timeouts) + ", throttles: " + std::to_string(d_throttledqueries) + ", queries: " + std::to_string(d_outqueries) + ", " + std::to_string(d_totUsec / 1000) + " ms");
+ }
int preOutQueryRet = RCode::NoError;
if (d_pdl && d_pdl->preoutquery(remoteIP, d_requestor, qname, qtype, doTCP, lwr.d_records, preOutQueryRet, d_eventTrace, timeval{0, 0})) {
LOG(prefix << qname << ": Adding EDNS Client Subnet Mask " << ednsmask->toString() << " to query" << endl);
s_ecsqueries++;
}
+ updateQueryCounts(prefix, qname, remoteIP, doTCP, doDoT);
resolveret = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, auth, qtype.getCode(),
doTCP, sendRDQuery, &d_now, ednsmask, &lwr, &chained, nsName); // <- we go out on the wire!
- if (ednsmask) {
- s_ecsresponses++;
- LOG(prefix << qname << ": Received EDNS Client Subnet Mask " << ednsmask->toString() << " on response" << endl);
- if (ednsmask->getBits() > 0) {
- if (ednsmask->isIPv4()) {
- ++SyncRes::s_ecsResponsesBySubnetSize4.at(ednsmask->getBits() - 1);
- }
- else {
- ++SyncRes::s_ecsResponsesBySubnetSize6.at(ednsmask->getBits() - 1);
- }
- }
- }
+ ednsStats(ednsmask, qname, prefix);
}
/* preoutquery killed the query by setting dq.rcode to -3 */
++t_Counters.at(rec::RCode::auth).rcodeCounters.at(static_cast<uint8_t>(lwr.d_rcode));
if (!dontThrottle) {
- auto dontThrottleNames = g_dontThrottleNames.getLocal();
- auto dontThrottleNetmasks = g_dontThrottleNetmasks.getLocal();
- dontThrottle = dontThrottleNames->check(nsName) || dontThrottleNetmasks->match(remoteIP);
+ dontThrottle = shouldNotThrottle(&nsName, &remoteIP);
}
if (resolveret != LWResult::Result::Success) {
d_timeouts++;
t_Counters.at(rec::Counter::outgoingtimeouts)++;
- if (remoteIP.sin4.sin_family == AF_INET)
+ if (remoteIP.sin4.sin_family == AF_INET) {
t_Counters.at(rec::Counter::outgoing4timeouts)++;
- else
+ }
+ else {
t_Counters.at(rec::Counter::outgoing6timeouts)++;
+ }
- if (t_timeouts)
+ if (t_timeouts) {
t_timeouts->push_back(remoteIP);
+ }
}
else if (resolveret == LWResult::Result::OSLimitError) {
/* OS resource limit reached */
LOG(prefix << qname << ": Error resolving from " << remoteIP.toString() << (doTCP ? " over TCP" : "") << ", possible error: " << stringerror() << endl);
}
+ // don't account for resource limits, they are our own fault
+ // And don't throttle when the IP address is on the dontThrottleNetmasks list or the name is part of dontThrottleNames
if (resolveret != LWResult::Result::OSLimitError && !chained && !dontThrottle) {
- // don't account for resource limits, they are our own fault
- // And don't throttle when the IP address is on the dontThrottleNetmasks list or the name is part of dontThrottleNames
s_nsSpeeds.lock()->find_or_enter(nsName.empty() ? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
- // code below makes sure we don't filter COM or the root
- if (s_serverdownmaxfails > 0 && (auth != g_rootdnsname) && s_fails.lock()->incr(remoteIP, d_now) >= s_serverdownmaxfails) {
+ // make sure we don't throttle the root
+ if (s_serverdownmaxfails > 0 && auth != g_rootdnsname && s_fails.lock()->incr(remoteIP, d_now) >= s_serverdownmaxfails) {
LOG(prefix << qname << ": Max fails reached resolving on " << remoteIP.toString() << ". Going full throttle for " << s_serverdownthrottletime << " seconds" << endl);
// mark server as down
doThrottle(d_now.tv_sec, remoteIP, s_serverdownthrottletime, 10000);
return false;
}
- if (lwr.d_validpacket == false) {
+ if (!lwr.d_validpacket) {
LOG(prefix << qname << ": " << nsName << " (" << remoteIP.toString() << ") returned a packet we could not parse over " << (doTCP ? "TCP" : "UDP") << ", trying sibling IP or NS" << endl);
if (!chained && !dontThrottle) {
}
return false;
}
- else {
- /* we got an answer */
- if (lwr.d_rcode != RCode::NoError && lwr.d_rcode != RCode::NXDomain) {
- LOG(prefix << qname << ": " << nsName << " (" << remoteIP.toString() << ") returned a " << RCode::to_s(lwr.d_rcode) << ", trying sibling IP or NS" << endl);
- if (!chained && !dontThrottle) {
- if (wasForwarded && lwr.d_rcode == RCode::ServFail) {
- // rather than throttling what could be the only server we have for this destination, let's make sure we try a different one if there is one available
- // on the other hand, we might keep hammering a server under attack if there is no other alternative, or the alternative is overwhelmed as well, but
- // at the very least we will detect that if our packets stop being answered
- s_nsSpeeds.lock()->find_or_enter(nsName.empty() ? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
- }
- else {
- doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 60, 3);
- }
+ /* we got an answer */
+ if (lwr.d_rcode != RCode::NoError && lwr.d_rcode != RCode::NXDomain) {
+ LOG(prefix << qname << ": " << nsName << " (" << remoteIP.toString() << ") returned a " << RCode::to_s(lwr.d_rcode) << ", trying sibling IP or NS" << endl);
+ if (!chained && !dontThrottle) {
+ if (wasForwarded && lwr.d_rcode == RCode::ServFail) {
+ // rather than throttling what could be the only server we have for this destination, let's make sure we try a different one if there is one available
+ // on the other hand, we might keep hammering a server under attack if there is no other alternative, or the alternative is overwhelmed as well, but
+ // at the very least we will detect that if our packets stop being answered
+ s_nsSpeeds.lock()->find_or_enter(nsName.empty() ? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
+ }
+ else {
+ doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 60, 3);
}
- return false;
}
+ return false;
}
/* this server sent a valid answer, mark it backup up if it was down */
if (s_serverdownmaxfails > 0) {
s_fails.lock()->clear(remoteIP);
}
+ // Clear all throttles for this IP, both general and specific throttles for qname-qtype
+ unThrottle(remoteIP, qname, qtype);
if (lwr.d_tcbit) {
truncated = true;
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);
updateValidationState(qname, state, cnameContext.state, prefix);
}
-bool SyncRes::processAnswer(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet& nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state, const ComboAddress& remoteIP)
+bool SyncRes::processAnswer(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask>& ednsmask, bool sendRDQuery, NsSet& nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state, const ComboAddress& remoteIP)
{
- if (s_minimumTTL) {
+ if (s_minimumTTL != 0) {
for (auto& rec : lwr.d_records) {
rec.d_ttl = max(rec.d_ttl, s_minimumTTL);
}
return true;
}
- if (nsset.empty() && !lwr.d_rcode && (negindic || lwr.d_aabit || sendRDQuery)) {
+ if (nsset.empty() && lwr.d_rcode == 0 && (negindic || lwr.d_aabit || sendRDQuery)) {
LOG(prefix << qname << ": Status=noerror, other types may exist, but we are done " << (negindic ? "(have negative SOA) " : "") << (lwr.d_aabit ? "(have aa bit) " : "") << endl);
auto tempState = getValidationStatus(qname, negIndicHasSignatures, qtype == QType::DS, depth, prefix);
}
LOG("looping to them" << endl);
*gotNewServers = true;
- auth = newauth;
+ auth = std::move(newauth);
return false;
}
return false;
}
-bool SyncRes::doDoTtoAuth(const DNSName& ns) const
+bool SyncRes::doDoTtoAuth(const DNSName& nameServer)
{
- return g_DoTToAuthNames.getLocal()->check(ns);
+ return g_DoTToAuthNames.getLocal()->check(nameServer);
}
/** returns:
* -1 in case of no results
* rcode otherwise
*/
+// NOLINTNEXTLINE(readability-function-cognitive-complexity)
int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, const DNSName& qname, const QType qtype,
vector<DNSRecord>& ret,
unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, Context& context, StopAtDelegation* stopAtDelegation,
// We always allow 5 NS name resolving attempts with empty results.
unsigned int nsLimit = s_maxnsaddressqperq;
if (rnameservers.size() > nsLimit) {
- int newLimit = static_cast<int>(nsLimit) - (rnameservers.size() - nsLimit);
+ int newLimit = static_cast<int>(nsLimit - (rnameservers.size() - nsLimit));
nsLimit = std::max(5, newLimit);
}
return rcode;
}
if (gotNewServers) {
- if (stopAtDelegation && *stopAtDelegation == Stop) {
+ if (stopAtDelegation != nullptr && *stopAtDelegation == Stop) {
*stopAtDelegation = Stopped;
return rcode;
}
}
else {
if (fallBack != nullptr) {
- if (auto it = fallBack->find(tns->first); it != fallBack->end()) {
- remoteIPs = it->second;
+ if (auto iter = fallBack->find(tns->first); iter != fallBack->end()) {
+ remoteIPs = iter->second;
}
}
- if (remoteIPs.size() == 0) {
+ if (remoteIPs.empty()) {
remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly, addressQueriesForNS);
}
flawedNSSet = true;
continue;
}
- else {
- bool hitPolicy{false};
- LOG(prefix << qname << ": Resolved '" << auth << "' NS " << tns->first << " to: ");
- for (remoteIP = remoteIPs.begin(); remoteIP != remoteIPs.end(); ++remoteIP) {
- if (remoteIP != remoteIPs.begin()) {
- LOG(", ");
- }
- LOG(remoteIP->toString());
- if (nameserverIPBlockedByRPZ(luaconfsLocal->dfe, *remoteIP)) {
- hitPolicy = true;
- }
+ bool hitPolicy{false};
+ LOG(prefix << qname << ": Resolved '" << auth << "' NS " << tns->first << " to: ");
+ for (remoteIP = remoteIPs.begin(); remoteIP != remoteIPs.end(); ++remoteIP) {
+ if (remoteIP != remoteIPs.begin()) {
+ LOG(", ");
}
- LOG(endl);
- if (hitPolicy) { // implies d_wantsRPZ
- /* RPZ hit */
- if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
- /* reset to no match */
- d_appliedPolicy = DNSFilterEngine::Policy();
- }
- else {
- throw PolicyHitException();
- }
+ LOG(remoteIP->toString());
+ if (nameserverIPBlockedByRPZ(luaconfsLocal->dfe, *remoteIP)) {
+ hitPolicy = true;
+ }
+ }
+ LOG(endl);
+ if (hitPolicy) { // implies d_wantsRPZ
+ /* RPZ hit */
+ if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+ /* reset to no match */
+ d_appliedPolicy = DNSFilterEngine::Policy();
+ }
+ else {
+ throw PolicyHitException();
}
}
*/
// cout<<"ms: "<<lwr.d_usec/1000.0<<", "<<g_avgLatency/1000.0<<'\n';
- s_nsSpeeds.lock()->find_or_enter(tns->first.empty() ? DNSName(remoteIP->toStringWithPort()) : tns->first, d_now).submit(*remoteIP, lwr.d_usec, d_now);
+ s_nsSpeeds.lock()->find_or_enter(tns->first.empty() ? DNSName(remoteIP->toStringWithPort()) : tns->first, d_now).submit(*remoteIP, static_cast<int>(lwr.d_usec), d_now);
/* we have received an answer, are we done ? */
bool done = processAnswer(depth, prefix, lwr, qname, qtype, auth, wasForwarded, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, context.state, *remoteIP);
return rcode;
}
if (gotNewServers) {
- if (stopAtDelegation && *stopAtDelegation == Stop) {
+ if (stopAtDelegation != nullptr && *stopAtDelegation == Stop) {
*stopAtDelegation = Stopped;
return rcode;
}
break;
}
/* was lame */
- doThrottle(d_now.tv_sec, *remoteIP, qname, qtype, 60, 100);
+ if (!shouldNotThrottle(&tns->first, &*remoteIP)) {
+ doThrottle(d_now.tv_sec, *remoteIP, qname, qtype, 60, 100);
+ }
}
if (gotNewServers) {
break;
}
- if (remoteIP == remoteIPs.cend()) // we tried all IP addresses, none worked
+ if (remoteIP == remoteIPs.cend()) { // we tried all IP addresses, none worked
continue;
+ }
}
}
}
}
}
-void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS)
+void SyncRes::setQuerySource(const ComboAddress& requestor, const boost::optional<const EDNSSubnetOpts&>& incomingECS)
{
d_requestor = requestor;
}
}
-boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const DNSName& dn, const ComboAddress& rem)
+boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const DNSName& name, const ComboAddress& rem)
{
- if (d_outgoingECSNetwork && (s_ednsdomains.check(dn) || s_ednsremotesubnets.match(rem))) {
+ if (d_outgoingECSNetwork && (s_ednsdomains.check(name) || s_ednsremotesubnets.match(rem))) {
return d_outgoingECSNetwork;
}
return boost::none;
{
vector<string> parts;
stringtok(parts, alist, ",; ");
- for (const auto& a : parts) {
+ for (const auto& allow : parts) {
try {
- s_ednsremotesubnets.addMask(Netmask(a));
+ s_ednsremotesubnets.addMask(Netmask(allow));
}
catch (...) {
- s_ednsdomains.add(DNSName(a));
+ s_ednsdomains.add(DNSName(allow));
}
}
}
{
vector<string> parts;
stringtok(parts, subnetlist, ",; ");
- for (const auto& a : parts) {
- s_ednslocalsubnets.addMask(a);
+ for (const auto& allow : parts) {
+ s_ednslocalsubnets.addMask(allow);
}
}
// used by PowerDNSLua - note that this neglects to add the packet count & statistics back to pdns_recursor.cc
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, Logr::log_t log)
+int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, Logr::log_t log)
{
return directResolve(qname, qtype, qclass, ret, pdl, SyncRes::s_qnameminimization, log);
}
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, bool qm, Logr::log_t slog)
+int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, bool qnamemin, Logr::log_t slog)
{
auto log = slog->withValues("qname", Logging::Loggable(qname), "qtype", Logging::Loggable(qtype));
- struct timeval now;
- gettimeofday(&now, 0);
+ struct timeval now
+ {
+ };
+ gettimeofday(&now, nullptr);
- SyncRes sr(now);
- sr.setQNameMinimization(qm);
+ SyncRes resolver(now);
+ resolver.setQNameMinimization(qnamemin);
if (pdl) {
- sr.setLuaEngine(pdl);
+ resolver.setLuaEngine(pdl);
}
int res = -1;
const std::string msg = "Exception while resolving";
try {
- res = sr.beginResolve(qname, qtype, qclass, ret, 0);
+ res = resolver.beginResolve(qname, qtype, qclass, ret, 0);
}
catch (const PDNSException& e) {
- SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got pdns exception: " << e.reason << endl,
- log->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
+ SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got pdns exception: " << e.reason << endl,
+ log->error(Logr::Warning, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
ret.clear();
}
catch (const ImmediateServFailException& e) {
- SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got ImmediateServFailException: " << e.reason << endl,
- log->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("ImmediateServFailException")));
+ SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got ImmediateServFailException: " << e.reason << endl,
+ log->error(Logr::Warning, e.reason, msg, "exception", Logging::Loggable("ImmediateServFailException")));
ret.clear();
}
catch (const PolicyHitException& e) {
- SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got a policy hit" << endl,
- log->info(Logr::Error, msg, "exception", Logging::Loggable("PolicyHitException")));
+ SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got a policy hit" << endl,
+ log->info(Logr::Warning, msg, "exception", Logging::Loggable("PolicyHitException")));
ret.clear();
}
catch (const std::exception& e) {
- SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got STL error: " << e.what() << endl,
- log->error(Logr::Error, e.what(), msg, "exception", Logging::Loggable("std::exception")));
+ SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got STL error: " << e.what() << endl,
+ log->error(Logr::Warning, e.what(), msg, "exception", Logging::Loggable("std::exception")));
ret.clear();
}
catch (...) {
- SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got an exception" << endl,
- log->info(Logr::Error, msg));
+ SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got an exception" << endl,
+ log->info(Logr::Warning, msg));
ret.clear();
}
int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth, Logr::log_t log)
{
- SyncRes sr(now);
- sr.d_prefix = "[getRootNS]";
- sr.setDoEDNS0(true);
- sr.setUpdatingRootNS();
- sr.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
- sr.setDNSSECValidationRequested(g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate);
- sr.setAsyncCallback(asyncCallback);
- sr.setRefreshAlmostExpired(true);
+ if (::arg()["hint-file"] == "no-refresh") {
+ return 0;
+ }
+ SyncRes resolver(now);
+ resolver.d_prefix = "[getRootNS]";
+ resolver.setDoEDNS0(true);
+ resolver.setUpdatingRootNS();
+ resolver.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
+ resolver.setDNSSECValidationRequested(g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate);
+ resolver.setAsyncCallback(std::move(asyncCallback));
+ resolver.setRefreshAlmostExpired(true);
const string msg = "Failed to update . records";
vector<DNSRecord> ret;
int res = -1;
try {
- res = sr.beginResolve(g_rootdnsname, QType::NS, 1, ret, depth + 1);
+ res = resolver.beginResolve(g_rootdnsname, QType::NS, 1, ret, depth + 1);
if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
- auto state = sr.getValidationState();
+ auto state = resolver.getValidationState();
if (vStateIsBogus(state)) {
throw PDNSException("Got Bogus validation result for .|NS");
}
}
return res;
}
+
+bool SyncRes::answerIsNOData(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records)
+{
+ if (rcode != RCode::NoError) {
+ return false;
+ }
+
+ // NOLINTNEXTLINE(readability-use-anyofallof)
+ for (const auto& rec : records) {
+ if (rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == requestedType) {
+ /* we have a record, of the right type, in the right section */
+ return false;
+ }
+ }
+ return true;
+#if 0
+ // This code should be equivalent to the code above, clang-tidy prefers any_of()
+ // I have doubts if that is easier to read
+ return !std::any_of(records.begin(), records.end(), [=](const DNSRecord& rec) {
+ return rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == requestedType;
+ });
+#endif
+}
#include "circular_buffer.hh"
#include "sstuff.hh"
#include "recursor_cache.hh"
-#include <boost/optional.hpp>
#include "mtasker.hh"
#include "iputils.hh"
#include "validate-recursor.hh"
class RecursorLua4;
-typedef std::unordered_map<
- DNSName,
- pair<
- vector<ComboAddress>,
- bool>>
- NsSet;
+using NsSet = std::unordered_map<DNSName, pair<vector<ComboAddress>, bool>>;
extern std::unique_ptr<NegCache> g_negCache;
Log,
Store
};
- typedef std::function<LWResult::Result(const ComboAddress& ip, const DNSName& qdomain, int qtype, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* lwr, bool* chained)> asyncresolve_t;
+ using asyncresolve_t = std::function<LWResult::Result(const ComboAddress&, const DNSName&, int, bool, bool, int, struct timeval*, boost::optional<Netmask>&, const ResolveContext&, LWResult*, bool*)>;
enum class HardenNXD
{
vState state{vState::Indeterminate};
};
- vState getDSRecords(const DNSName& zone, dsmap_t& ds, bool onlyTA, unsigned int depth, const string& prefix, bool bogusOnNXD = true, bool* foundCut = nullptr);
+ vState getDSRecords(const DNSName& zone, dsmap_t& dsMap, bool onlyTA, unsigned int depth, const string& prefix, bool bogusOnNXD = true, bool* foundCut = nullptr);
class AuthDomain
{
public:
- typedef multi_index_container<
+ using records_t = multi_index_container<
DNSRecord,
indexed_by<
ordered_non_unique<
composite_key<DNSRecord,
member<DNSRecord, DNSName, &DNSRecord::d_name>,
member<DNSRecord, uint16_t, &DNSRecord::d_type>>,
- composite_key_compare<std::less<DNSName>, std::less<uint16_t>>>>>
- records_t;
+ composite_key_compare<std::less<>, std::less<>>>>>;
records_t d_records;
vector<ComboAddress> d_servers;
const std::string& indentLevel = " ") const;
int getRecords(const DNSName& qname, QType qtype, std::vector<DNSRecord>& records) const;
- bool isAuth() const
+ [[nodiscard]] bool isAuth() const
{
return d_servers.empty();
}
- bool isForward() const
+ [[nodiscard]] bool isForward() const
{
return !isAuth();
}
- bool shouldRecurse() const
+ [[nodiscard]] bool shouldRecurse() const
{
return d_rdForward;
}
- const DNSName& getName() const
+ [[nodiscard]] const DNSName& getName() const
{
return d_name;
}
void addSOA(std::vector<DNSRecord>& records) const;
};
- typedef std::unordered_map<DNSName, AuthDomain> domainmap_t;
+ using domainmap_t = std::unordered_map<DNSName, AuthDomain>;
struct ThreadLocalStorage
{
std::shared_ptr<domainmap_t> domainmap;
};
- static void setDefaultLogMode(LogMode lm)
+ static void setDefaultLogMode(LogMode logmode)
{
- s_lm = lm;
+ s_lm = logmode;
}
OptLog LogObject(const string& prefix);
- static uint64_t doEDNSDump(int fd);
- static uint64_t doDumpNSSpeeds(int fd);
- static uint64_t doDumpThrottleMap(int fd);
- static uint64_t doDumpFailedServers(int fd);
- static uint64_t doDumpNonResolvingNS(int fd);
- static uint64_t doDumpSavedParentNSSets(int fd);
- static uint64_t doDumpDoTProbeMap(int fd);
+ static uint64_t doEDNSDump(int fileDesc);
+ static uint64_t doDumpNSSpeeds(int fileDesc);
+ static uint64_t doDumpThrottleMap(int fileDesc);
+ static uint64_t doDumpFailedServers(int fileDesc);
+ static uint64_t doDumpNonResolvingNS(int fileDesc);
+ static uint64_t doDumpSavedParentNSSets(int fileDesc);
+ static uint64_t doDumpDoTProbeMap(int fileDesc);
static int getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth, Logr::log_t);
static void addDontQuery(const std::string& mask)
{
- if (!s_dontQuery)
+ if (!s_dontQuery) {
s_dontQuery = std::make_unique<NetmaskGroup>();
-
+ }
s_dontQuery->addMask(mask);
}
static void addDontQuery(const Netmask& mask)
{
- if (!s_dontQuery)
+ if (!s_dontQuery) {
s_dontQuery = std::make_unique<NetmaskGroup>();
-
+ }
s_dontQuery->addMask(mask);
}
static void clearDontQuery()
static void pruneNSSpeeds(time_t limit);
static uint64_t getNSSpeedsSize();
- static void submitNSSpeed(const DNSName& server, const ComboAddress& ca, uint32_t usec, const struct timeval& now);
+ static void submitNSSpeed(const DNSName& server, const ComboAddress& address, int usec, const struct timeval& now);
static void clearNSSpeeds();
- static float getNSSpeed(const DNSName& server, const ComboAddress& ca);
+ static float getNSSpeed(const DNSName& server, const ComboAddress& address);
struct EDNSStatus
{
NOEDNS = 2
} mode{EDNSOK};
- std::string toString() const
+ [[nodiscard]] std::string toString() const
{
const std::array<std::string, 3> modes = {"OK", "Ignorant", "No"};
- unsigned int m = static_cast<unsigned int>(mode);
- if (m >= modes.size()) {
+ auto umode = static_cast<unsigned int>(mode);
+ if (umode >= modes.size()) {
return "?";
}
- return modes.at(m);
+ return modes.at(umode);
}
};
static bool isThrottled(time_t now, const ComboAddress& server);
static void doThrottle(time_t now, const ComboAddress& server, time_t duration, unsigned int tries);
static void doThrottle(time_t now, const ComboAddress& server, const DNSName& name, QType qtype, time_t duration, unsigned int tries);
+ static void unThrottle(const ComboAddress& server, const DNSName& qname, QType qtype);
static uint64_t getFailedServersSize();
static void clearFailedServers();
static void setDomainMap(std::shared_ptr<domainmap_t> newMap)
{
- t_sstorage.domainmap = newMap;
+ t_sstorage.domainmap = std::move(newMap);
}
- static const std::shared_ptr<domainmap_t> getDomainMap()
+ static std::shared_ptr<domainmap_t> getDomainMap()
{
return t_sstorage.domainmap;
}
}
}
- void setLogMode(LogMode lm)
+ void setLogMode(LogMode logmode)
{
- d_lm = lm;
+ d_lm = logmode;
}
bool doLog() const
void setLuaEngine(shared_ptr<RecursorLua4> pdl)
{
- d_pdl = pdl;
+ d_pdl = std::move(pdl);
}
bool wasVariable() const
}
// For debugging purposes
- void setNow(const struct timeval& tv)
+ void setNow(const struct timeval& tval)
{
- d_now = tv;
+ d_now = tval;
}
- void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS);
+ void setQuerySource(const ComboAddress& requestor, const boost::optional<const EDNSSubnetOpts&>& incomingECS);
void setQuerySource(const Netmask& netmask);
- void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
+ void setInitialRequestId(const boost::optional<const boost::uuids::uuid&>& initialRequestId)
{
d_initialRequestId = initialRequestId;
}
void setAsyncCallback(asyncresolve_t func)
{
- d_asyncResolve = func;
+ d_asyncResolve = std::move(func);
}
vState getValidationState() const
return d_queryValidationState;
}
+ [[nodiscard]] bool getDNSSECLimitHit() const
+ {
+ return d_validationContext.d_limitHit;
+ }
+
void setQueryReceivedOverTCP(bool tcp)
{
d_queryReceivedOverTCP = tcp;
return false;
}
+ static bool answerIsNOData(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records);
+
static thread_local ThreadLocalStorage t_sstorage;
static pdns::stat_t s_ecsqueries;
static unsigned int s_serverdownthrottletime;
static unsigned int s_nonresolvingnsmaxfails;
static unsigned int s_nonresolvingnsthrottletime;
-
+ static unsigned int s_unthrottle_n;
static unsigned int s_ecscachelimitttl;
+ static unsigned int s_maxvalidationsperq;
+ static unsigned int s_maxnsec3iterationsperq;
static uint8_t s_ecsipv4limit;
static uint8_t s_ecsipv6limit;
static uint8_t s_ecsipv4cachelimit;
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 unsigned int s_max_minimize_count;
+ static unsigned int s_minimize_one_label;
static const int event_trace_to_pb = 1;
static const int event_trace_to_log = 2;
unsigned int d_timeouts;
unsigned int d_unreachables;
unsigned int d_totUsec;
+ unsigned int d_maxdepth{0};
// Initialized ony once, as opposed to d_now which gets updated after outgoing requests
- const struct timeval d_fixednow;
+ struct timeval d_fixednow;
private:
ComboAddress d_requestor;
DNSName qname;
set<pair<DNSName, DNSName>> bestns;
uint8_t qtype;
- bool operator<(const GetBestNSAnswer& b) const
+ bool operator<(const GetBestNSAnswer& bestAnswer) const
{
- return std::tie(qtype, qname, bestns) < std::tie(b.qtype, b.qname, b.bestns);
+ return std::tie(qtype, qname, bestns) < std::tie(bestAnswer.qtype, bestAnswer.qname, bestAnswer.bestns);
}
};
- typedef std::map<DNSName, vState> zonesStates_t;
+ using zonesStates_t = std::map<DNSName, vState>;
enum StopAtDelegation
{
DontStop,
Stopped
};
- void resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMode, std::vector<DNSRecord>& additionals, unsigned int depth, bool& pushed);
- void addAdditionals(QType qtype, const vector<DNSRecord>& start, vector<DNSRecord>& addditionals, std::set<std::pair<DNSName, QType>>& uniqueCalls, std::set<std::tuple<DNSName, QType, QType>>& uniqueResults, unsigned int depth, unsigned int adddepth, bool& pushed);
+ void resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMode mode, std::vector<DNSRecord>& additionals, unsigned int depth, bool& additionalsNotInCache);
+ void addAdditionals(QType qtype, const vector<DNSRecord>& start, vector<DNSRecord>& additionals, std::set<std::pair<DNSName, QType>>& uniqueCalls, std::set<std::tuple<DNSName, QType, QType>>& uniqueResults, unsigned int depth, unsigned int additionaldepth, bool& additionalsNotInCache);
bool addAdditionals(QType qtype, vector<DNSRecord>& ret, unsigned int depth);
- bool doDoTtoAuth(const DNSName& ns) const;
+ void updateQueryCounts(const string& prefix, const DNSName& qname, const ComboAddress& address, bool doTCP, bool doDoT);
+ static bool doDoTtoAuth(const DNSName& nameServer);
int doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, const DNSName& qname, QType qtype, vector<DNSRecord>& ret,
unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, Context& context, StopAtDelegation* stopAtDelegation,
std::map<DNSName, std::vector<ComboAddress>>* fallback);
- bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle = false);
- bool processAnswer(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet& nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state, const ComboAddress& remoteIP);
+ void ednsStats(boost::optional<Netmask>& ednsmask, const DNSName& qname, const string& prefix);
+ bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool sendRDQuery, bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle = false);
+ bool processAnswer(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask>& ednsmask, bool sendRDQuery, NsSet& nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state, const ComboAddress& remoteIP);
int doResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context);
- int doResolveNoQNameMinimization(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL);
+ int doResolveNoQNameMinimization(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context, bool* fromCache = nullptr, StopAtDelegation* stopAtDelegation = nullptr);
bool doOOBResolve(const AuthDomain& domain, const DNSName& qname, QType qtype, vector<DNSRecord>& ret, int& res);
bool doOOBResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res);
- bool isRecursiveForwardOrAuth(const DNSName& qname) const;
- bool isForwardOrAuth(const DNSName& qname) const;
- domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
- bool doCNAMECacheCheck(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse);
+ static bool isRecursiveForwardOrAuth(const DNSName& qname);
+ static bool isForwardOrAuth(const DNSName& qname);
+ static domainmap_t::const_iterator getBestAuthZone(DNSName* qname);
+ bool doCNAMECacheCheck(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse, bool checkForDups);
bool doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context);
void getBestNSFromCache(const DNSName& qname, QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain = boost::none);
DNSName getBestNSNamesFromCache(const DNSName& qname, QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere);
vector<std::pair<DNSName, float>> shuffleInSpeedOrder(const DNSName& qname, NsSet& nameservers, const string& prefix);
- vector<ComboAddress> shuffleForwardSpeed(const DNSName& qname, const vector<ComboAddress>& rnameservers, const string& prefix, const bool wasRd);
- bool moreSpecificThan(const DNSName& a, const DNSName& b) const;
+ vector<ComboAddress> shuffleForwardSpeed(const DNSName& qname, const vector<ComboAddress>& rnameservers, const string& prefix, bool wasRd);
+ static bool moreSpecificThan(const DNSName& lhs, const DNSName& rhs);
+ void selectNSOnSpeed(const DNSName& qname, const string& prefix, vector<ComboAddress>& ret);
vector<ComboAddress> getAddrs(const DNSName& qname, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS);
bool nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers);
void checkMaxQperQ(const DNSName& qname) const;
bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, QType qtype, bool pierceDontQuery);
- vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<std::pair<DNSName, float>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly, unsigned int& addressQueriesForNS);
+ vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<std::pair<DNSName, float>>::const_iterator& tns, unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly, unsigned int& nretrieveAddressesForNS);
- void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
+ void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
/* This function will check whether the answer should have the AA bit set, and will set if it should be set and isn't.
This is unfortunately needed to deal with very crappy so-called DNS servers */
- void fixupAnswer(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
+ void fixupAnswer(const std::string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
void rememberParentSetIfNeeded(const DNSName& domain, const vector<DNSRecord>& newRecords, unsigned int depth, const string& prefix);
- RCode::rcodes_ updateCacheFromRecords(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool sendRDQuery, const ComboAddress& remoteIP);
- bool processRecords(const std::string& prefix, const DNSName& qname, const QType qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const bool gatherwildcardProof, const unsigned int wildcardLabelsCount, int& rcode, bool& negIndicHasSignatures, unsigned int depth);
+ RCode::rcodes_ updateCacheFromRecords(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>&, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool sendRDQuery, const ComboAddress& remoteIP);
+ bool processRecords(const std::string& prefix, const DNSName& qname, QType qtype, const DNSName& auth, LWResult& lwr, bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, bool needWildcardProof, bool gatherwildcardProof, unsigned int wildcardLabelsCount, int& rcode, bool& negIndicHasSignatures, unsigned int depth);
- bool doSpecialNamesResolve(const DNSName& qname, QType qtype, const QClass qclass, vector<DNSRecord>& ret);
+ bool doSpecialNamesResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret);
- LWResult::Result asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const;
+ LWResult::Result asyncresolveWrapper(const ComboAddress& address, bool ednsMANDATORY, const DNSName& domain, const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const;
- boost::optional<Netmask> getEDNSSubnetMask(const DNSName& dn, const ComboAddress& rem);
+ boost::optional<Netmask> getEDNSSubnetMask(const DNSName& name, const ComboAddress& rem);
- bool validationEnabled() const;
+ static bool validationEnabled();
uint32_t computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, uint32_t signaturesTTL, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs) const;
- void updateValidationState(const DNSName& qname, vState& state, const vState stateUpdate, const string& prefix);
- vState validateRecordsWithSigs(unsigned int depth, const string& prefix, const DNSName& qname, const QType qtype, const DNSName& name, const QType type, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures);
+ void updateValidationState(const DNSName& qname, vState& state, vState stateUpdate, const string& prefix);
+ vState validateRecordsWithSigs(unsigned int depth, const string& prefix, const DNSName& qname, QType qtype, const DNSName& name, QType type, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures);
vState validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, unsigned int depth, const string& prefix);
vState getDNSKeys(const DNSName& signer, skeyset_t& keys, bool& servFailOccurred, unsigned int depth, const string& prefix);
- dState getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned, const string& prefix);
- void updateDenialValidationState(const DNSName& qname, vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool isDS, unsigned int depth, const string& prefix);
- void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, QType qtype, const int res, vState& state, unsigned int depth, const string& prefix);
- vState getTA(const DNSName& zone, dsmap_t& ds, const string& prefix);
- vState getValidationStatus(const DNSName& subdomain, bool wouldBeValid, bool typeIsDS, unsigned int depth, const string& prefix);
- void updateValidationStatusInCache(const DNSName& qname, QType qt, bool aa, vState newState) const;
+ dState getDenialValidationState(const NegCache::NegCacheEntry& negEntry, dState expectedState, bool referralToUnsigned, const string& prefix);
+ void updateDenialValidationState(const DNSName& qname, vState& neValidationState, const DNSName& neName, vState& state, dState denialState, dState expectedState, bool isDS, unsigned int depth, const string& prefix);
+ void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& negEntry, const DNSName& qname, QType qtype, int res, vState& state, unsigned int depth, const string& prefix);
+ vState getTA(const DNSName& zone, dsmap_t& dsMap, const string& prefix);
+ vState getValidationStatus(const DNSName& name, bool wouldBeValid, bool typeIsDS, unsigned int depth, const string& prefix);
+ void updateValidationStatusInCache(const DNSName& qname, QType qtype, bool aaFlag, vState newState) const;
void initZoneCutsFromTA(const DNSName& from, const string& prefix);
size_t countSupportedDS(const dsmap_t& dsmap, const string& prefix);
std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> d_outgoingProtobufServers;
std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> d_frameStreamServers;
boost::optional<const boost::uuids::uuid&> d_initialRequestId;
+ pdns::validation::ValidationContext d_validationContext;
asyncresolve_t d_asyncResolve{nullptr};
// d_now is initialized in the constructor and updates after outgoing requests in lwres.cc:asyncresolve
struct timeval d_now;
PacketBuffer inMSG; // they'll go here
PacketBuffer outMSG; // the outgoing message that needs to be sent
- typedef set<uint16_t> chain_t;
+ using chain_t = set<uint16_t>;
mutable chain_t chain;
shared_ptr<TCPIOHandler> tcphandler{nullptr};
string::size_type inPos{0}; // how far are we along in the inMSG
bool operator<(const PacketID& /* b */) const
{
// We don't want explicit PacketID compare here, but always via predicate classes below
- assert(0);
+ assert(0); // NOLINT: lib
}
};
-inline ostream& operator<<(ostream& os, const PacketID& pid)
+inline ostream& operator<<(ostream& ostr, const PacketID& pid)
{
- return os << "PacketID(id=" << pid.id << ",remote=" << pid.remote.toString() << ",type=" << pid.type << ",tcpsock=" << pid.tcpsock << ",fd=" << pid.fd << ',' << pid.domain << ')';
+ return ostr << "PacketID(id=" << pid.id << ",remote=" << pid.remote.toString() << ",type=" << pid.type << ",tcpsock=" << pid.tcpsock << ",fd=" << pid.fd << ',' << pid.domain << ')';
}
-inline ostream& operator<<(ostream& os, const shared_ptr<PacketID>& pid)
+inline ostream& operator<<(ostream& ostr, const shared_ptr<PacketID>& pid)
{
- return os << *pid;
+ return ostr << *pid;
}
/*
*/
struct PacketIDCompare
{
- bool operator()(const std::shared_ptr<PacketID>& a, const std::shared_ptr<PacketID>& b) const
+ bool operator()(const std::shared_ptr<PacketID>& lhs, const std::shared_ptr<PacketID>& rhs) const
{
- if (std::tie(a->remote, a->tcpsock, a->type) < std::tie(b->remote, b->tcpsock, b->type)) {
+ if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) < std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
return true;
}
- if (std::tie(a->remote, a->tcpsock, a->type) > std::tie(b->remote, b->tcpsock, b->type)) {
+ if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) > std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
return false;
}
- return std::tie(a->domain, a->fd, a->id) < std::tie(b->domain, b->fd, b->id);
+ return std::tie(lhs->domain, lhs->fd, lhs->id) < std::tie(rhs->domain, rhs->fd, rhs->id);
}
};
struct PacketIDBirthdayCompare
{
- bool operator()(const std::shared_ptr<PacketID>& a, const std::shared_ptr<PacketID>& b) const
+ bool operator()(const std::shared_ptr<PacketID>& lhs, const std::shared_ptr<PacketID>& rhs) const
{
- if (std::tie(a->remote, a->tcpsock, a->type) < std::tie(b->remote, b->tcpsock, b->type)) {
+ if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) < std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
return true;
}
- if (std::tie(a->remote, a->tcpsock, a->type) > std::tie(b->remote, b->tcpsock, b->type)) {
+ if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) > std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
return false;
}
- return a->domain < b->domain;
+ return lhs->domain < rhs->domain;
}
};
extern std::unique_ptr<MemRecursorCache> g_recCache;
extern thread_local rec::TCounters t_Counters;
//! represents a running TCP/IP client session
-class TCPConnection : public boost::noncopyable
+class TCPConnection
{
public:
- TCPConnection(int fd, const ComboAddress& addr);
+ TCPConnection(int fileDesc, const ComboAddress& addr);
~TCPConnection();
+ TCPConnection(const TCPConnection&) = delete;
+ TCPConnection& operator=(const TCPConnection&) = delete;
+ TCPConnection(TCPConnection&&) = delete;
+ TCPConnection& operator=(TCPConnection&&) = delete;
- int getFD() const
+ [[nodiscard]] int getFD() const
{
return d_fd;
}
{
d_dropOnIdle = true;
}
- bool isDropOnIdle() const
+ [[nodiscard]] bool isDropOnIdle() const
{
return d_dropOnIdle;
}
+
+ // The max number of concurrent TCP requests we're willing to process
+ static uint16_t s_maxInFlight;
+ static unsigned int getCurrentConnections() { return s_currentConnections; }
+
std::vector<ProxyProtocolValue> proxyProtocolValues;
std::string data;
- const ComboAddress d_remote;
+ ComboAddress d_remote;
ComboAddress d_source;
ComboAddress d_destination;
ComboAddress d_mappedSource;
uint16_t qlen{0};
uint16_t bytesread{0};
uint16_t d_requestsInFlight{0}; // number of mthreads spawned for this connection
- // The max number of concurrent TCP requests we're willing to process
- static uint16_t s_maxInFlight;
- static unsigned int getCurrentConnections() { return s_currentConnections; }
private:
- const int d_fd;
+ int d_fd;
static std::atomic<uint32_t> s_currentConnections; //!< total number of current TCP connections
bool d_dropOnIdle{false};
};
class ImmediateServFailException
{
public:
- ImmediateServFailException(string r) :
- reason(r){};
+ ImmediateServFailException(string reason_) :
+ reason(std::move(reason_)){};
string reason; //! Print this to tell the user what went wrong
};
{
};
-typedef boost::circular_buffer<ComboAddress> addrringbuf_t;
+using addrringbuf_t = boost::circular_buffer<ComboAddress>;
extern thread_local std::unique_ptr<addrringbuf_t> t_servfailremotes, t_largeanswerremotes, t_remotes, t_bogusremotes, t_timeouts;
extern thread_local std::unique_ptr<boost::circular_buffer<pair<DNSName, uint16_t>>> t_queryring, t_servfailqueryring, t_bogusqueryring;
extern std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries;
extern bool g_lowercaseOutgoing;
-std::string reloadZoneConfiguration();
-typedef std::function<void*(void)> pipefunc_t;
+std::string reloadZoneConfiguration(bool yaml);
+using pipefunc_t = std::function<void*()>;
void broadcastFunction(const pipefunc_t& func);
-void distributeAsyncFunction(const std::string& question, const pipefunc_t& func);
+void distributeAsyncFunction(const std::string& packet, const pipefunc_t& func);
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, Logr::log_t);
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, bool qm, Logr::log_t);
-int followCNAMERecords(std::vector<DNSRecord>& ret, const QType qtype, int oldret);
+int directResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, Logr::log_t);
+int directResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, bool qnamemin, Logr::log_t);
+int followCNAMERecords(std::vector<DNSRecord>& ret, QType qtype, int rcode);
int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret);
int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret);
template <class T>
T broadcastAccFunction(const std::function<T*()>& func);
-typedef std::unordered_set<DNSName> notifyset_t;
-std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration();
-void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> ns);
+using notifyset_t = std::unordered_set<DNSName>;
+std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration(bool yaml);
+void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> allowNotifyFor);
uint64_t* pleaseGetNsSpeedsSize();
uint64_t* pleaseGetFailedServersSize();
uint64_t* pleaseGetThrottleSize();
void doCarbonDump(void*);
bool primeHints(time_t now = time(nullptr));
-const char* isoDateTimeMillis(const struct timeval& tv, char* buf, size_t sz);
+
+using timebuf_t = std::array<char, 64>;
+const char* isoDateTimeMillis(const struct timeval& tval, timebuf_t& buf);
struct WipeCacheResult
{
return ret;
}
-bool ResolveTask::run(bool logErrors)
+bool ResolveTask::run(bool logErrors) const
{
if (d_func == nullptr) {
auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(d_qname), "qtype", Logging::Loggable(QType(d_qtype).toString()));
log->error(Logr::Debug, "null task");
return false;
}
- struct timeval now;
+ struct timeval now
+ {
+ };
Utility::gettimeofday(&now);
if (d_deadline >= now.tv_sec) {
d_func(now, logErrors, *this);
namespace boost
{
-size_t hash_value(const ComboAddress& a)
+size_t hash_value(const ComboAddress& address)
{
- return ComboAddress::addressOnlyHash()(a);
+ return ComboAddress::addressOnlyHash()(address);
}
}
DNSName d_nsname;
Netmask d_netmask;
- bool operator<(const ResolveTask& a) const
+ bool operator<(const ResolveTask& task) const
{
- return std::tie(d_qname, d_qtype, d_refreshMode, d_func, d_ip, d_netmask) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func, a.d_ip, a.d_netmask);
+ return std::tie(d_qname, d_qtype, d_refreshMode, d_func, d_ip, d_netmask) < std::tie(task.d_qname, task.d_qtype, task.d_refreshMode, task.d_func, task.d_ip, task.d_netmask);
}
- bool run(bool logErrors);
+ [[nodiscard]] bool run(bool logErrors) const;
};
class TaskQueue
{
public:
- bool empty() const
+ [[nodiscard]] bool empty() const
{
return d_queue.empty();
}
- size_t size() const
+ [[nodiscard]] size_t size() const
{
return d_queue.size();
}
bool push(ResolveTask&& task);
ResolveTask pop();
- uint64_t getPushes()
+ [[nodiscard]] uint64_t getPushes() const
{
return d_pushes;
}
- uint64_t getExpired()
+ [[nodiscard]] uint64_t getExpired() const
{
return d_expired;
}
{
};
- typedef multi_index_container<
+ using queue_t = multi_index_container<
ResolveTask,
- indexed_by<
- ordered_unique<tag<HashTag>,
- composite_key<ResolveTask,
- member<ResolveTask, DNSName, &ResolveTask::d_qname>,
- member<ResolveTask, uint16_t, &ResolveTask::d_qtype>,
- member<ResolveTask, bool, &ResolveTask::d_refreshMode>,
- member<ResolveTask, ResolveTask::TaskFunction, &ResolveTask::d_func>,
- member<ResolveTask, ComboAddress, &ResolveTask::d_ip>,
- member<ResolveTask, Netmask, &ResolveTask::d_netmask>>>,
- sequenced<tag<SequencedTag>>>>
- queue_t;
+ indexed_by<ordered_unique<tag<HashTag>,
+ composite_key<ResolveTask,
+ member<ResolveTask, DNSName, &ResolveTask::d_qname>,
+ member<ResolveTask, uint16_t, &ResolveTask::d_qtype>,
+ member<ResolveTask, bool, &ResolveTask::d_refreshMode>,
+ member<ResolveTask, ResolveTask::TaskFunction, &ResolveTask::d_func>,
+ member<ResolveTask, ComboAddress, &ResolveTask::d_ip>,
+ member<ResolveTask, Netmask, &ResolveTask::d_netmask>>>,
+ sequenced<tag<SequencedTag>>>>;
queue_t d_queue;
uint64_t d_pushes{0};
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "aggressive_nsec.hh"
{"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "7ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 2, false},
{"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "fujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, false},
{"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, false},
+ {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 0, false},
+ {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 1, false},
+ {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 157, false},
};
for (const auto& [owner, next, boundary, result] : table) {
BOOST_CHECK_EQUAL(queriesCount, 5U);
}
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_replace)
+{
+ const size_t testSize = 10000;
+ auto cache = make_unique<AggressiveNSECCache>(testSize);
+
+ struct timeval now
+ {
+ };
+ Utility::gettimeofday(&now, nullptr);
+
+ vector<DNSName> names;
+ names.reserve(testSize);
+ for (size_t i = 0; i < testSize; i++) {
+ names.emplace_back(std::to_string(i) + "powerdns.com");
+ }
+
+ DTime time;
+ time.set();
+
+ for (const auto& name : names) {
+ DNSRecord rec;
+ rec.d_name = name;
+ rec.d_type = QType::NSEC3;
+ rec.d_ttl = now.tv_sec + 10;
+ rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3"));
+ auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+ cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, true);
+ }
+ auto diff1 = time.udiff(true);
+
+ BOOST_CHECK_EQUAL(cache->getEntriesCount(), testSize);
+ for (const auto& name : names) {
+ DNSRecord rec;
+ rec.d_name = name;
+ rec.d_type = QType::NSEC3;
+ rec.d_ttl = now.tv_sec + 10;
+ rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3"));
+ auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+ cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, true);
+ }
+
+ BOOST_CHECK_EQUAL(cache->getEntriesCount(), testSize);
+
+ auto diff2 = time.udiff(true);
+ // Check that replace is about equally fast as insert
+ BOOST_CHECK(diff1 < diff2 * 2 && diff2 < diff1 * 2);
+}
+
BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wiping)
{
auto cache = make_unique<AggressiveNSECCache>(10000);
rec.d_name = DNSName("www.powerdns.org");
rec.d_type = QType::NSEC3;
- rec.d_ttl = now.tv_sec + 10;
+ rec.d_ttl = now.tv_sec + 20;
rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3"));
rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
/* we have set a upper bound to 2 entries, so we are above,
- and all entries are actually expired, so we will prune one entry
+ and one entry is actually expired, so we will prune one entry
to get below the limit */
- cache->prune(now.tv_sec + 600);
+ cache->prune(now.tv_sec + 15);
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
- /* now we are at the limit, so we will scan 1/5th of the entries,
- and prune the expired ones, which mean we will also remove only one */
- cache->prune(now.tv_sec + 600);
- BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
-
- /* now we are below the limit, so we will scan 1/5th of the entries again,
- and prune the expired ones, which mean we will remove the last one */
+ /* now we are at the limit, so we will scan 1/10th of all zones entries, rounded up,
+ and prune the expired ones, which mean we will also be removing the remaining two */
cache->prune(now.tv_sec + 600);
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 0U);
}
auto cache = make_unique<AggressiveNSECCache>(10000);
std::vector<std::string> expected;
- expected.push_back("; Zone powerdns.com.\n");
- expected.push_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
- expected.push_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
- expected.push_back("z.powerdns.com. 10 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
- expected.push_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
- expected.push_back("; Zone powerdns.org.\n");
- expected.push_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n");
- expected.push_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
-
- struct timeval now;
- Utility::gettimeofday(&now, 0);
+ expected.emplace_back("; Zone powerdns.com.\n");
+ expected.emplace_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
+ expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+ expected.emplace_back("z.powerdns.com. 10 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
+ expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+ expected.emplace_back("; Zone powerdns.org.\n");
+ expected.emplace_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n");
+ expected.emplace_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+
+ struct timeval now
+ {
+ };
+ Utility::gettimeofday(&now, nullptr);
DNSRecord rec;
rec.d_name = DNSName("www.powerdns.com");
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
- if (!fp) {
+ auto filePtr = pdns::UniqueFilePtr(tmpfile());
+ if (!filePtr) {
BOOST_FAIL("Temporary file could not be opened");
}
- BOOST_CHECK_EQUAL(cache->dumpToFile(fp, now), 3U);
+ BOOST_CHECK_EQUAL(cache->dumpToFile(filePtr, now), 3U);
- rewind(fp.get());
+ rewind(filePtr.get());
char* line = nullptr;
size_t len = 0;
- ssize_t read;
- for (auto str : expected) {
- read = getline(&line, &len, fp.get());
+ for (const auto& str : expected) {
+ auto read = getline(&line, &len, filePtr.get());
+ if (read == -1) {
+ BOOST_FAIL("Unable to read a line from the temp file");
+ }
+ BOOST_CHECK_EQUAL(line, str);
+ }
+
+ expected.clear();
+ expected.emplace_back("; Zone powerdns.com.\n");
+ expected.emplace_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
+ expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+ expected.emplace_back("z.powerdns.com. 30 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
+ expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+ expected.emplace_back("; Zone powerdns.org.\n");
+ expected.emplace_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n");
+ expected.emplace_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+
+ rec.d_name = DNSName("z.powerdns.com");
+ rec.d_type = QType::NSEC;
+ rec.d_ttl = now.tv_sec + 30;
+ rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC"));
+ rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+ cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
+
+ rewind(filePtr.get());
+ BOOST_CHECK_EQUAL(cache->dumpToFile(filePtr, now), 3U);
+
+ rewind(filePtr.get());
+
+ for (const auto& str : expected) {
+ auto read = getline(&line, &len, filePtr.get());
if (read == -1) {
BOOST_FAIL("Unable to read a line from the temp file");
}
/* getline() allocates a buffer when called with a nullptr,
then reallocates it when needed, but we need to free the
last allocation if any. */
- free(line);
+ free(line); // NOLINT: it's the API.
+}
+
+static bool getDenialWrapper(std::unique_ptr<AggressiveNSECCache>& cache, time_t now, const DNSName& name, const QType& qtype, const std::optional<int> expectedResult = std::nullopt, const std::optional<size_t> expectedRecordsCount = std::nullopt)
+{
+ int res;
+ std::vector<DNSRecord> results;
+ pdns::validation::ValidationContext validationContext;
+ validationContext.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(validationContext.d_nsec3IterationsRemainingQuota)>::max();
+ bool found = cache->getDenial(now, name, qtype, results, res, ComboAddress("192.0.2.1"), boost::none, true, validationContext);
+ if (expectedResult) {
+ BOOST_CHECK_EQUAL(res, *expectedResult);
+ }
+ if (expectedRecordsCount) {
+ BOOST_CHECK_EQUAL(results.size(), *expectedRecordsCount);
+ }
+ return found;
}
BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
- int res;
- std::vector<DNSRecord> results;
-
/* we can use the NSEC3s we have */
/* direct match */
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), true);
DNSName other("other.powerdns.com");
/* now we insert a new NSEC3, with a different salt, changing that value for the zone */
/* we should be able to find a direct match for that name */
/* direct match */
- BOOST_CHECK_EQUAL(cache->getDenial(now, other, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other, QType::AAAA), true);
/* but we should not be able to use the other NSEC3s */
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false);
/* and the same thing but this time updating the iterations count instead of the salt */
DNSName other2("other2.powerdns.com");
/* we should be able to find a direct match for that name */
/* direct match */
- BOOST_CHECK_EQUAL(cache->getDenial(now, other2, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other2, QType::AAAA), true);
/* but we should not be able to use the other NSEC3s */
- BOOST_CHECK_EQUAL(cache->getDenial(now, other, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other, QType::AAAA), false);
}
BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
/* the cache should now be able to deny other types (except the DS) */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NoError);
- BOOST_CHECK_EQUAL(results.size(), 3U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true);
/* but not the DS that lives in the parent zone */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
- BOOST_CHECK_EQUAL(results.size(), 0U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false);
}
{
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
/* the cache should now be able to deny the DS */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NoError);
- BOOST_CHECK_EQUAL(results.size(), 3U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true);
/* but not any type that lives in the child zone */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false);
}
{
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
/* the cache should now be able to deny other types */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NoError);
- BOOST_CHECK_EQUAL(results.size(), 3U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true);
/* including the DS */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NoError);
- BOOST_CHECK_EQUAL(results.size(), 3U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true);
}
{
}
/* the cache should now be able to deny any type for the name */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NXDomain);
- BOOST_CHECK_EQUAL(results.size(), 5U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NXDomain, 5U), true);
/* including the DS, since we are not at the apex */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NXDomain);
- BOOST_CHECK_EQUAL(results.size(), 5U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NXDomain, 5U), true);
}
}
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
/* the cache should now be able to deny other types (except the DS) */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NoError);
- BOOST_CHECK_EQUAL(results.size(), 3U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true);
/* but not the DS that lives in the parent zone */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
- BOOST_CHECK_EQUAL(results.size(), 0U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false);
}
{
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
/* the cache should now be able to deny the DS */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NoError);
- BOOST_CHECK_EQUAL(results.size(), 3U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true);
/* but not any type that lives in the child zone */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false);
}
{
BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
/* the cache should now be able to deny other types */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NoError);
- BOOST_CHECK_EQUAL(results.size(), 3U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true);
/* including the DS */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NoError);
- BOOST_CHECK_EQUAL(results.size(), 3U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true);
}
{
}
/* the cache should now be able to deny any type for the name */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NXDomain);
- BOOST_CHECK_EQUAL(results.size(), 7U);
-
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NXDomain, 7U), true);
/* including the DS, since we are not at the apex */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
- BOOST_CHECK_EQUAL(res, RCode::NXDomain);
- BOOST_CHECK_EQUAL(results.size(), 7U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NXDomain, 7U), true);
}
{
/* we insert NSEC3s coming from the parent zone that could look like a valid denial but are not */
}
/* the cache should NOT be able to deny the name */
- int res;
- std::vector<DNSRecord> results;
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
- BOOST_CHECK_EQUAL(results.size(), 0U);
-
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, std::nullopt, 0U), false);
/* and the same for the DS */
- results.clear();
- BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
- BOOST_CHECK_EQUAL(results.size(), 0U);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_max_nsec3_hash_cost)
+{
+ AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
+ g_recCache = std::make_unique<MemRecursorCache>();
+
+ const DNSName zone("powerdns.com");
+ time_t now = time(nullptr);
+
+ /* first we need a SOA */
+ std::vector<DNSRecord> records;
+ time_t ttd = now + 30;
+ DNSRecord drSOA;
+ drSOA.d_name = zone;
+ drSOA.d_type = QType::SOA;
+ drSOA.d_class = QClass::IN;
+ drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
+ drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
+ drSOA.d_place = DNSResourceRecord::ANSWER;
+ records.push_back(drSOA);
+
+ g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure);
+ BOOST_CHECK_EQUAL(g_recCache->size(), 1U);
+
+ auto insertNSEC3s = [zone, now](std::unique_ptr<AggressiveNSECCache>& cache, const std::string& salt, unsigned int iterationsCount) -> void {
+ {
+ /* insert a NSEC3 matching the apex (will be the closest encloser) */
+ DNSName name("powerdns.com");
+ std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
+ DNSRecord rec;
+ rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
+ rec.d_type = QType::NSEC3;
+ rec.d_ttl = now + 10;
+
+ NSEC3RecordContent nrc;
+ nrc.d_algorithm = 1;
+ nrc.d_flags = 0;
+ nrc.d_iterations = iterationsCount;
+ nrc.d_salt = salt;
+ nrc.d_nexthash = hashed;
+ incrementHash(nrc.d_nexthash);
+ for (const auto& type : {QType::A}) {
+ nrc.set(type);
+ }
+
+ rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
+ auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
+ cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
+ }
+ {
+ /* insert a NSEC3 matching *.powerdns.com (wildcard) */
+ DNSName name("*.powerdns.com");
+ std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
+ auto before = hashed;
+ decrementHash(before);
+ DNSRecord rec;
+ rec.d_name = DNSName(toBase32Hex(before)) + zone;
+ rec.d_type = QType::NSEC3;
+ rec.d_ttl = now + 10;
+
+ NSEC3RecordContent nrc;
+ nrc.d_algorithm = 1;
+ nrc.d_flags = 0;
+ nrc.d_iterations = iterationsCount;
+ nrc.d_salt = salt;
+ nrc.d_nexthash = hashed;
+ incrementHash(nrc.d_nexthash);
+ for (const auto& type : {QType::A}) {
+ nrc.set(type);
+ }
+
+ rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
+ auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
+ cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
+ }
+ {
+ /* insert a NSEC3 matching sub.powerdns.com (next closer) */
+ DNSName name("sub.powerdns.com");
+ std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
+ auto before = hashed;
+ decrementHash(before);
+ DNSRecord rec;
+ rec.d_name = DNSName(toBase32Hex(before)) + zone;
+ rec.d_type = QType::NSEC3;
+ rec.d_ttl = now + 10;
+
+ NSEC3RecordContent nrc;
+ nrc.d_algorithm = 1;
+ nrc.d_flags = 0;
+ nrc.d_iterations = iterationsCount;
+ nrc.d_salt = salt;
+ nrc.d_nexthash = hashed;
+ incrementHash(nrc.d_nexthash);
+ for (const auto& type : {QType::A}) {
+ nrc.set(type);
+ }
+
+ rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
+ auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
+ cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
+ }
+ BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
+ };
+
+ {
+ /* zone with cheap parameters */
+ const std::string salt;
+ const unsigned int iterationsCount = 0;
+ AggressiveNSECCache::s_nsec3DenialProofMaxCost = 10;
+
+ auto cache = make_unique<AggressiveNSECCache>(10000);
+ insertNSEC3s(cache, salt, iterationsCount);
+
+ /* the cache should now be able to deny everything below sub.powerdns.com,
+ IF IT DOES NOT EXCEED THE COST */
+ {
+ /* short name: 10 labels below the zone apex */
+ DNSName lookupName("a.b.c.d.e.f.g.h.i.sub.powerdns.com.");
+ BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 10U);
+ BOOST_CHECK_LE(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA, RCode::NXDomain, 7U), true);
+ }
+ {
+ /* longer name: 11 labels below the zone apex */
+ DNSName lookupName("a.b.c.d.e.f.g.h.i.j.sub.powerdns.com.");
+ BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 11U);
+ BOOST_CHECK_GT(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA), false);
+ }
+ }
+
+ {
+ /* zone with expensive parameters */
+ const std::string salt("deadbeef");
+ const unsigned int iterationsCount = 50;
+ AggressiveNSECCache::s_nsec3DenialProofMaxCost = 100;
+
+ auto cache = make_unique<AggressiveNSECCache>(10000);
+ insertNSEC3s(cache, salt, iterationsCount);
+
+ /* the cache should now be able to deny everything below sub.powerdns.com,
+ IF IT DOES NOT EXCEED THE COST */
+ {
+ /* short name: 1 label below the zone apex */
+ DNSName lookupName("sub.powerdns.com.");
+ BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 1U);
+ BOOST_CHECK_LE(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA, RCode::NXDomain, 7U), true);
+ }
+ {
+ /* longer name: 2 labels below the zone apex */
+ DNSName lookupName("a.sub.powerdns.com.");
+ BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 2U);
+ BOOST_CHECK_GT(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost);
+ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA), false);
+ }
}
}
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
const auto matchingPolicy = dfe.getProcessingPolicy(DNSName("sub.sub.wildcard.wolf."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSDName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
- BOOST_CHECK_EQUAL(matchingPolicy.d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
- BOOST_CHECK_EQUAL(matchingPolicy.d_hit, "sub.sub.wildcard.wolf");
+ BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
+ BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_hit, "sub.sub.wildcard.wolf");
/* looking for wildcard.wolf. should not match *.wildcard-blocked. */
const auto notMatchingPolicy = dfe.getProcessingPolicy(DNSName("wildcard.wolf."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
/* except if we look exactly for the wildcard */
BOOST_CHECK(zone->findExactNSPolicy(nsWildcardName, zonePolicy));
BOOST_CHECK(zonePolicy == matchingPolicy);
- BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
- BOOST_CHECK_EQUAL(zonePolicy.d_hit, nsWildcardName.toStringNoDot());
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, nsWildcardName.toStringNoDot());
}
{
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zone->findNSIPPolicy(nsIP, zonePolicy));
BOOST_CHECK(zonePolicy == matchingPolicy);
- BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.0.2.0.192.rpz-nsip"));
- BOOST_CHECK_EQUAL(zonePolicy.d_hit, nsIP.toString());
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.0.2.0.192.rpz-nsip"));
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, nsIP.toString());
}
{
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zone->findExactQNamePolicy(blockedName, zonePolicy));
BOOST_CHECK(zonePolicy == matchingPolicy);
- BOOST_CHECK_EQUAL(zonePolicy.d_trigger, blockedName);
- BOOST_CHECK_EQUAL(zonePolicy.d_hit, blockedName.toStringNoDot());
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, blockedName);
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, blockedName.toStringNoDot());
/* but a subdomain should not be blocked (not a wildcard, and this is not suffix domain matching */
matchingPolicy = dfe.getQueryPolicy(DNSName("sub") + blockedName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
const auto matchingPolicy = dfe.getQueryPolicy(DNSName("sub.sub.wildcard-blocked."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
- BOOST_CHECK_EQUAL(matchingPolicy.d_trigger, blockedWildcardName);
- BOOST_CHECK_EQUAL(matchingPolicy.d_hit, "sub.sub.wildcard-blocked");
+ BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_trigger, blockedWildcardName);
+ BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_hit, "sub.sub.wildcard-blocked");
/* looking for wildcard-blocked. should not match *.wildcard-blocked. */
const auto notMatchingPolicy = dfe.getQueryPolicy(DNSName("wildcard-blocked."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
/* except if we look exactly for the wildcard */
BOOST_CHECK(zone->findExactQNamePolicy(blockedWildcardName, zonePolicy));
BOOST_CHECK(zonePolicy == matchingPolicy);
- BOOST_CHECK_EQUAL(zonePolicy.d_trigger, blockedWildcardName);
- BOOST_CHECK_EQUAL(zonePolicy.d_hit, blockedWildcardName.toStringNoDot());
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, blockedWildcardName);
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, blockedWildcardName.toStringNoDot());
}
{
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zone->findClientPolicy(clientIP, zonePolicy));
BOOST_CHECK(zonePolicy == matchingPolicy);
- BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.128.2.0.192.rpz-client-ip"));
- BOOST_CHECK_EQUAL(zonePolicy.d_hit, clientIP.toString());
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.128.2.0.192.rpz-client-ip"));
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, clientIP.toString());
}
{
/* blocked A */
DNSRecord dr;
dr.d_type = QType::A;
- dr.setContent(DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString()));
+ dr.setContent(DNSRecordContent::make(QType::A, QClass::IN, responseIP.toString()));
const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ResponseIP);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zone->findResponsePolicy(responseIP, zonePolicy));
BOOST_CHECK(zonePolicy == matchingPolicy);
- BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.254.2.0.192.rpz-ip"));
- BOOST_CHECK_EQUAL(zonePolicy.d_hit, responseIP.toString());
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.254.2.0.192.rpz-ip"));
+ BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, responseIP.toString());
}
{
/* allowed A */
DNSRecord dr;
dr.d_type = QType::A;
- dr.setContent(DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.142"));
+ dr.setContent(DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.142"));
const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
DNSFilterEngine::Policy zonePolicy;
const DNSName bad1("bad1.example.com.");
const DNSName bad2("bad2.example.com.");
- zone->addQNameTrigger(bad1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden.example.net.")}));
+ zone->addQNameTrigger(bad1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden.example.net.")}));
BOOST_CHECK_EQUAL(zone->size(), 1U);
- zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1")}));
+ zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.1")}));
BOOST_CHECK_EQUAL(zone->size(), 2U);
- zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.2")}));
+ zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.2")}));
BOOST_CHECK_EQUAL(zone->size(), 2U);
- zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::MX, QClass::IN, "10 garden-mail.example.net.")}));
+ zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::MX, QClass::IN, "10 garden-mail.example.net.")}));
BOOST_CHECK_EQUAL(zone->size(), 2U);
dfe.addZone(zone);
}
/* remove only one entry, one of the A local records */
- zone->rmQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1")}));
+ zone->rmQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.1")}));
BOOST_CHECK_EQUAL(zone->size(), 2U);
{
const DNSName name("foo.example.com");
const Netmask nm1("192.168.1.0/24");
- zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
- zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.5")}));
- zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::AAAA, QClass::IN, "::1234")}));
+ zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.4")}));
+ zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.5")}));
+ zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::AAAA, QClass::IN, "::1234")}));
BOOST_CHECK_EQUAL(zone->size(), 1U);
dfe.addZone(zone);
}
// Try to zap 1 nonexisting record
- zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.1.1.1")}));
+ zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.1.1.1")}));
// Zap a record using a wider netmask
- zone->rmClientTrigger(Netmask("192.168.0.0/16"), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+ zone->rmClientTrigger(Netmask("192.168.0.0/16"), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.4")}));
// Zap a record using a narrow netmask
- zone->rmClientTrigger(Netmask("192.168.1.1/32"), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+ zone->rmClientTrigger(Netmask("192.168.1.1/32"), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.4")}));
// Zap 1 existing record
- zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.5")}));
+ zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.5")}));
{ // A query should match one record now
const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
}
// Zap one more A record
- zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+ zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.4")}));
// Zap now nonexisting record
- zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+ zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.4")}));
{ // AAAA query should still match one record
const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
}
// Zap AAAA record
- zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::AAAA, QClass::IN, "::1234")}));
+ zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::AAAA, QClass::IN, "::1234")}));
{ // there should be no match left
const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
const DNSName badWildcard("*.bad-wildcard.example.com.");
const DNSName badUnderWildcard("sub.bad-wildcard.example.com.");
- zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
- zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
- zone1->addQNameTrigger(badWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1b.example.net.")}));
- zone2->addQNameTrigger(badUnderWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2b.example.net.")}));
+ zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
+ zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
+ zone1->addQNameTrigger(badWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden1b.example.net.")}));
+ zone2->addQNameTrigger(badUnderWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden2b.example.net.")}));
dfe.addZone(zone1);
dfe.addZone(zone2);
const DNSName nsName("ns.bad.wolf.");
const ComboAddress responseIP("192.0.2.254");
- zone1->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "client1a.example.net.")}));
- zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
- zone1->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsip1a.example.net.")}));
- zone1->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsname1a.example.net.")}));
- zone1->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "response1a.example.net.")}));
+ zone1->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "client1a.example.net.")}));
+ zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
+ zone1->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "nsip1a.example.net.")}));
+ zone1->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "nsname1a.example.net.")}));
+ zone1->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "response1a.example.net.")}));
- zone2->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "client2a.example.net.")}));
- zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
- zone2->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsip2a.example.net.")}));
- zone2->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsname2a.example.net.")}));
- zone2->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "response2a.example.net.")}));
+ zone2->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "client2a.example.net.")}));
+ zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
+ zone2->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "nsip2a.example.net.")}));
+ zone2->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "nsname2a.example.net.")}));
+ zone2->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "response2a.example.net.")}));
dfe.addZone(zone1);
dfe.addZone(zone2);
/* blocked A in the response */
DNSRecord dr;
dr.d_type = QType::A;
- dr.setContent(DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString()));
+ dr.setContent(DNSRecordContent::make(QType::A, QClass::IN, responseIP.toString()));
const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ResponseIP);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
/* blocked A in the response, except 1 is disabled and 2's priority is too high */
DNSRecord dr;
dr.d_type = QType::A;
- dr.setContent(DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString()));
+ dr.setContent(DNSRecordContent::make(QType::A, QClass::IN, responseIP.toString()));
const auto matchingPolicy = dfe.getPostPolicy({dr}, {{zone1->getName(), true}}, 1);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
bool first = true;
int o = 24;
for (;;) {
- while (mt.schedule(&now))
- ;
+ while (mt.schedule(now)) {
+ }
+
if (first) {
mt.sendEvent(12, &o);
first = false;
break;
}
BOOST_CHECK_EQUAL(g_result, o);
+ vector<int> events;
+ mt.getEvents(events);
+ BOOST_CHECK_EQUAL(events.size(), 0U);
}
static const size_t stackSize = 8 * 1024;
bool first = true;
int o = 25;
for (;;) {
- while (mt.schedule(&now)) {
+ while (mt.schedule(now)) {
;
}
if (first) {
BOOST_CHECK_EQUAL(g_result, o);
}
-#if defined(HAVE_FIBER_SANITIZER) && defined(__APPLE__) && defined(__arm64__)
-
-// This test is buggy on MacOS when compiled with asan. It also causes subsequents tests to report spurious issues.
-// So switch it off for now
-// See https://github.com/PowerDNS/pdns/issues/12151
-
-BOOST_AUTO_TEST_CASE(test_MtaskerException)
-{
- cerr << "test_MtaskerException test disabled on this platform with asan enabled, please fix" << endl;
-}
-
-#else
-
static void willThrow(void* /* p */)
{
throw std::runtime_error("Help!");
now.tv_sec = now.tv_usec = 0;
for (;;) {
- mt.schedule(&now);
+ mt.schedule(now);
}
},
std::exception);
}
-#endif // defined(HAVE_FIBER_SANITIZER) && defined(__APPLE__) && defined(__arm64__)
-
BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
rec.d_type = qtype;
rec.d_ttl = 600;
rec.d_place = DNSResourceRecord::AUTHORITY;
- rec.setContent(DNSRecordContent::mastermake(qtype, QClass::IN, content));
+ rec.setContent(DNSRecordContent::make(qtype, QClass::IN, content));
ret.records.push_back(rec);
BOOST_CHECK_EQUAL(cache.size(), 400U);
- cache.prune(100);
+ cache.prune(now.tv_sec, 100);
BOOST_CHECK_EQUAL(cache.size(), 100U);
}
BOOST_CHECK_EQUAL(cache.size(), 400U);
- cache.prune(100);
+ cache.prune(now.tv_sec, 100);
BOOST_CHECK_EQUAL(cache.size(), 100U);
}
/* power2 has been inserted more recently, so it should be
removed last */
- cache.prune(1);
+ cache.prune(now.tv_sec, 1);
BOOST_CHECK_EQUAL(cache.size(), 1U);
NegCache::NegCacheEntry got;
/* power2 has been updated more recently, so it should be
removed last */
- cache.prune(1);
+ cache.prune(now.tv_sec, 1);
BOOST_CHECK_EQUAL(cache.size(), 1U);
got = NegCache::NegCacheEntry();
cache.add(genNegCacheEntry(DNSName("www1.powerdns.com"), DNSName("powerdns.com"), now));
cache.add(genNegCacheEntry(DNSName("www2.powerdns.com"), DNSName("powerdns.com"), now));
- auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
- if (!fp)
+ auto filePtr = pdns::UniqueFilePtr(tmpfile());
+ if (!filePtr) {
BOOST_FAIL("Temporary file could not be opened");
+ }
- cache.doDump(fileno(fp.get()), 0, now.tv_sec);
+ cache.doDump(fileno(filePtr.get()), 0, now.tv_sec);
- rewind(fp.get());
+ rewind(filePtr.get());
char* line = nullptr;
size_t len = 0;
ssize_t read;
for (auto str : expected) {
- read = getline(&line, &len, fp.get());
+ read = getline(&line, &len, filePtr.get());
if (read == -1)
BOOST_FAIL("Unable to read a line from the temp file");
// The clock might have ticked so the 600 becomes 599
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
#include "nod.hh"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include <stdio.h>
BOOST_AUTO_TEST_CASE(test_resolve_queue_rate_limit)
{
taskQueueClear();
- pushResolveTask(DNSName("foo"), QType::AAAA, 0, 1);
+ pushResolveTask(DNSName("foo"), QType::AAAA, 0, 1, false);
BOOST_CHECK_EQUAL(getTaskSize(), 1U);
taskQueuePop();
BOOST_CHECK_EQUAL(getTaskSize(), 0U);
// Should hit rate limiting
- pushResolveTask(DNSName("foo"), QType::AAAA, 0, 1);
+ pushResolveTask(DNSName("foo"), QType::AAAA, 0, 1, false);
BOOST_CHECK_EQUAL(getTaskSize(), 0U);
// Should not hit rate limiting as time has passed
- pushResolveTask(DNSName("foo"), QType::AAAA, 61, 62);
+ pushResolveTask(DNSName("foo"), QType::AAAA, 61, 62, false);
BOOST_CHECK_EQUAL(getTaskSize(), 1U);
}
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include <stdio.h>
BOOST_AUTO_TEST_CASE(test_zonetocachegeneric)
{
+ g_log.setLoglevel(Logger::Critical);
+ g_log.toConsole(Logger::Critical);
zonemdGenericTest(genericTest, pdns::ZoneMD::Config::Require, pdns::ZoneMD::Config::Ignore, 4U);
zonemdGenericTest(genericBadTest, pdns::ZoneMD::Config::Require, pdns::ZoneMD::Config::Ignore, 0U);
}
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "dns_random.hh"
#include "iputils.hh"
#include "recpacketcache.hh"
+#include "taskqueue.hh"
+#include "rec-taskqueue.hh"
#include <utility>
BOOST_AUTO_TEST_SUITE(test_recpacketcache_cc)
string qpacket((const char*)&packet[0], packet.size());
pw.startRecord(qname, QType::A, ttd);
- BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
- BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &qhash), false);
+ time_t now = time(nullptr);
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, now, &fpacket, &age, &qhash), false);
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash), false);
ARecordContent ar("127.0.0.1");
ar.toPacket(pw);
pw.commit();
string rpacket((const char*)&packet[0], packet.size());
- rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none, false);
+ rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), now, ttd, vState::Indeterminate, boost::none, false);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
- rpc.doPruneTo(0);
+ rpc.doPruneTo(now, 0);
BOOST_CHECK_EQUAL(rpc.size(), 0U);
- rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none, false);
+ rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), now, ttd, vState::Indeterminate, boost::none, false);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
rpc.doWipePacketCache(qname);
BOOST_CHECK_EQUAL(rpc.size(), 0U);
- rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none, false);
+ rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), now, ttd, vState::Indeterminate, boost::none, false);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
uint32_t qhash2 = 0;
- bool found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash2);
+ bool found = rpc.getResponsePacket(tag, qpacket, now, &fpacket, &age, &qhash2);
BOOST_CHECK_EQUAL(found, true);
BOOST_CHECK_EQUAL(qhash, qhash2);
BOOST_CHECK_EQUAL(fpacket, rpacket);
- found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &qhash2);
+ found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash2);
BOOST_CHECK_EQUAL(found, true);
BOOST_CHECK_EQUAL(qhash, qhash2);
BOOST_CHECK_EQUAL(fpacket, rpacket);
pw2.getHeader()->id = dns_random_uint16();
qpacket.assign((const char*)&packet[0], packet.size());
- found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash);
+ found = rpc.getResponsePacket(tag, qpacket, now, &fpacket, &age, &qhash);
BOOST_CHECK_EQUAL(found, false);
- found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &qhash);
+ found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash);
BOOST_CHECK_EQUAL(found, false);
rpc.doWipePacketCache(DNSName("com"), 0xffff, true);
BOOST_CHECK_EQUAL(rpc.size(), 0U);
}
+BOOST_AUTO_TEST_CASE(test_recPacketCacheSimpleWithRefresh)
+{
+ RecursorPacketCache::s_refresh_ttlperc = 30;
+ RecursorPacketCache rpc(1000);
+ string fpacket;
+ unsigned int tag = 0;
+ uint32_t age = 0;
+ uint32_t qhash = 0;
+ uint32_t ttd = 3600;
+ BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+ taskQueueClear();
+
+ DNSName qname("www.powerdns.com");
+ vector<uint8_t> packet;
+ DNSPacketWriter pw(packet, qname, QType::A);
+ pw.getHeader()->rd = true;
+ pw.getHeader()->qr = false;
+ pw.getHeader()->id = dns_random_uint16();
+ string qpacket((const char*)&packet[0], packet.size());
+ pw.startRecord(qname, QType::A, ttd);
+
+ time_t now = time(nullptr);
+
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash), false);
+
+ ARecordContent ar("127.0.0.1");
+ ar.toPacket(pw);
+ pw.commit();
+ string rpacket((const char*)&packet[0], packet.size());
+
+ rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), now, ttd, vState::Indeterminate, boost::none, false);
+ BOOST_CHECK_EQUAL(rpc.size(), 1U);
+ uint32_t qhash2 = 0;
+ bool found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash2);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK_EQUAL(qhash, qhash2);
+ BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+ BOOST_REQUIRE_EQUAL(getTaskSize(), 0U);
+
+ // Half of time has passed, no task should have been submitted
+ now += ttd / 2;
+ found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash2);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK_EQUAL(qhash, qhash2);
+ BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+ BOOST_REQUIRE_EQUAL(getTaskSize(), 0U);
+
+ // 75% of time has passed, task should have been submitted as refresh perc is 30 and (100-75) = 25 <= 30
+ now += ttd / 4;
+ found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash2);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK_EQUAL(qhash, qhash2);
+ BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+ BOOST_REQUIRE_EQUAL(getTaskSize(), 1U);
+ auto task = taskQueuePop();
+ BOOST_CHECK_EQUAL(task.d_qname, qname);
+}
+
BOOST_AUTO_TEST_CASE(test_recPacketCacheSimplePost2038)
{
RecursorPacketCache rpc(1000);
rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), future, ttd, vState::Indeterminate, boost::none, false);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
- rpc.doPruneTo(0);
+ rpc.doPruneTo(time(nullptr), 0);
BOOST_CHECK_EQUAL(rpc.size(), 0U);
rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), future, ttd, vState::Indeterminate, boost::none, false);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
BOOST_CHECK_EQUAL(rpc.size(), 2U);
/* remove all responses from the cache */
- rpc.doPruneTo(0);
+ rpc.doPruneTo(time(nullptr), 0);
BOOST_CHECK_EQUAL(rpc.size(), 0U);
/* reinsert both */
BOOST_AUTO_TEST_CASE(test_recPacketCache_TCP)
{
/* Insert a response with UDP, the exact same query with a TCP flag
- should lead to a miss.
+ should lead to a miss.
*/
RecursorPacketCache rpc(1000);
string fpacket;
BOOST_CHECK_EQUAL(rpc.size(), 2U);
/* remove all responses from the cache */
- rpc.doPruneTo(0);
+ rpc.doPruneTo(time(nullptr), 0);
BOOST_CHECK_EQUAL(rpc.size(), 0U);
/* reinsert both */
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
static void simple(time_t now)
{
+ MemRecursorCache::resetStaticsForTests();
MemRecursorCache MRC;
std::vector<DNSRecord> records;
BOOST_AUTO_TEST_CASE(test_RecursorCacheGhost)
{
+ MemRecursorCache::resetStaticsForTests();
MemRecursorCache MRC;
std::vector<DNSRecord> records;
{
// Test #12140: as QM does a best NS lookup and then uses it, incoming infra records should update
// cache, otherwise they might expire in-between.
+ MemRecursorCache::resetStaticsForTests();
MemRecursorCache MRC;
std::vector<DNSRecord> records;
BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
{
+ MemRecursorCache::resetStaticsForTests();
MemRecursorCache MRC(1);
std::vector<DNSRecord> records;
/* we ask that 10 entries remain in the cache, this is larger than
the cache size (2), so 1 entry will be looked at as the code
rounds up the 10% of entries per shard to look at */
- MRC.doPrune(10);
+ MRC.doPrune(now, 10);
BOOST_CHECK_EQUAL(MRC.size(), 1U);
/* the remaining entry should be power2, but to get it
/* we ask that 10 entries remain in the cache, this is larger than
the cache size (2), so 1 entry will be looked at as the code
rounds up the 10% of entries per shard to look at */
- MRC.doPrune(10);
+ MRC.doPrune(now, 10);
BOOST_CHECK_EQUAL(MRC.size(), 1U);
/* the remaining entry should be power1, but to get it
BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
{
+ MemRecursorCache::resetStaticsForTests();
MemRecursorCache MRC(1);
std::vector<DNSRecord> records;
/* the one for power2 having been inserted
more recently should be removed last */
/* we ask that only entry remains in the cache */
- MRC.doPrune(1);
+ MRC.doPrune(now, 1);
BOOST_CHECK_EQUAL(MRC.size(), 1U);
/* the remaining entry should be power2 */
to the back of the expunge queue, so power2 should be at the front
and should this time be removed first */
/* we ask that only entry remains in the cache */
- MRC.doPrune(1);
+ MRC.doPrune(now, 1);
BOOST_CHECK_EQUAL(MRC.size(), 1U);
/* the remaining entry should be power1 */
/* the entry for power1 should have been moved to the back of the expunge queue
due to the hit, so power2 should be at the front and should this time be removed first */
/* we ask that only entry remains in the cache */
- MRC.doPrune(1);
+ MRC.doPrune(now, 1);
BOOST_CHECK_EQUAL(MRC.size(), 1U);
/* the remaining entry should be power1 */
/* check that power2 is gone */
BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), -1);
- MRC.doPrune(0);
+ MRC.doPrune(now, 0);
BOOST_CHECK_EQUAL(MRC.size(), 0U);
/* add a lot of netmask-specific entries */
/* remove a bit less than half of them */
size_t keep = 129;
- MRC.doPrune(keep);
+ MRC.doPrune(now, keep);
BOOST_CHECK_EQUAL(MRC.size(), keep);
BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
BOOST_CHECK_EQUAL(found, keep);
/* remove the rest */
- MRC.doPrune(0);
+ MRC.doPrune(now, 0);
BOOST_CHECK_EQUAL(MRC.size(), 0U);
BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
}
BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
{
+ MemRecursorCache::resetStaticsForTests();
MemRecursorCache MRC(1);
const DNSName power("powerdns.com.");
BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
/* wipe everything */
- MRC.doPrune(0);
+ MRC.doPrune(now, 0);
BOOST_CHECK_EQUAL(MRC.size(), 0U);
BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
/* wipe everything */
- MRC.doPrune(0);
+ MRC.doPrune(now, 0);
BOOST_CHECK_EQUAL(MRC.size(), 0U);
BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
BOOST_CHECK_EQUAL(MRC.size(), 2U);
/* wipe everything */
- MRC.doPrune(0);
+ MRC.doPrune(now, 0);
BOOST_CHECK_EQUAL(MRC.size(), 0U);
BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
/* wipe everything */
- MRC.doPrune(0);
+ MRC.doPrune(now, 0);
BOOST_CHECK_EQUAL(MRC.size(), 0U);
BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
}
BOOST_AUTO_TEST_CASE(test_RecursorCache_Wipe)
{
+ MemRecursorCache::resetStaticsForTests();
MemRecursorCache MRC;
const DNSName power("powerdns.com.");
BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
{
+ MemRecursorCache::resetStaticsForTests();
MemRecursorCache MRC;
const DNSName authZone(".");
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include <cstdio>
{
static std::shared_ptr<DNSRecordContent> makeLocalhostRootDRC()
{
- return DNSRecordContent::mastermake(QType::SOA, QClass::IN, "localhost. root 1 604800 86400 2419200 604800");
+ return DNSRecordContent::make(QType::SOA, QClass::IN, "localhost. root 1 604800 86400 2419200 604800");
}
static std::shared_ptr<DNSRecordContent> makeLocalhostDRC()
{
- return DNSRecordContent::mastermake(QType::NS, QClass::IN, "localhost.");
+ return DNSRecordContent::make(QType::NS, QClass::IN, "localhost.");
}
static std::shared_ptr<DNSRecordContent> makePtrDRC(const std::string& name)
{
- return DNSRecordContent::mastermake(QType::PTR, QClass::IN, name);
+ return DNSRecordContent::make(QType::PTR, QClass::IN, name);
}
static void addDomainMapFixtureEntry(SyncRes::domainmap_t& domainMap,
{
domainMap[DNSName{name}] = SyncRes::AuthDomain{
.d_records = {
- DNSRecord(name, DNSRecordContent::mastermake(type, QClass::IN, address), type),
+ DNSRecord(name, DNSRecordContent::make(type, QClass::IN, address), type),
DNSRecord(name, makeLocalhostDRC(), QType::NS),
DNSRecord(name, makeLocalhostRootDRC(), QType::SOA),
},
"localhost" + actualSearchSuffix,
{DNSRecord("localhost" + actualSearchSuffix, makeLocalhostDRC(), QType::NS),
DNSRecord("localhost" + actualSearchSuffix, makeLocalhostRootDRC(), QType::SOA),
- DNSRecord("localhost" + actualSearchSuffix, DNSRecordContent::mastermake(QType::AAAA, QClass::IN, "::1"), QType::AAAA),
- DNSRecord("localhost" + actualSearchSuffix, DNSRecordContent::mastermake(QType::A, QClass::IN, "127.0.0.1"), QType::A)});
+ DNSRecord("localhost" + actualSearchSuffix, DNSRecordContent::make(QType::AAAA, QClass::IN, "::1"), QType::AAAA),
+ DNSRecord("localhost" + actualSearchSuffix, DNSRecordContent::make(QType::A, QClass::IN, "127.0.0.1"), QType::A)});
addDomainMapFixtureEntry(domainMap, "self" + actualSearchSuffix, QType::AAAA, "::1");
addDomainMapFixtureEntry(
domainMap,
#include "config.h"
#endif
+#include <array>
+
+#include "arguments.hh"
#include "rpzloader.hh"
#include "syncres.hh"
}
}
+static string makeFile(const string& lines)
+{
+ std::array<char, 20> temp{"/tmp/rpzXXXXXXXXXX"};
+ int fileDesc = mkstemp(temp.data());
+ BOOST_REQUIRE(fileDesc > 0);
+ auto filePtr = pdns::UniqueFilePtr(fdopen(fileDesc, "w"));
+ BOOST_REQUIRE(filePtr);
+ size_t written = fwrite(lines.data(), 1, lines.length(), filePtr.get());
+ BOOST_REQUIRE(written == lines.length());
+ BOOST_REQUIRE(fflush(filePtr.get()) == 0);
+ return temp.data();
+}
+
+BOOST_AUTO_TEST_CASE(load_rpz_ok)
+{
+ const string lines = "\n"
+ "$ORIGIN rpz.example.net.\n"
+ "$TTL 1H\n"
+ "@ SOA LOCALHOST. named-mgr.example.net. (\n"
+ " 1 1h 15m 30d 2h)\n"
+ " NS LOCALHOST.\n"
+ "\n"
+ "; QNAME policy records.\n"
+ "; There are no periods (.) after the relative owner names.\n"
+ "nxdomain.example.com CNAME . ; NXDOMAIN policy\n"
+ "nodata.example.com CNAME *. ; NODATA policy\n"
+ "\n"
+ "; Redirect to walled garden\n"
+ "bad.example.com A 10.0.0.1\n"
+ " AAAA 2001:db8::1\n"
+ "\n"
+ "; Rewrite all names inside \"AZONE.EXAMPLE.COM\"\n"
+ "; except \"OK.AZONE.EXAMPLE.COM\"\n"
+ "*.azone.example.com CNAME garden.example.net.\n"
+ "ok.azone.example.com CNAME rpz-passthru.\n"
+ "\n"
+ "; Redirect \"BZONE.EXAMPLE.COM\" and \"X.BZONE.EXAMPLE.COM\"\n"
+ "; to \"BZONE.EXAMPLE.COM.GARDEN.EXAMPLE.NET\" and\n"
+ "; \"X.BZONE.EXAMPLE.COM.GARDEN.EXAMPLE.NET\", respectively.\n"
+ "bzone.example.com CNAME *.garden.example.net.\n"
+ "*.bzone.example.com CNAME *.garden.example.net.\n"
+ "\n"
+ "; Rewrite all answers containing addresses in 192.0.2.0/24,\n"
+ "; except 192.0.2.1\n"
+ "24.0.2.0.192.rpz-ip CNAME .\n"
+ "32.1.2.0.192.rpz-ip CNAME rpz-passthru.\n"
+ "\n"
+ "; Rewrite to NXDOMAIN all responses for domains for which\n"
+ "; \"NS.EXAMPLE.COM\" is an authoritative DNS server for that domain\n"
+ "; or any of its ancestors, or that have an authoritative server\n"
+ "; in 2001:db8::/32\n"
+ "ns.example.com.rpz-nsdname CNAME .\n"
+ "32.zz.db8.2001.rpz-nsip CNAME .\n"
+ "\n"
+ "; Local Data can include many record types\n"
+ "25.128.2.0.192.rpz-ip A 172.16.0.1\n"
+ "25.128.2.0.192.rpz-ip A 172.16.0.2\n"
+ "25.128.2.0.192.rpz-ip A 172.16.0.3\n"
+ "25.128.2.0.192.rpz-ip MX 10 mx1.example.com\n"
+ "25.128.2.0.192.rpz-ip MX 20 mx2.example.com\n"
+ "25.128.2.0.192.rpz-ip TXT \"Contact Central Services\"\n"
+ "25.128.2.0.192.rpz-ip TXT \"Your system is infected.\"\n";
+
+ auto rpz = makeFile(lines);
+
+ ::arg().set("max-generate-steps") = "1";
+ ::arg().set("max-include-depth") = "20";
+ auto zone = std::make_shared<DNSFilterEngine::Zone>();
+ auto soa = loadRPZFromFile(rpz, zone, boost::none, false, 3600);
+ unlink(rpz.c_str());
+
+ BOOST_CHECK_EQUAL(soa->d_st.serial, 1U);
+ BOOST_CHECK_EQUAL(zone->getDomain(), DNSName("rpz.example.net."));
+ BOOST_CHECK_EQUAL(zone->size(), 12U);
+}
+
+BOOST_AUTO_TEST_CASE(load_rpz_dups)
+{
+ const string lines = "\n"
+ "$TTL 300\n"
+ "\n"
+ "@ IN SOA need.to.know.only. hostmaster.spamhaus.org. (\n"
+ " 1000000000 ; Serial number\n"
+ " 60 ; Refresh every 1 minutes\n"
+ " 60 ; Retry every minute\n"
+ " 432000 ; Expire in 5 days\n"
+ " 60 ) ; negative caching ttl 1 minute\n"
+ " IN NS LOCALHOST.\n"
+ "qqq.powerdns.net CNAME .\n"
+ "qqq.powerdns.net IN A 3.4.5.6\n";
+
+ auto rpz = makeFile(lines);
+
+ ::arg().set("max-generate-steps") = "1";
+ ::arg().set("max-include-depth") = "20";
+ auto zone = std::make_shared<DNSFilterEngine::Zone>();
+
+ BOOST_CHECK_THROW(loadRPZFromFile(rpz, zone, boost::none, false, 3600),
+ std::runtime_error);
+ unlink(rpz.c_str());
+}
+
+BOOST_AUTO_TEST_CASE(load_rpz_dups_allow)
+{
+ const string lines = "\n"
+ "$TTL 300\n"
+ "\n"
+ "@ IN SOA need.to.know.only. hostmaster.powerdns.org. (\n"
+ " 1000000000 ; Serial number\n"
+ " 60 ; Refresh every 1 minutes\n"
+ " 60 ; Retry every minute\n"
+ " 432000 ; Expire in 5 days\n"
+ " 60 ) ; negative caching ttl 1 minute\n"
+ " IN NS LOCALHOST.\n"
+ "qqq.powerdns.net CNAME .\n"
+ "qqq.powerdns.net CNAME rpz-passthru\n";
+
+ auto rpz = makeFile(lines);
+
+ ::arg().set("max-generate-steps") = "1";
+ ::arg().set("max-include-depth") = "20";
+ auto zone = std::make_shared<DNSFilterEngine::Zone>();
+ zone->setIgnoreDuplicates(true);
+ auto soa = loadRPZFromFile(rpz, zone, boost::none, false, 3600);
+ unlink(rpz.c_str());
+ BOOST_CHECK_EQUAL(soa->d_st.serial, 1000000000U);
+ BOOST_CHECK_EQUAL(zone->getDomain(), DNSName("."));
+ BOOST_CHECK_EQUAL(zone->size(), 1U);
+}
+
BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
--- /dev/null
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#include <boost/test/unit_test.hpp>
+
+#include <memory>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/format.hpp>
+
+#include "settings/cxxsettings.hh"
+
+BOOST_AUTO_TEST_SUITE(test_settings)
+
+BOOST_AUTO_TEST_CASE(test_rust_empty)
+{
+ const std::string yaml = "{}\n";
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+ // Check an attribute to see if it has the right default value
+ BOOST_CHECK_EQUAL(settings.dnssec.aggressive_nsec_cache_size, 100000U);
+
+ // Generate yaml, should be empty as all values are default
+ auto back = settings.to_yaml_string();
+ // rust::String does not play nice with BOOST_CHECK_EQUAL, it lacks a <<
+ BOOST_CHECK_EQUAL(yaml, std::string(back));
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_syntaxerror)
+{
+ const std::string yaml = "{incoming: port: \n";
+ BOOST_CHECK_THROW(pdns::rust::settings::rec::parse_yaml_string(yaml), rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_unknown_section)
+{
+ const std::string yaml = "{adskldsaj: port: \n";
+ BOOST_CHECK_THROW(pdns::rust::settings::rec::parse_yaml_string(yaml), rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_unknown_field)
+{
+ const std::string yaml = "{incoming: akajkj0: \n";
+ BOOST_CHECK_THROW(pdns::rust::settings::rec::parse_yaml_string(yaml), rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_parse)
+{
+ const std::string yaml = R"EOT(dnssec:
+ aggressive_nsec_cache_size: 10
+incoming:
+ allow_from:
+ - '!123.123.123.123'
+ - ::1
+recursor:
+ auth_zones:
+ - zone: example.com
+ file: a/file/name
+ - zone: example.net
+ file: another/file/name
+ forward_zones:
+ - zone: .
+ forwarders:
+ - 9.9.9.9
+ forward_zones_recurse:
+ - zone: .
+ forwarders:
+ - 9.9.9.9
+ - 1.2.3.4
+ - ::99
+ recurse: true
+webservice:
+ api_key: otto
+packetcache:
+ disable: true
+)EOT";
+
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ BOOST_CHECK_EQUAL(settings.dnssec.aggressive_nsec_cache_size, 10U);
+ BOOST_CHECK_EQUAL(settings.incoming.allow_from.size(), 2U);
+ BOOST_REQUIRE_EQUAL(settings.recursor.auth_zones.size(), 2U);
+ BOOST_REQUIRE_EQUAL(settings.recursor.forward_zones.size(), 1U);
+ BOOST_REQUIRE_EQUAL(settings.recursor.forward_zones[0].forwarders.size(), 1U);
+ BOOST_REQUIRE_EQUAL(settings.recursor.forward_zones_recurse.size(), 1U);
+ BOOST_REQUIRE_EQUAL(settings.recursor.forward_zones_recurse[0].forwarders.size(), 3U);
+ BOOST_CHECK(settings.recursor.forward_zones_recurse[0].recurse);
+ auto back = settings.to_yaml_string();
+ // rust::String does not play nice with BOOST_CHECK_EQUAL, it lacks a <<
+ BOOST_CHECK_EQUAL(yaml, std::string(back));
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error1)
+{
+ const std::string yaml = R"EOT(
+incoming:
+ allow_from: ["1.2.3.8999"]
+)EOT";
+
+ BOOST_CHECK_THROW({
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto back = settings.to_yaml_string();
+ settings.validate();
+ },
+ rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error2)
+{
+ const std::string yaml = R"EOT(
+recursor:
+ forward_zones:
+ - zone: "example.com"
+ forwarders:
+ - 1.2.3.4
+ - a.b
+)EOT";
+
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto back = settings.to_yaml_string();
+ BOOST_CHECK_THROW({
+ settings.validate();
+ },
+ rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error3)
+{
+ const std::string yaml = R"EOT(
+recursor:
+ forward_zones:
+ - zone:
+ forwarders:
+ - 1.2.3.4
+)EOT";
+
+ BOOST_CHECK_THROW({
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto back = settings.to_yaml_string();
+ settings.validate();
+ },
+ rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error4)
+{
+ const std::string yaml = R"EOT(
+recursor:
+ forward_zones:
+ - zone: ok
+)EOT";
+
+ BOOST_CHECK_THROW({
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto back = settings.to_yaml_string();
+ settings.validate();
+ },
+ rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error5)
+{
+ const std::string yaml = R"EOT(
+recursor:
+ auth_zones:
+ - zone: %1%
+ file: filename
+)EOT";
+
+ const vector<string> oktests = {
+ ".",
+ "one",
+ "one.",
+ "two.label"
+ "two.label.",
+ };
+ for (const auto& ok : oktests) {
+ auto yamltest = boost::format(yaml) % ok;
+ BOOST_CHECK_NO_THROW({
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yamltest.str());
+ settings.validate();
+ });
+ }
+ const vector<string> noktests = {
+ "",
+ "..",
+ "two..label",
+ ".two.label",
+ "three€.a.label",
+ "three.a.label..",
+ };
+ for (const auto& nok : noktests) {
+ auto yamltest = boost::format(yaml) % nok;
+ BOOST_CHECK_THROW({
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yamltest.str());
+ auto back = settings.to_yaml_string();
+ settings.validate();
+ },
+ rust::Error);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_no_error)
+{
+ // All defaults
+ const std::string yaml = "{}\n";
+
+ BOOST_CHECK_NO_THROW({
+ auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ settings.validate();
+ });
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_forwardzones_to_yaml)
+{
+ using pdns::rust::settings::rec::ForwardZone;
+ rust::Vec<ForwardZone> vec;
+ vec.emplace_back(ForwardZone{"zone1", {"1.2.3.4"}, false, false});
+ vec.emplace_back(ForwardZone{"zone2", {"1.2.3.4", "::1"}, true, true});
+
+ auto yaml = pdns::rust::settings::rec::forward_zones_to_yaml_string(vec);
+
+ const std::string expected = R"EOT(- zone: zone1
+ forwarders:
+ - 1.2.3.4
+- zone: zone2
+ forwarders:
+ - 1.2.3.4
+ - ::1
+ recurse: true
+ notify_allowed: true
+)EOT";
+
+ BOOST_CHECK_EQUAL(std::string(yaml), expected);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_parse_forwardzones_to_yaml)
+{
+ std::string fileContent = R"EOT(
+# aap
+example1.com= 1.2.3.4, 5.6.7.8; 8.9.0.1
+^+example2.com = ::1
+)EOT";
+
+ const std::string expected = R"EOT(- zone: example1.com
+ forwarders:
+ - 1.2.3.4
+ - 5.6.7.8
+ - 8.9.0.1
+- zone: example2.com
+ forwarders:
+ - ::1
+ recurse: true
+ notify_allowed: true
+)EOT";
+
+ std::string temp("/tmp/test-settingsXXXXXXXXXX");
+ int fileDesc = mkstemp(temp.data());
+ BOOST_REQUIRE(fileDesc > 0);
+ auto filePtr = pdns::UniqueFilePtr(fdopen(fileDesc, "w"));
+ BOOST_REQUIRE(filePtr != nullptr);
+ size_t written = fwrite(fileContent.data(), 1, fileContent.length(), filePtr.get());
+ BOOST_REQUIRE(written == fileContent.length());
+ filePtr = nullptr;
+
+ rust::Vec<pdns::rust::settings::rec::ForwardZone> forwards;
+ pdns::settings::rec::oldStyleForwardsFileToBridgeStruct(temp, forwards);
+ unlink(temp.data());
+
+ auto yaml = pdns::rust::settings::rec::forward_zones_to_yaml_string(forwards);
+ BOOST_CHECK_EQUAL(std::string(yaml), expected);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_defaults)
+{
+ const std::string yaml = "{}\n";
+ auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+ pdns::rust::settings::rec::merge(lhs, yaml);
+ auto back = lhs.to_yaml_string();
+ BOOST_CHECK_EQUAL(yaml, std::string(back));
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_lhs_default)
+{
+ const std::string yaml = "{}\n";
+ auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+ const std::string yaml2 = R"EOT(
+recursor:
+ forward_zones:
+ - zone: zone
+ forwarders:
+ - 1.2.3.4
+dnssec:
+ validation: validate
+)EOT";
+
+ pdns::rust::settings::rec::merge(lhs, yaml2);
+
+ BOOST_CHECK_EQUAL(std::string(lhs.dnssec.validation), "validate");
+ BOOST_CHECK_EQUAL(lhs.recursor.forward_zones.size(), 1U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_lhs_nondefault)
+{
+ const std::string yaml = "{}\n";
+ auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+ lhs.dnssec.validation = "no";
+ lhs.recursor.forward_zones.emplace_back(pdns::rust::settings::rec::ForwardZone{"zone1", {"1.2.3.4"}, false, false});
+
+ rhs.dnssec.validation = "validate";
+ rhs.recursor.forward_zones.emplace_back(pdns::rust::settings::rec::ForwardZone{"zone2", {"1.2.3.4"}, false, false});
+
+ const auto yaml2 = rhs.to_yaml_string();
+ pdns::rust::settings::rec::merge(lhs, yaml2);
+
+ BOOST_CHECK_EQUAL(std::string(lhs.dnssec.validation), "validate");
+ BOOST_CHECK_EQUAL(lhs.recursor.forward_zones.size(), 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_rhs_mixed)
+{
+ const std::string yaml = "{}\n";
+ auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+ lhs.dnssec.validation = "no";
+ lhs.recursor.forward_zones.emplace_back(pdns::rust::settings::rec::ForwardZone{"zone1", {"1.2.3.4"}, false, false});
+ rhs.recursor.forward_zones.emplace_back(pdns::rust::settings::rec::ForwardZone{"zone2", {"1.2.3.4"}, false, false});
+
+ const auto yaml2 = rhs.to_yaml_string();
+ pdns::rust::settings::rec::merge(lhs, yaml);
+
+ pdns::rust::settings::rec::merge(lhs, yaml2);
+
+ BOOST_CHECK_EQUAL(std::string(lhs.dnssec.validation), "no");
+ BOOST_CHECK_EQUAL(lhs.recursor.forward_zones.size(), 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_list_nonempty_default1)
+{
+ const std::string yaml = "{}\n";
+ auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+ // Note that dont_query is a non-empty list by default
+ // lhs default, rhs is not (empty ), rhs overwrites lhs
+ rhs.outgoing.dont_query = {};
+ const auto yaml2 = rhs.to_yaml_string();
+ pdns::rust::settings::rec::merge(lhs, yaml2);
+
+ BOOST_CHECK_EQUAL(lhs.outgoing.dont_query.size(), 0U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_list_nonempty_default2)
+{
+ const std::string yaml = "{}\n";
+ auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+ rhs.outgoing.dont_query = {"1.2.3.4"};
+ // lhs default, rhs overwrites lhs
+ const auto yaml2 = rhs.to_yaml_string();
+ pdns::rust::settings::rec::merge(lhs, yaml2);
+
+ BOOST_CHECK_EQUAL(lhs.outgoing.dont_query.size(), 1U);
+
+ rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+ rhs.outgoing.dont_query = {"4.5.6.7"};
+ // lhs not default, rhs gets merged
+ const auto yaml3 = rhs.to_yaml_string();
+ pdns::rust::settings::rec::merge(lhs, yaml2);
+
+ BOOST_CHECK_EQUAL(lhs.outgoing.dont_query.size(), 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_nondefault_and_default)
+{
+ const std::string yaml1 = "{}\n";
+ auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml1);
+ lhs.recordcache.max_entries = 99;
+ lhs.dnssec.validation = "no";
+ const std::string yaml2 = R"EOT(
+ dnssec:
+ validation: process
+ incoming:
+ allow_from:
+ - 4.5.6.7/1
+)EOT";
+ pdns::rust::settings::rec::merge(lhs, yaml2);
+
+ BOOST_CHECK_EQUAL(lhs.dnssec.validation, "process");
+ BOOST_CHECK_EQUAL(lhs.incoming.allow_from.size(), 1U);
+ BOOST_CHECK_EQUAL(lhs.recordcache.max_entries, 99U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_override)
+{
+ const std::string yaml1 = R"EOT(
+ incoming:
+ allow_from:
+ - 4.5.6.7/1
+)EOT";
+ auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml1);
+ lhs.recordcache.max_entries = 99;
+ lhs.dnssec.validation = "no";
+ const std::string yaml2 = R"EOT(
+ dnssec:
+ validation: process
+ incoming:
+ allow_from: !override
+ - 1.2.3.4/1
+)EOT";
+ pdns::rust::settings::rec::merge(lhs, yaml2);
+
+ BOOST_CHECK_EQUAL(lhs.dnssec.validation, "process");
+ BOOST_REQUIRE_EQUAL(lhs.incoming.allow_from.size(), 1U);
+ BOOST_CHECK_EQUAL(lhs.incoming.allow_from.at(0), "1.2.3.4/1");
+ BOOST_CHECK_EQUAL(lhs.recordcache.max_entries, 99U);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "aggressive_nsec.hh"
#include "root-dnssec.hh"
#include "rec-taskqueue.hh"
#include "test-syncres_cc.hh"
+#include "recpacketcache.hh"
GlobalStateHolder<LuaConfigItems> g_luaconfs;
GlobalStateHolder<SuffixMatchNode> g_xdnssec;
{
}
-LWResult::Result asyncresolve(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 */, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& /* outgoingLoggers */, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& /* fstrmLoggers */, const std::set<uint16_t>& /* exportTypes */, LWResult* /* res */, bool* /* chained */)
+LWResult::Result asyncresolve(const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& /* outgoingLoggers */, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& /* fstrmLoggers */, const std::set<uint16_t>& /* exportTypes */, LWResult* /* res */, bool* /* chained */)
{
return LWResult::Result::Timeout;
}
g_log.toConsole(Logger::Error);
}
+ RecursorPacketCache::s_refresh_ttlperc = 0;
MemRecursorCache::s_maxServedStaleExtensions = 0;
NegCache::s_maxServedStaleExtensions = 0;
g_recCache = std::make_unique<MemRecursorCache>();
SyncRes::s_save_parent_ns_set = true;
SyncRes::s_maxnsperresolve = 13;
SyncRes::s_locked_ttlperc = 0;
+ SyncRes::s_minimize_one_label = 4;
+ SyncRes::s_max_minimize_count = 10;
SyncRes::clearNSSpeeds();
BOOST_CHECK_EQUAL(SyncRes::getNSSpeedsSize(), 0U);
::arg().set("version-string", "string reported on version.pdns or version.bind") = "PowerDNS Unit Tests";
::arg().set("rng") = "auto";
::arg().set("entropy-source") = "/dev/urandom";
+ ::arg().set("hint-file") = "";
}
void initSR(std::unique_ptr<SyncRes>& sr, bool dnssec, bool debug, time_t fakeNow)
throw std::runtime_error("No DNSKEY found for " + signer.toLogString() + ", unable to compute the requested RRSIG");
}
- size_t recordsCount = records.size();
- const DNSName& name = records[recordsCount - 1].d_name;
- const uint16_t type = records[recordsCount - 1].d_type;
+ DNSName name;
+ uint16_t type{QType::ENT};
+ DNSResourceRecord::Place place{DNSResourceRecord::ANSWER};
+ uint32_t ttl{0};
+ bool found = false;
+
+ /* locate the last non-RRSIG record */
+ for (auto recordIterator = records.rbegin(); recordIterator != records.rend(); ++recordIterator) {
+ if (recordIterator->d_type != QType::RRSIG) {
+ name = recordIterator->d_name;
+ type = recordIterator->d_type;
+ place = recordIterator->d_place;
+ ttl = recordIterator->d_ttl;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ throw std::runtime_error("Unable to locate the record that the RRSIG should cover");
+ }
sortedRecords_t recordcontents;
for (const auto& record : records) {
}
RRSIGRecordContent rrc;
- computeRRSIG(it->second.first, signer, wildcard ? *wildcard : records[recordsCount - 1].d_name, records[recordsCount - 1].d_type, records[recordsCount - 1].d_ttl, sigValidity, rrc, recordcontents, algo, boost::none, now);
+ computeRRSIG(it->second.first, signer, wildcard ? *wildcard : name, type, ttl, sigValidity, rrc, recordcontents, algo, boost::none, now);
if (broken) {
rrc.d_signature[0] ^= 42;
}
DNSRecord rec;
rec.d_type = QType::RRSIG;
- rec.d_place = records[recordsCount - 1].d_place;
- rec.d_name = records[recordsCount - 1].d_name;
- rec.d_ttl = records[recordsCount - 1].d_ttl;
+ rec.d_place = place;
+ rec.d_name = name;
+ rec.d_ttl = ttl;
rec.setContent(std::make_shared<RRSIGRecordContent>(rrc));
records.push_back(rec);
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == g_rootdnsname && type == QType::NS) {
then call getRootNS(), for which at least one of the root servers needs to answer.
None will, so it should ServFail.
*/
- sr->setAsyncCallback([&downServers](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 */) {
- downServers.insert(ip);
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
+ downServers.insert(address);
return LWResult::Result::Timeout;
});
primeHints();
const DNSName target("www.example.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (domain == g_rootdnsname && type == QType::NS) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- auto asynccb = [target, &queriesCount, aroot, newA, newAAAA](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 */) {
+ auto asynccb = [&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
size_t queriesWithEDNS = 0;
size_t queriesWithoutEDNS = 0;
- sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS, &noEDNSServer, sample](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([&](const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool /* sendRDQuery */, int EDNS0Level, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (EDNS0Level != 0) {
queriesWithEDNS++;
- noEDNSServer = ip;
+ noEDNSServer = address;
setLWResult(res, RCode::FormErr);
return LWResult::Result::Success;
size_t queriesWithoutEDNS = 0;
std::set<ComboAddress> usedServers;
- sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS, &usedServers](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([&](const ComboAddress& address, const DNSName& /* domain */, int type, bool /* doTCP */, bool /* sendRDQuery */, int EDNS0Level, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (EDNS0Level > 0) {
queriesWithEDNS++;
}
else {
queriesWithoutEDNS++;
}
- usedServers.insert(ip);
+ usedServers.insert(address);
if (type == QType::DNAME) {
setLWResult(res, RCode::FormErr);
for (const auto qtype : invalidTypes) {
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
});
std::unique_ptr<SyncRes> sr;
initSR(sr);
- sr->setAsyncCallback([](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool doTCP, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (!doTCP) {
setLWResult(res, 0, false, true, false);
return LWResult::Result::Success;
size_t tcpQueriesCount = 0;
- sr->setAsyncCallback([&tcpQueriesCount](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool doTCP, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (!doTCP) {
setLWResult(res, 0, true, true, false);
return LWResult::Result::Success;
primeHints();
- sr->setAsyncCallback([&downServers](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
return LWResult::Result::Success;
}
- else {
- downServers.insert(ip);
- return LWResult::Result::Timeout;
- }
+ downServers.insert(address);
+ return LWResult::Result::Timeout;
});
DNSName target("powerdns.com.");
primeHints();
- sr->setAsyncCallback([&downServers](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
return LWResult::Result::Success;
}
- else {
- downServers.insert(ip);
- return LWResult::Result::Timeout;
- }
+ downServers.insert(address);
+ return LWResult::Result::Timeout;
});
/* exact same test than the previous one, except instead of a time out we fake a network error */
std::set<ComboAddress> downServers;
- sr->setAsyncCallback([&downServers](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool doTCP, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "lock-up.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
setLWResult(res, 0, false, true, false);
return LWResult::Result::Success;
}
- else {
- downServers.insert(ip);
+ downServers.insert(address);
- setLWResult(res, RCode::FormErr, false, false, false);
- res->d_validpacket = false;
- return LWResult::Result::Success;
- }
+ setLWResult(res, RCode::FormErr, false, false, false);
+ res->d_validpacket = false;
+ return LWResult::Result::Success;
});
DNSName target("www.lock-up.");
std::set<ComboAddress> downServers;
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount, &downServers](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "lock-up.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
}
++queriesCount;
- downServers.insert(ip);
+ downServers.insert(address);
setLWResult(res, RCode::FormErr, false, false, false);
res->d_validpacket = false;
std::set<ComboAddress> downServers;
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount, &downServers](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "refused.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
}
++queriesCount;
- downServers.insert(ip);
+ downServers.insert(address);
setLWResult(res, RCode::Refused, false, false, true);
ad.d_servers = forwardedNSs;
(*SyncRes::t_sstorage.domainmap)[target] = ad;
- sr->setAsyncCallback([&queriesCount, &downServers](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "refused.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
}
++queriesCount;
- downServers.insert(ip);
+ downServers.insert(address);
setLWResult(res, RCode::Refused, false, false, true);
ad.d_servers = forwardedNSs;
(*SyncRes::t_sstorage.domainmap)[DNSName("refused.")] = ad;
- sr->setAsyncCallback([&queriesCount, &downServers](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "refused.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
}
++queriesCount;
- downServers.insert(ip);
+ downServers.insert(address);
setLWResult(res, RCode::ServFail, false, false, true);
DNSName target("www.powerdns.com.");
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain == target) {
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.3:53")) {
+ if (address == ComboAddress("192.0.2.3:53")) {
setLWResult(res, 0, true, false, true);
if (domain == DNSName("pdns-public-ns2.powerdns.net.")) {
if (type == QType::A) {
primeHints();
- sr->setAsyncCallback([&downServers](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
return LWResult::Result::Success;
}
- else {
+ {
if (downServers.size() < 3) {
/* only the last one will answer */
- downServers.insert(ip);
+ downServers.insert(address);
return LWResult::Result::OSLimitError;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, "powerdns.com.", QType::A, "192.0.2.42");
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::A, "192.0.2.42");
+ return LWResult::Result::Success;
}
});
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
if (domain != target) {
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4");
return LWResult::Result::Success;
}
- else {
- return LWResult::Result::Timeout;
- }
+ return LWResult::Result::Timeout;
});
vector<DNSRecord> ret;
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain.isPartOf(DNSName("com."))) {
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
if (domain == target) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
+ if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::A, "192.0.2.2");
addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::AAAA, "2001:DB8::2");
return LWResult::Result::Success;
}
- else if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
+ if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::A, "192.0.2.3");
addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::AAAA, "2001:DB8::3");
setLWResult(res, RCode::NXDomain, false, false, true);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4");
return LWResult::Result::Success;
}
- else {
- return LWResult::Result::Timeout;
- }
+ return LWResult::Result::Timeout;
});
vector<DNSRecord> ret;
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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
+ 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;
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain.isPartOf(DNSName("com."))) {
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
if (domain == target) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
+ if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
setLWResult(res, 0, true, false, true);
if (type == QType::A) {
addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::A, "192.0.2.2");
}
return LWResult::Result::Success;
}
- else if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
+ if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
setLWResult(res, 0, true, false, true);
if (type == QType::A) {
addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::A, "192.0.2.3");
setLWResult(res, RCode::NXDomain, false, false, true);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4");
return LWResult::Result::Success;
}
- else {
- return LWResult::Result::Timeout;
- }
+ return LWResult::Result::Timeout;
});
vector<DNSRecord> ret;
incomingECS.source = Netmask("192.0.2.128/32");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
- sr->setAsyncCallback([target](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
incomingECS.source = Netmask("2001:DB8::FF/128");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
BOOST_REQUIRE(!srcmask);
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "2001:db8::/56");
// No incoming ECS data
sr->setQuerySource(ComboAddress("192.0.2.127"), boost::none);
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
BOOST_REQUIRE(!srcmask);
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
// No incoming ECS data, Requestor IP not in ecs-add-for
sr->setQuerySource(ComboAddress("192.0.2.127"), boost::none);
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
BOOST_REQUIRE(!srcmask);
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "127.0.0.1/32");
incomingECS.source = Netmask("192.0.0.0/16");
sr->setQuerySource(ComboAddress("192.0.2.127"), boost::optional<const EDNSSubnetOpts&>(incomingECS));
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
BOOST_REQUIRE(!srcmask);
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.0.0/16");
incomingECS.source = Netmask("0.0.0.0/0");
sr->setQuerySource(ComboAddress("192.0.2.127"), boost::optional<const EDNSSubnetOpts&>(incomingECS));
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
BOOST_REQUIRE(!srcmask);
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "127.0.0.1/32");
const DNSName target("cname.powerdns.com.");
const DNSName cnameTarget("cname-target.powerdns.com");
- sr->setAsyncCallback([target, cnameTarget](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
return LWResult::Result::Success;
}
- else if (domain == cnameTarget) {
+ if (domain == cnameTarget) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
}
const DNSName target("cname.powerdns.com.");
const DNSName cnameTarget("cname-target.powerdns.com");
- sr->setAsyncCallback([target, cnameTarget](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
setLWResult(res, RCode::NXDomain, true, false, false);
const DNSName target("cname.powerdns.com.");
const DNSName cnameTarget("cname-target.powerdns.com");
- sr->setAsyncCallback([target, cnameTarget](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, cnameTarget, QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL);
return LWResult::Result::Success;
}
- else if (domain == cnameTarget) {
+ if (domain == cnameTarget) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, cnameTarget, QType::A, "192.0.2.3");
return LWResult::Result::Success;
size_t count = 0;
const DNSName target("cname.powerdns.com.");
- 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 */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
count++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
setLWResult(res, 0, true, false, false);
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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
count++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target1) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::CNAME, target2.toString());
return LWResult::Result::Success;
}
- else if (domain == target2) {
+ if (domain == target2) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::CNAME, target3.toString());
return LWResult::Result::Success;
}
- else if (domain == target3) {
+ if (domain == target3) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::CNAME, target4.toString());
return LWResult::Result::Success;
}
- else if (domain == target4) {
+ if (domain == target4) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::CNAME, target1.toString());
return LWResult::Result::Success;
}
}
-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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == 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;
}
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_cname_target_servfail)
+{
+ std::unique_ptr<SyncRes> resolver;
+ initSR(resolver);
+
+ primeHints();
+
+ const DNSName target("cname.powerdns.com.");
+ const DNSName cnameTarget("cname-target.powerdns.com");
+
+ resolver->setAsyncCallback([&](const ComboAddress& ipAddress, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(ipAddress)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ if (ipAddress == ComboAddress("192.0.2.1:53")) {
+
+ if (domain == target) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+ return LWResult::Result::Success;
+ }
+ if (domain == cnameTarget) {
+ return LWResult::Result::PermanentError;
+ }
+
+ return LWResult::Result::Success;
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = resolver->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_target_servfail_servestale)
+{
+ std::unique_ptr<SyncRes> resolver;
+ initSR(resolver);
+ MemRecursorCache::s_maxServedStaleExtensions = 1440;
+
+ primeHints();
+
+ const DNSName target("cname.powerdns.com.");
+ const DNSName cnameTarget("cname-target.powerdns.com");
+
+ resolver->setAsyncCallback([&](const ComboAddress& ipAddress, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(ipAddress)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ if (ipAddress == ComboAddress("192.0.2.1:53")) {
+
+ if (domain == target) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+ return LWResult::Result::Success;
+ }
+ if (domain == cnameTarget) {
+ return LWResult::Result::PermanentError;
+ }
+
+ return LWResult::Result::Success;
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = resolver->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ // different compared no non-servestale case (returns ServFail), handled by pdns_recursor
+ BOOST_CHECK_EQUAL(res, -1);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
}
BOOST_AUTO_TEST_CASE(test_time_limit)
size_t queries = 0;
const DNSName target("cname.powerdns.com.");
- sr->setAsyncCallback([target, &queries](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
/* Pretend that this query took 2000 ms */
res->d_usec = 2000;
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
size_t queries = 0;
- sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, uncachedTarget, uncachedCNAMETarget, &queries](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain.isPartOf(dnameOwner)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, dnameOwner, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ else if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, dnameOwner, QType::DNAME, dnameTarget.toString());
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == cnameTarget) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
size_t queries = 0;
- sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, keys, &queries](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
/* We don't use the genericDSAndDNSKEYHandler here, as it would deny names existing at the wrong level of the tree, due to the way computeZoneCuts works
* As such, we need to do some more work to make the answers correct.
*/
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain.countLabels() == 0 && type == QType::DNSKEY) { // .|DNSKEY
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ else if (address == ComboAddress("192.0.2.1:53")) {
if (domain.countLabels() == 1 && type == QType::DNSKEY) { // powerdns|DNSKEY
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain.countLabels() == 1 && type == QType::DNSKEY) { // example|DNSKEY
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
size_t queries = 0;
- sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, keys, &queries](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
if (type == QType::DS || type == QType::DNSKEY) {
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (domain == cnameTarget) {
+ if (domain == cnameTarget) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
addRRSIG(keys, res->d_records, dnameTarget, 300);
size_t queries = 0;
- sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, keys, &queries](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain.countLabels() == 0 && type == QType::DNSKEY) { // .|DNSKEY
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ else if (address == ComboAddress("192.0.2.1:53")) {
if (domain.countLabels() == 1 && type == QType::DNSKEY) { // powerdns|DNSKEY
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == target && type == QType::DS) { // dname.example|DS
return genericDSAndDNSKEYHandler(res, domain, dnameTarget, type, keys, false);
}
size_t queries = 0;
- sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, &queries](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain.isPartOf(dnameOwner)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, dnameOwner, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ else if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, dnameOwner, QType::DNAME, dnameTarget.toString());
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == cnameTarget) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
if (domain != target) {
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
if (type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4");
return LWResult::Result::Success;
}
- else if (type == QType::NS) {
+ if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-nsX1.powerdns.com.", DNSResourceRecord::ANSWER, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-nsX2.powerdns.com.", DNSResourceRecord::ANSWER, 172800);
addRecordToLW(res, "pdns-public-nsX2.powerdns.com.", QType::AAAA, "2001:DB8::12", DNSResourceRecord::ADDITIONAL, 172800);
return LWResult::Result::Success;
}
- else {
- return LWResult::Result::Timeout;
- }
- }
- else {
return LWResult::Result::Timeout;
}
+ return LWResult::Result::Timeout;
});
vector<DNSRecord> ret;
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
int queries = 0;
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target, &v4Hit, &v6Hit, &queries](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
- v4Hit |= ip.isIPv4();
- v6Hit |= ip.isIPv6();
+ v4Hit |= address.isIPv4();
+ v6Hit |= address.isIPv6();
if (domain == DNSName("powerdns.com.")) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, false);
v4Hit |= true;
if (domain == DNSName("powerdns.com.")) {
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("[2001:DB8:1::53]:53")) {
+ if (address == ComboAddress("[2001:DB8:1::53]:53")) {
setLWResult(res, 0, true, false, false);
v6Hit |= true;
if (domain == DNSName("powerdns.com.")) {
int queries = 0;
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target, &queries](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain == DNSName("powerdns.com.")) {
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("[2001:DB8:1::53]:53")) {
+ if (address == ComboAddress("[2001:DB8:1::53]:53")) {
setLWResult(res, 0, true, false, false);
if (domain == DNSName("powerdns.com.")) {
addRecordToLW(res, domain, QType::A, "192.0.2.2");
int queries = 0;
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target, &queries](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain == DNSName("powerdns.com.")) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, false);
if (domain == DNSName("powerdns.com.")) {
addRecordToLW(res, domain, QType::A, "192.0.2.2");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("sub.powerdns.com.")) {
+ if (domain == DNSName("sub.powerdns.com.")) {
/* sends a NODATA for the DS, but it is in fact a NXD proof, which would be bogus if the zone was actually secure */
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain.isPartOf(DNSName("powerdns.com."))) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain.isPartOf(DNSName("sub.powerdns.com."))) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("nx.powerdns.com.")) {
+ if (domain == DNSName("nx.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain.isPartOf(DNSName("powerdns.com."))) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain.isPartOf(DNSName("sub.powerdns.com."))) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, DNSName("com.") /* wrong signer !! */, 300, /* broken !!!*/ true);
return LWResult::Result::Success;
}
- else if (domain == DNSName("www.powerdns.com.")) {
+ if (domain == DNSName("www.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
return LWResult::Result::Success;
size_t queriesCount = 0;
- sr->setAsyncCallback([targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("sub.powerdns.com.")) {
+ if (domain == DNSName("sub.powerdns.com.")) {
/* sends a NODATA for the DS */
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain.isPartOf(DNSName("powerdns.com."))) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == DNSName("nxd.sub.powerdns.com.")) {
setLWResult(res, RCode::NXDomain, true, false, true);
addNSECRecordToLW(DNSName("nxc.sub.powerdns.com."), DNSName("nxe.sub.powerdnsz.com."), {QType::AAAA}, 600, res->d_records);
addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("nodata.sub.powerdns.com.")) {
+ if (domain == DNSName("nodata.sub.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
/* NSEC but no SOA */
addNSECRecordToLW(DNSName("nodata.sub.powerdns.com."), DNSName("nodata2.sub.powerdnsz.com."), {QType::AAAA}, 600, res->d_records);
addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("www.powerdns.com.")) {
+ if (domain == DNSName("www.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
return LWResult::Result::Success;
size_t queriesCount = 0;
- sr->setAsyncCallback([targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("sub.powerdns.com.")) {
+ if (domain == DNSName("sub.powerdns.com.")) {
/* sends a NODATA for the DS */
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == DNSName("sub.powerdns.com.")) {
/* sends a NODATA for the DNSKEY */
setLWResult(res, 0, true, false, true);
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain.isPartOf(DNSName("powerdns.com."))) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == DNSName("www.sub.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("www.powerdns.com.")) {
+ if (domain == DNSName("www.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
return LWResult::Result::Success;
size_t queriesCount = 0;
- sr->setAsyncCallback([targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("sub.powerdns.com.")) {
+ if (domain == DNSName("sub.powerdns.com.")) {
/* sends a NODATA for the DS */
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == DNSName("sub.powerdns.com.")) {
/* sends a NXD for the DNSKEY */
setLWResult(res, RCode::NXDomain, true, false, true);
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain.isPartOf(DNSName("powerdns.com."))) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == DNSName("www.sub.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("www.powerdns.com.")) {
+ if (domain == DNSName("www.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
return LWResult::Result::Success;
size_t queriesCount = 0;
- sr->setAsyncCallback([targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("sub.powerdns.com.")) {
+ if (domain == DNSName("sub.powerdns.com.")) {
/* sends a NXD!! for the DS */
setLWResult(res, RCode::NXDomain, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain.isPartOf(DNSName("powerdns.com."))) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == DNSName("www.sub.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("www.powerdns.com.")) {
+ if (domain == DNSName("www.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
return LWResult::Result::Success;
size_t queriesCount = 0;
- sr->setAsyncCallback([targetAddr, &queriesCount, keys, wrongKeys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == DNSName("powerdns.com.")) {
/* wrong DNSKEY */
return genericDSAndDNSKEYHandler(res, domain, domain, type, wrongKeys);
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain.isPartOf(DNSName("powerdns.com."))) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == DNSName("www.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
const int theTTL = 5;
- sr->setAsyncCallback([&downServers, &downCount, &lookupCount, target](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([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
- if (downServers.find(ip) != downServers.end()) {
+ if (downServers.find(address) != downServers.end()) {
downCount++;
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, 5);
lookupCount++;
return LWResult::Result::Success;
}
- else {
- return LWResult::Result::Timeout;
- }
+ return LWResult::Result::Timeout;
});
time_t now = time(nullptr);
const int theTTL = 5;
- sr->setAsyncCallback([&downServers, &downCount, &lookupCount, target](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([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
- if (downServers.find(ip) != downServers.end()) {
+ if (downServers.find(address) != downServers.end()) {
downCount++;
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, "powerdns.com.", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY);
lookupCount++;
const int theTTL = 5;
const int negTTL = 60;
- sr->setAsyncCallback([&downServers, &downCount, &lookupCount, &negLookup, target](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([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
- if (downServers.find(ip) != downServers.end()) {
+ if (downServers.find(address) != downServers.end()) {
downCount++;
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
if (negLookup) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, "powerdns.com.", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL);
lookupCount++;
return LWResult::Result::Success;
}
- else {
+ {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, theTTL);
lookupCount++;
const int theTTL = 5;
const int negTTL = 60;
- sr->setAsyncCallback([&downServers, &downCount, &lookupCount, &cnameOK, target, auth](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([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
- if (downServers.find(ip) != downServers.end()) {
+ if (downServers.find(address) != downServers.end()) {
downCount++;
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
if (cnameOK) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::CNAME, "cname.powerdns.com.", DNSResourceRecord::ANSWER, 5);
lookupCount++;
return LWResult::Result::Success;
}
- else {
- setLWResult(res, RCode::NXDomain, true, false, true);
- addRecordToLW(res, auth, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL);
- lookupCount++;
- return LWResult::Result::Success;
- }
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, auth, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL);
+ lookupCount++;
+ return LWResult::Result::Success;
}
else {
return LWResult::Result::Timeout;
BOOST_CHECK_EQUAL(lookupCount, 3U);
}
+BOOST_AUTO_TEST_CASE(test_servestale_cname_to_nodata)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr);
+ MemRecursorCache::s_maxServedStaleExtensions = 1440;
+ NegCache::s_maxServedStaleExtensions = 1440;
+
+ primeHints();
+
+ const DNSName target("www.powerdns.com.");
+ const DNSName auth("powerdns.com.");
+
+ std::set<ComboAddress> downServers;
+ size_t downCount = 0;
+ size_t lookupCount = 0;
+ bool cnameOK = true;
+
+ const time_t theTTL = 5;
+ const time_t negTTL = 60;
+
+ sr->setAsyncCallback([&](const ComboAddress& ipAddress, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ /* this will cause issue with qname minimization if we ever implement it */
+ if (downServers.find(ipAddress) != downServers.end()) {
+ downCount++;
+ return LWResult::Result::Timeout;
+ }
+
+ if (isRootServer(ipAddress)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
+ return LWResult::Result::Success;
+ }
+ if (ipAddress == ComboAddress("192.0.2.1:53") || ipAddress == ComboAddress("[2001:DB8::1]:53")) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, theTTL);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, theTTL);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, theTTL);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
+ return LWResult::Result::Success;
+ }
+ if (ipAddress == ComboAddress("192.0.2.2:53") || ipAddress == ComboAddress("192.0.2.3:53") || ipAddress == ComboAddress("[2001:DB8::2]:53") || ipAddress == ComboAddress("[2001:DB8::3]:53")) {
+ if (cnameOK) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, target, QType::CNAME, "cname.powerdns.com.", DNSResourceRecord::ANSWER, 5);
+ addRecordToLW(res, DNSName("cname.powerdns.com"), QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, theTTL);
+ lookupCount++;
+ return LWResult::Result::Success;
+ }
+ setLWResult(res, RCode::NoError, true, false, true);
+ addRecordToLW(res, auth, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL);
+ lookupCount++;
+ return LWResult::Result::Success;
+ }
+ return LWResult::Result::Timeout;
+ });
+
+ time_t now = time(nullptr);
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK(ret[1].d_type == QType::A);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+ BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com"));
+ BOOST_CHECK_EQUAL(downCount, 0U);
+ BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+ downServers.insert(ComboAddress("192.0.2.2:53"));
+ downServers.insert(ComboAddress("192.0.2.3:53"));
+ downServers.insert(ComboAddress("[2001:DB8::2]:53"));
+ downServers.insert(ComboAddress("[2001:DB8::3]:53"));
+
+ sr->setNow(timeval{now + theTTL + 1, 0});
+
+ // record is expired, so serve stale should kick in
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK(ret[1].d_type == QType::A);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+ BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com"));
+ BOOST_CHECK_EQUAL(downCount, 8U);
+ BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+ // Again, no lookup as the record is marked stale
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK(ret[1].d_type == QType::A);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+ BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com"));
+ BOOST_CHECK_EQUAL(downCount, 8U);
+ BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+ // Again, no lookup as the record is marked stale but as the TTL has passed a task should have been pushed
+ sr->setNow(timeval{now + 2 * (theTTL + 1), 0});
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK(ret[1].d_type == QType::A);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+ BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com"));
+ BOOST_CHECK_EQUAL(downCount, 8U);
+ BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+ BOOST_REQUIRE_EQUAL(getTaskSize(), 2U);
+ auto task = taskQueuePop();
+ BOOST_CHECK(task.d_qname == target);
+ BOOST_CHECK_EQUAL(task.d_qtype, QType::CNAME);
+ task = taskQueuePop();
+ BOOST_CHECK(task.d_qname == DNSName("cname.powerdns.com"));
+ BOOST_CHECK_EQUAL(task.d_qtype, QType::A);
+
+ // Now simulate a succeeding task execution and NoDATA on explicit CNAME result becomes available
+ cnameOK = false;
+ sr->setNow(timeval{now + 3 * (theTTL + 1), 0});
+ downServers.clear();
+ sr->setRefreshAlmostExpired(true);
+
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::CNAME), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+ BOOST_CHECK(ret[0].d_type == QType::SOA);
+ BOOST_CHECK_EQUAL(ret[0].d_name, auth);
+ BOOST_CHECK_EQUAL(downCount, 8U);
+ BOOST_CHECK_EQUAL(lookupCount, 3U);
+
+ // And again, result should come from cache
+ sr->setRefreshAlmostExpired(false);
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::CNAME), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+ BOOST_CHECK(ret[0].d_type == QType::SOA);
+ BOOST_CHECK_EQUAL(ret[0].d_name, auth);
+ BOOST_CHECK_EQUAL(downCount, 8U);
+ BOOST_CHECK_EQUAL(lookupCount, 3U);
+}
+
BOOST_AUTO_TEST_CASE(test_servestale_immediateservfail)
{
std::unique_ptr<SyncRes> sr;
const int theTTL = 5;
- sr->setAsyncCallback([&downServers, &downCount, &lookupCount, target](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([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
- if (downServers.find(ip) != downServers.end()) {
+ if (downServers.find(address) != downServers.end()) {
downCount++;
throw ImmediateServFailException("FAIL!");
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, 5);
lookupCount++;
const DNSName target1("powerdns.com.");
const DNSName target2("pdns.com.");
- sr->setAsyncCallback([=](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
if (domain != target1 && domain != target2) {
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
if (domain == target1) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
}
return LWResult::Result::Timeout;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
if (domain == target1) {
addRecordToLW(res, target1, QType::A, "192.0.2.4");
const DNSName target1("powerdns.com.");
const DNSName target2("pdns.com.");
- sr->setAsyncCallback([=](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
if (domain != target1 && domain != target2) {
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
if (domain == target1) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
}
return LWResult::Result::Timeout;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
if (domain == target1) {
addRecordToLW(res, target1, QType::A, "192.0.2.4");
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([=](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
/* this will cause issue with qname minimization if we ever implement it */
if (domain != target) {
return LWResult::Result::Timeout;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, target, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
if (type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4");
return LWResult::Result::Success;
}
- else if (type == QType::NS) {
+ if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::ANSWER, 172800);
addRecordToLW(res, target, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::ANSWER, 172800);
BOOST_CHECK_GT(secondTTL, 0);
}
+BOOST_AUTO_TEST_CASE(test_nodata_ok)
+{
+ vector<DNSRecord> vec;
+ vec.emplace_back("nz.compass.com", nullptr, QType::CNAME, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+ vec.emplace_back("nz.compass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+ vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::NSEC3, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+ vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+
+ BOOST_CHECK(SyncRes::answerIsNOData(QType::A, RCode::NoError, vec));
+}
+
+BOOST_AUTO_TEST_CASE(test_nodata_not)
+{
+ vector<DNSRecord> vec;
+ vec.emplace_back("kc-pro.westeurope.cloudapp.azure.com", nullptr, QType::A, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+ vec.emplace_back("nz.compass.com", nullptr, QType::CNAME, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+ vec.emplace_back("nz.compass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+ vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::NSEC3, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+ vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+
+ BOOST_CHECK(!SyncRes::answerIsNOData(QType::A, RCode::NoError, vec));
+}
+
+BOOST_AUTO_TEST_CASE(test_nodata_out_of_order)
+{
+ vector<DNSRecord> vec;
+ vec.emplace_back("nz.compass.com", nullptr, QType::CNAME, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+ vec.emplace_back("nz.compass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+ vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::NSEC3, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+ vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+ vec.emplace_back("kc-pro.westeurope.cloudapp.azure.com", nullptr, QType::A, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+
+ BOOST_CHECK(!SyncRes::answerIsNOData(QType::A, RCode::NoError, vec));
+}
+
BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
size_t queries = 0;
const DNSName target("www.powerdns.com.");
- sr->setAsyncCallback([target, &queries](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain == DNSName("www.powerdns.com.")) {
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, false);
if (domain == DNSName("www.powerdns.com.")) {
addRecordToLW(res, domain, QType::A, "192.0.2.2");
const DNSName target2("powerdns.org.");
size_t queriesToNS = 0;
- sr->setAsyncCallback([target1, target2, &queriesToNS](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesToNS++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain.isPartOf(DNSName("com."))) {
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
if (domain.isPartOf(target1)) {
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;
}
- else if (domain.isPartOf(target2)) {
+ if (domain.isPartOf(target2)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.org.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.org.", QType::NS, "ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
const DNSName target2("powerdns.org.");
size_t queriesToNS = 0;
- sr->setAsyncCallback([target1, target2, &queriesToNS](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesToNS++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain.isPartOf(DNSName("com."))) {
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
if (domain.isPartOf(target1)) {
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;
}
- else if (domain.isPartOf(target2)) {
+ if (domain.isPartOf(target2)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.org.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.org.", QType::NS, "ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
size_t queryCount = 0;
- sr->setAsyncCallback([target, &queryCount](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
if (domain.isPartOf(DNSName("com."))) {
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
if (domain == target) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
+ if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
queryCount++;
setLWResult(res, 0, true, false, true);
if (queryCount > 8) {
setLWResult(res, RCode::NXDomain, false, false, true);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4");
return LWResult::Result::Success;
}
- else
- return LWResult::Result::Timeout;
+ return LWResult::Result::Timeout;
});
SyncRes::s_nonresolvingnsmaxfails = 10;
size_t queries = 0;
const DNSName target("cname.powerdns.com.");
- sr->setAsyncCallback([target, &queries](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queries++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::CNAME, std::to_string(queries) + "-cname.powerdns.com");
const ComboAddress ns("192.0.2.1:53");
size_t queriesToNS = 0;
- sr->setAsyncCallback([target, ns, &queriesToNS](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
queriesToNS++;
const ComboAddress ns("192.0.2.1:53");
size_t queriesToNS = 0;
- sr->setAsyncCallback([target, ns, &queriesToNS](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
queriesToNS++;
const ComboAddress ns("192.0.2.1:53");
size_t queriesCount = 0;
- sr->setAsyncCallback([target1, target2, ns, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain == target1) {
setLWResult(res, RCode::NXDomain, true, false, true);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
/* This time the root denies target1 with a "com." SOA instead of a "." one.
We should add target1 to the negcache, but not "com.". */
- sr->setAsyncCallback([target1, target2, ns, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain == target1) {
setLWResult(res, RCode::NXDomain, true, false, true);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
const ComboAddress ns("192.0.2.1:53");
size_t queriesCount = 0;
- sr->setAsyncCallback([target1, target2, ns, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain == target1) {
setLWResult(res, RCode::NXDomain, true, false, true);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
const ComboAddress ns("192.0.2.1:53");
size_t queriesCount = 0;
- sr->setAsyncCallback([ns, &queriesCount](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([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "ns1.powerdns.com.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, RCode::NXDomain, true, false, false);
addRecordToLW(res, "powerdns.com.", QType::SOA, "ns1.powerdns.com. hostmaster.powerdns.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
return LWResult::Result::Success;
size_t queriesCount = 0;
- sr->setAsyncCallback([target1, target2, target3, target4, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
addRRSIG(keys, res->d_records, auth, 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
if (domain == DNSName("powerdns.com.")) {
const ComboAddress ns("192.0.2.1:53");
size_t queriesCount = 0;
- sr->setAsyncCallback([ns, target1, target2, target3, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "ns1.powerdns.com.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
if (domain == target1) { // NODATA for TXT, NOERROR for A
if (type == QType::TXT) {
setLWResult(res, RCode::NoError, true);
const ComboAddress ns("192.0.2.1:53");
size_t queriesCount = 0;
- sr->setAsyncCallback([ns, target1, target2, target3, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "ns1.powerdns.com.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
if (domain == target1) { // NODATA for TXT, NOERROR for A
if (type == QType::TXT) {
setLWResult(res, RCode::NoError, true);
incomingECS.source = Netmask("192.0.2.128/32");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
- sr->setAsyncCallback([target, cnameTarget](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
/* Type 2 NXDOMAIN (rfc2308 section-2.1) */
setLWResult(res, RCode::NXDomain, true, false, true);
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::s_ecsipv4cachelimit = 24;
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::s_ecsipv4cachelimit = 16;
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::s_ecscachelimitttl = 30;
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
SyncRes::s_ecscachelimitttl = 100;
SyncRes::s_ecsipv4cachelimit = 24;
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
SyncRes::s_ecscachelimitttl = 100;
SyncRes::s_ecsipv4cachelimit = 16;
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
std::map<ComboAddress, uint64_t> nsCounts;
- sr->setAsyncCallback([target, &nsCounts](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, domain, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else {
- nsCounts[ip]++;
+ {
+ nsCounts[address]++;
- if (ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("192.0.2.2:53")) {
BOOST_CHECK_LT(nsCounts.size(), 3U);
/* let's time out on pdns-public-ns2.powerdns.com. */
return LWResult::Result::Timeout;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
BOOST_CHECK_EQUAL(nsCounts.size(), 3U);
setLWResult(res, 0, true, false, true);
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.254");
return LWResult::Result::Success;
const DNSName target("powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount, target](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip) && domain == target) {
+ if (isRootServer(address) && domain == target) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, domain, QType::NS, "pdns-public-ns3.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else if (domain == DNSName("pdns-public-ns2.powerdns.com.") || domain == DNSName("pdns-public-ns3.powerdns.com.")) {
+ if (domain == DNSName("pdns-public-ns2.powerdns.com.") || domain == DNSName("pdns-public-ns3.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
return LWResult::Result::Success;
const DNSName target("powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount, target](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip) && domain == target) {
+ if (isRootServer(address) && domain == target) {
setLWResult(res, 0, false, false, true);
// 20 NS records
for (int i = 0; i < 20; i++) {
}
return LWResult::Result::Success;
}
- else if (domain.toString().length() > 14 && domain.toString().substr(0, 14) == "pdns-public-ns") {
+ if (domain.toString().length() > 14 && domain.toString().substr(0, 14) == "pdns-public-ns") {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
return LWResult::Result::Success;
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
return LWResult::Result::Timeout;
});
sr->setCacheOnly();
- sr->setAsyncCallback([target, &queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
});
const DNSName target("cachettl.powerdns.com.");
const ComboAddress ns("192.0.2.1:53");
- sr->setAsyncCallback([target, ns](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 7200);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10);
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::addEDNSDomain(target);
- sr->setAsyncCallback([target, ns](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10);
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
return LWResult::Result::Success;
const DNSName target("powerdns.com.");
- auto cb = [target](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 */) {
- if (isRootServer(ip)) {
+ auto callback = [&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
return LWResult::Result::Success;
return LWResult::Result::Timeout;
};
- sr->setAsyncCallback(cb);
+ sr->setAsyncCallback(callback);
/* we populate the cache with an 60s TTL entry that is 31s old*/
const time_t now = sr->getNow().tv_sec;
std::vector<shared_ptr<const RRSIGRecordContent>> sigs;
addRecordToList(records, target, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, now + 29);
- g_recCache->replace(now - 30, target, QType(QType::A), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, g_rootdnsname, boost::optional<Netmask>());
+ g_recCache->replace(now - 30, target, QType(QType::A), records, sigs, {}, true, g_rootdnsname, boost::optional<Netmask>(), boost::none, vState::Indeterminate, boost::none, false, now - 31);
/* Same for the NS record */
std::vector<DNSRecord> ns;
addRecordToList(ns, target, QType::NS, "pdns-public-ns1.powerdns.com", DNSResourceRecord::ANSWER, now + 29);
- g_recCache->replace(now - 30, target, QType::NS, ns, sigs, vector<std::shared_ptr<DNSRecord>>(), false, target, boost::optional<Netmask>());
+ g_recCache->replace(now - 30, target, QType::NS, ns, sigs, {}, false, target, boost::optional<Netmask>(), boost::none, vState::Indeterminate, boost::none, false, now - 31);
vector<DNSRecord> ret;
int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(ttl, 29U);
// One task should be submitted
- BOOST_CHECK_EQUAL(getTaskSize(), 1U);
+ BOOST_REQUIRE_EQUAL(getTaskSize(), 1U);
auto task = taskQueuePop();
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
check that we only return one result, and we only cache one too. */
const DNSName target("cache-auth.powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10);
addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 10);
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
const DNSName target("powerdns.com.");
sr->setAsyncCallback(
- [target](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 */) {
+ [&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */,
+ struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */,
+ LWResult* res, bool* /* chained */) {
setLWResult(res, 0, true, false, true);
return LWResult::Result::Success;
});
const DNSName target2("www2.powerdns.com."); // in bailiwick, but not asked for
const DNSName target3("www.random.net."); // out of bailiwick and not asked for
- sr->setAsyncCallback([target, target2, target3](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
generateKeyMaterial(DNSName("powerdns.com"), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
g_luaconfs.setState(luaconfsCopy);
- sr->setAsyncCallback([target, target2, target3, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRRSIG(keys, res->d_records, DNSName("."), 300);
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
addRecordToLW(res, domain, QType::ANY, "\\# 0");
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
/* the NSEC and RRSIG contents are complete garbage, please ignore them */
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
/* the NSEC and RRSIG contents are complete garbage, please ignore them */
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, RCode::NXDomain, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
/* the NSEC and RRSIG contents are complete garbage, please ignore them */
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, RCode::NXDomain, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
/* the NSEC and RRSIG contents are complete garbage, please ignore them */
/* apart from special names and QClass::ANY, anything else than QClass::IN should be rejected right away */
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
});
const DNSName target("powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.1");
return LWResult::Result::Success;
/* {A,I}XFR, RRSIG and NSEC3 should be rejected right away */
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount](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 */) {
- cerr << "asyncresolve called to ask " << ip.toStringWithPort() << " about " << domain.toString() << " / " << QType(type).toString() << " over " << (doTCP ? "TCP" : "UDP") << " (rd: " << sendRDQuery << ", EDNS0 level: " << EDNS0Level << ")" << endl;
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
+ cerr << "asyncresolve called to ask " << address.toStringWithPort() << " about " << domain.toString() << " / " << QType(type).toString() << " over " << (doTCP ? "TCP" : "UDP") << " (rd: " << sendRDQuery << ", EDNS0 level: " << EDNS0Level << ")" << endl;
queriesCount++;
return LWResult::Result::Timeout;
});
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
});
const DNSName target("rpz.powerdns.com.");
const ComboAddress ns("192.0.2.1:53");
- sr->setAsyncCallback([target, ns](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, false, true, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
const DNSName target("rpz.powerdns.com.");
const ComboAddress ns("[2001:DB8::42]:53");
- sr->setAsyncCallback([target, ns](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
const ComboAddress ns("192.0.2.1:53");
const DNSName nsName("ns1.powerdns.com.");
- sr->setAsyncCallback([target, ns, nsName](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
const ComboAddress ns("192.0.2.1:53");
const DNSName nsName("ns1.powerdns.com.");
- sr->setAsyncCallback([target, ns, nsName](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 */) {
- if (isRootServer(ip)) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ns) {
+ if (address == ns) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
size_t queriesCount = 0;
- sr->setAsyncCallback([forwardedNS, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
++queriesCount;
- if (ip == forwardedNS) {
+ if (address == forwardedNS) {
BOOST_CHECK_EQUAL(sendRDQuery, false);
setLWResult(res, 0, true, false, true);
ad.d_servers.push_back(forwardedNS);
(*SyncRes::t_sstorage.domainmap)[target] = ad;
- sr->setAsyncCallback([forwardedNS, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (ip == forwardedNS) {
+ if (address == forwardedNS) {
BOOST_CHECK_EQUAL(sendRDQuery, true);
/* set AA=0, we are a recursor */
size_t queriesCount = 0;
- sr->setAsyncCallback([forwardedNS, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
++queriesCount;
- if (ip == forwardedNS) {
+ if (address == forwardedNS) {
BOOST_CHECK_EQUAL(sendRDQuery, true);
setLWResult(res, 0, true, false, true);
ad.d_servers.push_back(forwardedNS);
(*SyncRes::t_sstorage.domainmap)[target] = ad;
- sr->setAsyncCallback([forwardedNS](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 */) {
- if (ip == forwardedNS) {
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ if (address == forwardedNS) {
BOOST_CHECK_EQUAL(sendRDQuery, true);
setLWResult(res, 0, true, false, true);
ad.d_servers.push_back(forwardedNS);
(*SyncRes::t_sstorage.domainmap)[g_rootdnsname] = ad;
- sr->setAsyncCallback([target, cnameTarget, keys, forwardedNS, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
BOOST_CHECK_EQUAL(sendRDQuery, true);
- if (ip != forwardedNS) {
+ if (address != forwardedNS) {
return LWResult::Result::Timeout;
}
ad.d_servers.push_back(forwardedNS);
(*SyncRes::t_sstorage.domainmap)[DNSName("test.")] = ad;
- sr->setAsyncCallback([parent, target1, target2, keys, forwardedNS, &queriesCount, &DSforParentCount](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
BOOST_CHECK_EQUAL(sendRDQuery, false);
if (domain != parent && domain.isPartOf(parent)) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false /* no cut / delegation */);
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, parent, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 42);
addRRSIG(keys, res->d_records, g_rootdnsname, 300);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
}
- if (ip != forwardedNS) {
+ if (address != forwardedNS) {
return LWResult::Result::Timeout;
}
ad.d_servers.push_back(forwardedNS);
(*SyncRes::t_sstorage.domainmap)[g_rootdnsname] = ad;
- sr->setAsyncCallback([target, cnameTarget, keys, forwardedNS, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
BOOST_CHECK_EQUAL(sendRDQuery, true);
- if (ip != forwardedNS) {
+ if (address != forwardedNS) {
return LWResult::Result::Timeout;
}
size_t queriesCount = 0;
- sr->setAsyncCallback([target, forwardedNS, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
BOOST_CHECK_EQUAL(sendRDQuery, true);
- if (ip != forwardedNS) {
+ if (address != forwardedNS) {
return LWResult::Result::Timeout;
}
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
-
- setLWResult(res, 0, false, false, true);
- return LWResult::Result::Success;
- }
- return LWResult::Result::Timeout;
+ setLWResult(res, 0, false, false, true);
+ return LWResult::Result::Success;
});
vector<DNSRecord> ret;
BOOST_CHECK_EQUAL(queriesCount, 4U);
}
+BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_cname_wildcard_expanded)
+{
+ std::unique_ptr<SyncRes> testSR;
+ initSR(testSR, true);
+
+ setDNSSECValidation(testSR, DNSSECMode::ValidateAll);
+
+ primeHints();
+ /* unsigned */
+ const DNSName target("test.");
+ /* signed */
+ const DNSName cnameTarget("cname.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(cnameTarget, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+ g_luaconfs.setState(luaconfsCopy);
+
+ const ComboAddress forwardedNS("192.0.2.42:53");
+ size_t queriesCount = 0;
+
+ SyncRes::AuthDomain authDomain;
+ authDomain.d_rdForward = true;
+ authDomain.d_servers.push_back(forwardedNS);
+ (*SyncRes::t_sstorage.domainmap)[g_rootdnsname] = authDomain;
+
+ testSR->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ queriesCount++;
+
+ BOOST_CHECK_EQUAL(sendRDQuery, true);
+
+ if (address != forwardedNS) {
+ return LWResult::Result::Timeout;
+ }
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+ }
+
+ if (domain == target && type == QType::A) {
+
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, target, QType::CNAME, cnameTarget.toString());
+ addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
+ /* the RRSIG proves that the cnameTarget was expanded from a wildcard */
+ addRRSIG(keys, res->d_records, cnameTarget, 300, false, boost::none, DNSName("*"));
+ /* we need to add the proof that this name does not exist, so the wildcard may apply */
+ addNSECRecordToLW(DNSName("cnamd."), DNSName("cnamf."), {QType::A, QType::NSEC, QType::RRSIG}, 60, res->d_records);
+ addRRSIG(keys, res->d_records, cnameTarget, 300);
+
+ return LWResult::Result::Success;
+ }
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = testSR->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(testSR->getValidationState(), vState::Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 5U);
+ BOOST_CHECK_EQUAL(queriesCount, 5U);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = testSR->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(testSR->getValidationState(), vState::Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 5U);
+ BOOST_CHECK_EQUAL(queriesCount, 5U);
+}
+
BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
{
std::unique_ptr<SyncRes> sr;
(*SyncRes::t_sstorage.domainmap)[authZone] = ad;
- sr->setAsyncCallback([&queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
});
(*SyncRes::t_sstorage.domainmap)[authZone] = ad;
- sr->setAsyncCallback([&queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
});
(*map)[target] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
(*map)[authZone] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount, target, authZone](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target) {
(*map)[target] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount, externalCNAME, addr](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == externalCNAME) {
(*map)[target] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type != QType::DS) {
setLWResult(res, 0, true, false, true);
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
(*map)[authZone] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount](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([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
(*map)[authZone] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount](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([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
takes too long. */
const time_t fixedNow = sr->getNow().tv_sec;
- sr->setAsyncCallback([&queriesCount, target, targetAddr, nsAddr, authZone, keys, fixedNow](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, domain == DNSName("com.") || domain == authZone, fixedNow);
}
- if (ip == ComboAddress(nsAddr.toString(), 53) && domain == target) {
+ if (address == ComboAddress(nsAddr.toString(), 53) && domain == target) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
return LWResult::Result::Success;
(*map)[authZone] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount, nsAddr, target, targetAddr](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (ip == ComboAddress(nsAddr.toString(), 53) && domain == target) {
+ if (address == ComboAddress(nsAddr.toString(), 53) && domain == target) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
return LWResult::Result::Success;
(*map)[authZone] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount](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([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
(*map)[authZone] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount](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([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
(*map)[authZone] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount](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([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
queriesCount++;
return LWResult::Result::Timeout;
(*map)[target] = ad;
SyncRes::setDomainMap(map);
- sr->setAsyncCallback([&queriesCount](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([&queriesCount](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
dcke->create(dcke->getBits());
- // cerr<<dcke->convertToISC()<<endl;
DNSSECPrivateKey dpk;
dpk.setKey(std::move(dcke), 256);
std::vector<std::shared_ptr<const RRSIGRecordContent>> sigs;
sigs.push_back(std::make_shared<RRSIGRecordContent>(rrc));
- BOOST_CHECK(validateWithKeySet(now, qname, recordcontents, sigs, keyset, std::nullopt) == vState::Secure);
+ pdns::validation::ValidationContext validationContext;
+ BOOST_CHECK(validateWithKeySet(now, qname, recordcontents, sigs, keyset, std::nullopt, validationContext) == vState::Secure);
+ BOOST_CHECK_EQUAL(validationContext.d_validationsCounter, 1U);
}
BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_csk)
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, zskeys, kskeys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
BOOST_REQUIRE_EQUAL(ret.size(), 14U);
BOOST_CHECK_EQUAL(queriesCount, 2U);
}
+
BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
{
std::unique_ptr<SyncRes> sr;
dcke->create(dcke->getBits());
DNSSECPrivateKey dpk;
dpk.setKey(std::move(dcke), 256);
- DSRecordContent uselessdrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+ DSRecordContent seconddrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
dskeys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dskey, drc);
- keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, uselessdrc);
+ keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, seconddrc);
/* Set the root DS */
auto luaconfsCopy = g_luaconfs.getCopy();
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
BOOST_CHECK_EQUAL(queriesCount, 4U);
}
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_dss)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::Process);
+
+ primeHints();
+ const DNSName target(".");
+ testkeysset_t keys;
+
+ g_maxDSsToConsider = 1;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ /* generate more DSs for the zone than we are willing to consider: only the last one will be used to generate DNSKEY records */
+ for (size_t idx = 0; idx < (g_maxDSsToConsider + 10U); idx++) {
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::DIGEST_SHA384, keys, luaconfsCopy.dsAnchors);
+ }
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys](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 */) {
+ queriesCount++;
+
+ if (domain == target && type == QType::NS) {
+
+ setLWResult(res, 0, true, false, true);
+ char addr[] = "a.root-servers.net.";
+ for (char idx = 'a'; idx <= 'm'; idx++) {
+ addr[0] = idx;
+ addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600);
+ }
+
+ addRRSIG(keys, res->d_records, domain, 300);
+
+ addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return LWResult::Result::Success;
+ }
+ else if (domain == target && type == QType::DNSKEY) {
+
+ setLWResult(res, 0, true, false, true);
+
+ addDNSKEY(keys, domain, 300, res->d_records);
+ addRRSIG(keys, res->d_records, domain, 300);
+
+ return LWResult::Result::Success;
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ /* === with validation enabled === */
+ sr->setDNSSECValidationRequested(true);
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusNoValidDNSKEY);
+ /* 13 NS + 1 RRSIG */
+ BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+ BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusNoValidDNSKEY);
+ BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+ BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+ g_maxDNSKEYsToConsider = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_dnskeys)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::Process);
+
+ primeHints();
+ const DNSName target(".");
+ testkeysset_t dskeys;
+ testkeysset_t keys;
+
+ DNSKEYRecordContent dnskeyRecordContent;
+ dnskeyRecordContent.d_algorithm = 13;
+ /* Generate key material for "." */
+ auto dckeDS = DNSCryptoKeyEngine::makeFromISCString(dnskeyRecordContent, R"PKEY(Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: Ovj4pzrSh0U6aEVoKaPFhK1D4NMG0xrymj9+6TpwC8o=)PKEY");
+ DNSSECPrivateKey dskey;
+ dskey.setKey(std::move(dckeDS), 257);
+ assert(dskey.getTag() == 31337);
+ DSRecordContent drc = makeDSFromDNSKey(target, dskey.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+ dskeys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dskey, drc);
+
+ /* Different key, same tag */
+ auto dcke = DNSCryptoKeyEngine::makeFromISCString(dnskeyRecordContent, R"PKEY(Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: n7SRA4n6NejhZBWQOhjTaICYSpkTl6plJn1ATFG23FI=)PKEY");
+ DNSSECPrivateKey dpk;
+ dpk.setKey(std::move(dcke), 256);
+ assert(dpk.getTag() == dskey.getTag());
+ DSRecordContent uselessdrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+ keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, uselessdrc);
+
+ /* Set the root DS (one of them!) */
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ luaconfsCopy.dsAnchors[g_rootdnsname].insert(drc);
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys, dskeys](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 */) {
+ queriesCount++;
+
+ if (domain == target && type == QType::NS) {
+
+ setLWResult(res, 0, true, false, true);
+ char addr[] = "a.root-servers.net.";
+ for (char idx = 'a'; idx <= 'm'; idx++) {
+ addr[0] = idx;
+ addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600);
+ }
+
+ addRRSIG(dskeys, res->d_records, domain, 300);
+
+ addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return LWResult::Result::Success;
+ }
+ else if (domain == target && type == QType::DNSKEY) {
+
+ setLWResult(res, 0, true, false, true);
+
+ addDNSKEY(keys, domain, 300, res->d_records);
+ addDNSKEY(dskeys, domain, 300, res->d_records);
+ addRRSIG(keys, res->d_records, domain, 300);
+ addRRSIG(dskeys, res->d_records, domain, 300);
+
+ return LWResult::Result::Success;
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ g_maxDNSKEYsToConsider = 1;
+
+ /* === with validation enabled === */
+ sr->setDNSSECValidationRequested(true);
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusNoValidDNSKEY);
+ /* 13 NS + 1 RRSIG */
+ BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+ BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusNoValidDNSKEY);
+ BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+ BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+ g_maxDNSKEYsToConsider = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_dnskeys_while_checking_signature)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::Process);
+
+ primeHints();
+ const DNSName target(".");
+ testkeysset_t dskeys;
+ testkeysset_t keys;
+ testkeysset_t otherkeys;
+
+ DNSKEYRecordContent dnskeyRecordContent;
+ dnskeyRecordContent.d_algorithm = 13;
+ /* Generate key material for "." */
+ auto dckeDS = DNSCryptoKeyEngine::makeFromISCString(dnskeyRecordContent, R"PKEY(Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: Ovj4pzrSh0U6aEVoKaPFhK1D4NMG0xrymj9+6TpwC8o=)PKEY");
+ DNSSECPrivateKey dskey;
+ dskey.setKey(std::move(dckeDS), 257);
+ assert(dskey.getTag() == 31337);
+ DSRecordContent drc = makeDSFromDNSKey(target, dskey.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+ dskeys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dskey, drc);
+
+ /* Different key, same tag */
+ auto dcke = DNSCryptoKeyEngine::makeFromISCString(dnskeyRecordContent, R"PKEY(Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: pTaMJcvNrPIIiQiHGvCLZZASroyQpUwew5FvCgjHNsk=)PKEY");
+ DNSSECPrivateKey dpk;
+ // why 258, you may ask? We need this record to be sorted AFTER the other one in a sortedRecords_t
+ // so that the validation of the DNSKEY rrset succeeds
+ dpk.setKey(std::move(dcke), 258);
+ assert(dpk.getTag() == dskey.getTag());
+ DSRecordContent uselessdrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+ keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, uselessdrc);
+
+ /* Set the root DSs (only one of them) */
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ luaconfsCopy.dsAnchors[g_rootdnsname].insert(drc);
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys, dskeys](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 */) {
+ queriesCount++;
+
+ if (domain == target && type == QType::NS) {
+
+ setLWResult(res, 0, true, false, true);
+ char addr[] = "a.root-servers.net.";
+ for (char idx = 'a'; idx <= 'm'; idx++) {
+ addr[0] = idx;
+ addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600);
+ }
+
+ addRRSIG(keys, res->d_records, domain, 300);
+ addRRSIG(dskeys, res->d_records, domain, 300);
+
+ addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return LWResult::Result::Success;
+ }
+ else if (domain == target && type == QType::DNSKEY) {
+
+ setLWResult(res, 0, true, false, true);
+
+ addDNSKEY(dskeys, domain, 300, res->d_records);
+ addDNSKEY(keys, domain, 300, res->d_records);
+ addRRSIG(dskeys, res->d_records, domain, 300);
+
+ return LWResult::Result::Success;
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ g_maxDNSKEYsToConsider = 1;
+
+ /* === with validation enabled === */
+ sr->setDNSSECValidationRequested(true);
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusNoValidRRSIG);
+ /* 13 NS + 1 RRSIG */
+ BOOST_REQUIRE_EQUAL(ret.size(), 15U);
+ BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusNoValidRRSIG);
+ BOOST_REQUIRE_EQUAL(ret.size(), 15U);
+ BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+ g_maxDNSKEYsToConsider = 0;
+}
+
BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey)
{
std::unique_ptr<SyncRes> sr;
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys, rrsigkeys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys, fixedNow](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
BOOST_CHECK_EQUAL(queriesCount, 2U);
}
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_sigs)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target(".");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::DIGEST_SHA384, keys, luaconfsCopy.dsAnchors);
+
+ g_luaconfs.setState(luaconfsCopy);
+ /* make sure that the signature inception and validity times are computed
+ based on the SyncRes time, not the current one, in case the function
+ takes too long. */
+ const time_t fixedNow = sr->getNow().tv_sec;
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys, fixedNow](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 */) {
+ queriesCount++;
+
+ if (domain == target && type == QType::NS) {
+
+ setLWResult(res, 0, true, false, true);
+ char addr[] = "a.root-servers.net.";
+ for (char idx = 'a'; idx <= 'm'; idx++) {
+ addr[0] = idx;
+ addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600);
+ }
+
+ addRRSIG(keys, res->d_records, domain, 300, true, boost::none, boost::none, fixedNow);
+ addRRSIG(keys, res->d_records, domain, 300, true, boost::none, boost::none, fixedNow);
+ addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+
+ addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return LWResult::Result::Success;
+ }
+ else if (domain == target && type == QType::DNSKEY) {
+
+ setLWResult(res, 0, true, false, true);
+
+ addDNSKEY(keys, domain, 300, res->d_records);
+ addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+
+ return LWResult::Result::Success;
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ g_maxRRSIGsPerRecordToConsider = 2;
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusNoValidRRSIG);
+ /* 13 NS + 1 RRSIG */
+ BOOST_REQUIRE_EQUAL(ret.size(), 16U);
+ BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusNoValidRRSIG);
+ BOOST_REQUIRE_EQUAL(ret.size(), 16U);
+ BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+ g_maxRRSIGsPerRecordToConsider = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_sig_validations)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target(".");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::DIGEST_SHA384, keys, luaconfsCopy.dsAnchors);
+
+ g_luaconfs.setState(luaconfsCopy);
+ /* make sure that the signature inception and validity times are computed
+ based on the SyncRes time, not the current one, in case the function
+ takes too long. */
+ const time_t fixedNow = sr->getNow().tv_sec;
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys, fixedNow](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 */) {
+ queriesCount++;
+
+ if (domain == target && type == QType::NS) {
+
+ setLWResult(res, 0, true, false, true);
+ char addr[] = "a.root-servers.net.";
+ for (char idx = 'a'; idx <= 'm'; idx++) {
+ addr[0] = idx;
+ addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600);
+ }
+
+ addRRSIG(keys, res->d_records, domain, 300, true, boost::none, boost::none, fixedNow);
+ addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+
+ addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return LWResult::Result::Success;
+ }
+ else if (domain == target && type == QType::DNSKEY) {
+
+ setLWResult(res, 0, true, false, true);
+
+ addDNSKEY(keys, domain, 300, res->d_records);
+ addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+
+ return LWResult::Result::Success;
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ SyncRes::s_maxvalidationsperq = 1U;
+
+ vector<DNSRecord> ret;
+ BOOST_REQUIRE_THROW(sr->beginResolve(target, QType(QType::NS), QClass::IN, ret), ImmediateServFailException);
+
+ SyncRes::s_maxvalidationsperq = 0U;
+}
+
BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_algo)
{
std::unique_ptr<SyncRes> sr;
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return LWResult::Result::Success;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
/* Include the DS but omit the RRSIG*/
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, RCode::NoError, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, auth, 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return LWResult::Result::Success;
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
/* Include the DS but omit the RRSIG*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
const time_t fixedNow = sr->getNow().tv_sec;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, fixedNow](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, fixedNow](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
addRRSIG(keys, res->d_records, auth, 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
if (domain == DNSName("powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
/* no data */
addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
/* no data */
addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
BOOST_CHECK_EQUAL(queriesCount, 4U);
}
-BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_duplicated_nsec3)
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_too_many_nsec3s)
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
return LWResult::Result::Success;
}
else if (ip == ComboAddress("192.0.2.1:53")) {
+ setLWResult(res, 0, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* no record for this name */
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* then the next closer */
+ addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* a wildcard matches but has no record for this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+ return LWResult::Result::Success;
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ /* we allow at most 2 NSEC3s, but we need at least 3 of them to
+ get a valid denial so we will go Bogus */
+ g_maxNSEC3sPerRecordToConsider = 2;
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusInvalidDenial);
+ BOOST_REQUIRE_EQUAL(ret.size(), 8U);
+ BOOST_CHECK_EQUAL(queriesCount, 5U);
+
+ g_maxNSEC3sPerRecordToConsider = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_too_many_nsec3s_per_query)
+{
+ SyncRes::s_maxnsec3iterationsperq = 20;
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("www.com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys](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 */) {
+ queriesCount++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (type == QType::DS && domain == target) {
+ DNSName auth("com.");
+ setLWResult(res, 0, true, false, true);
+
+ addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* add a NSEC3 denying the DS AND the existence of a cut (no NS) */
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), auth, "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* then the next closer */
+ addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* a wildcard matches but has no record for this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+ return LWResult::Result::Success;
+ }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ setLWResult(res, 0, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* no record for this name */
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* then the next closer */
+ addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* a wildcard matches but has no record for this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+ return LWResult::Result::Success;
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ BOOST_CHECK_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), pdns::validation::TooManySEC3IterationsException);
+
+ SyncRes::s_maxnsec3iterationsperq = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_duplicated_nsec3)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("www.com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+ queriesCount++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (type == QType::DS && domain == target) {
+ DNSName auth("com.");
+ setLWResult(res, 0, true, false, true);
+
+ addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* add a NSEC3 denying the DS AND the existence of a cut (no NS) */
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), auth, "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* then the next closer */
+ addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* a wildcard matches but has no record for this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+ return LWResult::Result::Success;
+ }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ {
+ if (isRootServer(address)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
/* no data */
addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
/* no data */
addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("powerdns.com.")) {
g_luaconfs.setState(luaconfsCopy);
- sr->setAsyncCallback([target, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (type == QType::DS || type == QType::DNSKEY) {
if (domain == target) {
const auto auth = DNSName("powerdns.com.");
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
+ {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
g_luaconfs.setState(luaconfsCopy);
- sr->setAsyncCallback([target, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (type == QType::DS || type == QType::DNSKEY) {
if (domain == target) {
const auto auth = DNSName("powerdns.com.");
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
+ {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "powerdns.com. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
g_luaconfs.setState(luaconfsCopy);
- sr->setAsyncCallback([target, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (type == QType::DS || type == QType::DNSKEY) {
if (domain == target) {
const auto auth = DNSName("powerdns.com.");
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com"));
return LWResult::Result::Success;
}
- else if (domain == DNSName("sub.powerdns.com.")) {
+ if (domain == DNSName("sub.powerdns.com.")) {
const auto auth = DNSName("powerdns.com.");
/* we don't want a cut there */
setLWResult(res, 0, true, false, true);
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
+ {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
size_t queriesCount = 0;
- sr->setAsyncCallback([&queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth(domain);
DNSName com("com.");
DNSName net("net.");
+ DNSName nsone("nsone.net.");
+ DNSName hero("herokuapp.com.");
+ DNSName p03nsone("dns1.p03.nsone.net.");
- //cerr << ip.toString() << ": " << domain << '|' << QType(type).getName() << endl;
+ // cerr << ip.toString() << ": " << domain << '|' << QType(type).toString() << endl;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain == com) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, com, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, g_rootdnsname, 300);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
}
+ else if (domain == p03nsone && type == QType::A) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, nsone, QType::NS, "dns1.p01.nsone.net.", DNSResourceRecord::AUTHORITY, 3600);
+ addNSECRecordToLW(nsone, DNSName("zzz.nsone.net."), {QType::NS, QType::SOA, QType::RRSIG, QType::DNSKEY}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, net, 300);
+ addRecordToLW(res, "dns1.p01.nsone.net", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ }
else {
BOOST_ASSERT(0);
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
- DNSName hero("herokuapp.com.");
- DNSName nsone("nsone.net.");
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == hero && type == QType::NS) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, hero, QType::NS, "dns1.p03.nsone.net.", DNSResourceRecord::AUTHORITY, 3600);
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
- DNSName hero("herokuapp.com.");
+ if (address == ComboAddress("192.0.2.2:53")) {
DNSName p01("p01.nsone.net.");
DNSName p03("p03.nsone.net.");
DNSName p01nsone("dns1.p01.nsone.net.");
- DNSName p03nsone("dns1.p03.nsone.net.");
if (domain == hero && type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, hero, QType::NS, "dns1.p03.nsone.net.", DNSResourceRecord::ANSWER, 3600);
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
BOOST_REQUIRE_EQUAL(ret.size(), 2U);
- BOOST_CHECK_EQUAL(queriesCount, 10U);
+ BOOST_CHECK_EQUAL(queriesCount, 8U);
ret.clear();
res = sr->beginResolve(DNSName("dns1.p03.nsone.net."), QType(QType::A), QClass::IN, ret);
const time_t fixedNow = sr->getNow().tv_sec;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, fixedNow](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
const time_t fixedNow = sr->getNow().tv_sec;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, fixedNow](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
const time_t fixedNow = sr->getNow().tv_sec;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, pdnskeys, fixedNow](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
return LWResult::Result::Success;
}
- if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
size_t queriesCount = 0;
size_t dsQueriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, &dsQueriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, auth, 300);
return LWResult::Result::Success;
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
/* No DS on referral, and no denial of the DS either */
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
}
return LWResult::Result::Success;
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
addRRSIG(keys, res->d_records, DNSName("."), 300);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
if (domain == DNSName("powerdns.com.")) {
setLWResult(res, RCode::Refused, false, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
DNSName auth(domain);
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
addRRSIG(keys, res->d_records, DNSName("."), 300);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "insecure.powerdns.", QType::NS, "ns1.insecure.powerdns.", DNSResourceRecord::AUTHORITY, 3600);
/* no DS */
addRecordToLW(res, "ns1.insecure.powerdns.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::DS && domain == target) {
/* in that case we return a NODATA signed by the DNSKEY of the child zone */
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("."), 300);
return LWResult::Result::Success;
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, DNSName("."), 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, auth, 300);
return LWResult::Result::Success;
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
if (domain == DNSName("www.powerdns.com.")) {
}
return LWResult::Result::Success;
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
addRRSIG(keys, res->d_records, DNSName("."), 300);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, auth, 300, false);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == target) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
addRRSIG(keys, res->d_records, domain, 300, false);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
addRRSIG(keys, res->d_records, DNSName("."), 300);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
addRRSIG(keys, res->d_records, domain, 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, auth, 300, false);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */, boost::none, true /* nsec3 */);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */, boost::none, true /* nsec3 */);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == target) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
addRRSIG(keys, res->d_records, domain, 300, false);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */, boost::none, true /* nsec3 */);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */, boost::none, true /* nsec3 */);
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
addRRSIG(keys, res->d_records, DNSName("."), 300);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.42");
addRRSIG(keys, res->d_records, domain, 300);
size_t queriesCount = 0;
size_t dsQueriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, &dsQueriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, auth, 300);
return LWResult::Result::Success;
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
/* No DS on referral, and no denial of the DS either */
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("www.sub.powerdns.com.")) {
+ if (domain == DNSName("www.sub.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("sub.powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("www.sub.powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, /* broken SIG */ true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, pdnskeys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(pdnskeys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == DNSName("powerdns.com.") || domain == DNSName("sub.powerdns.com.")) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, pdnskeys);
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("sub.powerdns.com."), QType::NS, "ns1.sub.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(pdnskeys, res->d_records, DNSName("powerdns.com."), 300);
addRecordToLW(res, "ns1.sub.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.3:53")) {
+ if (address == ComboAddress("192.0.2.3:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(pdnskeys, res->d_records, DNSName("sub.powerdns.com."), 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("www.powerdns.com."), QType::A, "192.168.1.1", DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
}
return LWResult::Result::Success;
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("sub.powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
}
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
/* no DS */
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
if (domain == DNSName("www.sub.powerdns.com.")) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
else if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.")) {
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, false, false, true);
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName1, targetCName2, targetCName2Addr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, false, false, true);
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else {
- if (isRootServer(ip)) {
+ {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DNSKEY) {
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("com.")) {
+ if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, ". yop. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addNSECRecordToLW(DNSName("com."), DNSName("com."), {QType::NS}, 600, res->d_records);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (target == domain) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
}
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DNSKEY) {
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else if (domain == DNSName("com.")) {
+ if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, ". yop. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
}
}
else {
- if (target.isPartOf(domain) && isRootServer(ip)) {
+ if (target.isPartOf(domain) && isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addNSECRecordToLW(DNSName("com."), DNSName("com."), {QType::NS}, 600, res->d_records);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (target == domain) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
}
return LWResult::Result::Success;
}
- else if (domain == target && ip == ComboAddress("192.0.2.2:53")) {
+ if (domain == target && address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::DNSKEY) {
+ if (domain == target && type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (domain == target && type == QType::NS) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::A) {
+ if (type == QType::A) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com"), QType::SOA, "whatever.com. blah.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == target) {
+ if (domain == target) {
setLWResult(res, 0, true, false, true);
return LWResult::Result::Success;
}
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
/* proves cut */
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true);
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "ns1.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == target) {
+ if (domain == target) {
setLWResult(res, 0, true, false, true);
return LWResult::Result::Success;
}
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
/* proves cut */
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true);
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "ns1.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == target) {
+ if (domain == target) {
setLWResult(res, RCode::NXDomain, true, false, true);
return LWResult::Result::Success;
}
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::A) {
+ if (type == QType::A) {
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, DNSName("com"), QType::SOA, "whatever.com. blah.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, DNSName("com."), 300);
return LWResult::Result::Success;
}
- else if (domain == target) {
+ if (domain == target) {
setLWResult(res, RCode::NXDomain, true, false, true);
return LWResult::Result::Success;
}
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
if (domain == DNSName("www.powerdns.com.") || domain == DNSName("www2.powerdns.com.")) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, false, false, true);
if (domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ if (address == ComboAddress("192.0.2.2:53")) {
setLWResult(res, 0, true, false, true);
if (type == QType::NS) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS) {
/* technically the zone is com., but we are going to chop off in genericDSAndDNSKEYHandler() */
return genericDSAndDNSKEYHandler(res, domain, DNSName("powerdns.com."), type, keys, false);
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- else if (type == QType::DNSKEY) {
+ if (type == QType::DNSKEY) {
if (domain == g_rootdnsname || domain == DNSName("com.")) {
setLWResult(res, 0, true, false, true);
addDNSKEY(keys, domain, 300, res->d_records);
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else {
- setLWResult(res, 0, true, false, true);
- addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- return LWResult::Result::Success;
- }
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ return LWResult::Result::Success;
}
else {
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, true, false, true);
if (domain == DNSName("com.")) {
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
BOOST_AUTO_TEST_SUITE(syncres_cc8)
+static dState getDenial(const cspmap_t& validrrsets, const DNSName& qname, uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, const OptLog& log = std::nullopt, bool needWildcardProof = true, unsigned int wildcardLabelsCount = 0)
+{
+ pdns::validation::ValidationContext context;
+ context.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(context.d_nsec3IterationsRemainingQuota)>::max();
+ return getDenial(validrrsets, qname, qtype, referralToUnsigned, wantsNoDataProof, context, log, needWildcardProof, wildcardLabelsCount);
+}
+
BOOST_AUTO_TEST_CASE(test_nsec_denial_nowrap)
{
initSR();
sortedRecords_t recordContents;
vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
-
- addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A}, 600, records);
+ const unsigned int nbIterations = 10;
+ addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A}, 600, records, nbIterations);
recordContents.insert(records.at(0).getContent());
addRRSIG(keys, records, DNSName("powerdns.com."), 300);
signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
denialMap[std::pair(records.at(0).d_name, records.at(0).d_type)] = pair;
records.clear();
+ pdns::validation::ValidationContext validationContext;
+ validationContext.d_nsec3IterationsRemainingQuota = 100U;
/* this NSEC3 is not valid to deny the DS since it is from the child zone */
- BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("powerdns.com."), QType::DS, false, true), dState::NODENIAL);
+ BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("powerdns.com."), QType::DS, false, true, validationContext), dState::NODENIAL);
+ /* the NSEC3 hash is not computed since we it is from the child zone */
+ BOOST_CHECK_EQUAL(validationContext.d_nsec3IterationsRemainingQuota, 100U);
/* AAAA should be fine, though */
- BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("powerdns.com."), QType::AAAA, false, true), dState::NXQTYPE);
+ BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("powerdns.com."), QType::AAAA, false, true, validationContext), dState::NXQTYPE);
+ BOOST_CHECK_EQUAL(validationContext.d_nsec3IterationsRemainingQuota, (100U - nbIterations));
}
BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_cname)
size_t queriesCount = 0;
const time_t fixedNow = sr->getNow().tv_sec;
- sr->setAsyncCallback([target, &queriesCount, keys, fixedNow](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- else {
+ {
setLWResult(res, RCode::NoError, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, domain, 300);
size_t queriesCount = 0;
const time_t fixedNow = sr->getNow().tv_sec;
- sr->setAsyncCallback([&queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- else {
+ {
setLWResult(res, RCode::NoError, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 86400);
addRRSIG(keys, res->d_records, domain, 86400);
size_t queriesCount = 0;
const time_t tnow = sr->getNow().tv_sec;
- sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, tnow](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- else {
+ {
setLWResult(res, RCode::NoError, true, false, true);
addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
addRRSIG(keys, res->d_records, domain, 1, false, boost::none, boost::none, tnow);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
+ {
if (domain == target && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
+ {
if (domain == target && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
+ {
if (domain == target && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, 86400);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
+ {
if (domain == target && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");
addRRSIG(keys, res->d_records, DNSName("."), 300);
return LWResult::Result::Success;
}
- else if (domain == target && type == QType::AAAA) {
+ if (domain == target && type == QType::AAAA) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::AAAA, "2001:db8::1");
addRRSIG(keys, res->d_records, DNSName("."), 300);
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#include <boost/test/unit_test.hpp>
#include "test-syncres_cc.hh"
size_t queriesCount = 0;
- sr->setAsyncCallback([target, cnameTarget, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
+ {
if (domain == target && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::CNAME, cnameTarget.toString());
addRRSIG(keys, res->d_records, DNSName("."), 300);
return LWResult::Result::Success;
}
- else if (domain == cnameTarget && type == QType::A) {
+ if (domain == cnameTarget && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
addRRSIG(keys, res->d_records, DNSName("."), 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, cnameTarget, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
+ {
if (domain == target && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::CNAME, cnameTarget.toString());
addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
return LWResult::Result::Success;
}
- else if (domain == cnameTarget && type == QType::A) {
+ if (domain == cnameTarget && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
return LWResult::Result::Success;
size_t queriesCount = 0;
- sr->setAsyncCallback([target, cnameTarget, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
+ {
if (domain == target && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::CNAME, cnameTarget.toString(), DNSResourceRecord::ANSWER, 86400);
/* no RRSIG */
return LWResult::Result::Success;
}
- else if (domain == cnameTarget && type == QType::A) {
+ if (domain == cnameTarget && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, 86400);
/* no RRSIG */
size_t queriesCount = 0;
- sr->setAsyncCallback([target, addTarget, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
}
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
}
- else {
+ {
if (domain == target && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");
/* no RRSIG for the additional record */
return LWResult::Result::Success;
}
- else if (domain == addTarget && type == QType::A) {
+ if (domain == addTarget && type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, addTarget, QType::A, "192.0.2.42");
addRRSIG(keys, res->d_records, DNSName("."), 300);
size_t queriesCount = 0;
const time_t fixedNow = sr->getNow().tv_sec;
- sr->setAsyncCallback([target, &queriesCount, keys, fixedNow](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- else {
+ {
setLWResult(res, RCode::NoError, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, domain, 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- else {
+ {
setLWResult(res, RCode::NoError, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
return LWResult::Result::Success;
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
DNSName auth = domain;
if (type == QType::DS || type == QType::DNSKEY) {
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
}
- else {
+ {
setLWResult(res, RCode::NoError, true, false, true);
addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 86400);
addRRSIG(keys, res->d_records, domain, 86400);
const DNSName target("WWW.POWERDNS.COM");
const DNSName cname("WWW.PowerDNS.org");
- sr->setAsyncCallback([target, cname, &sentOutQnames](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
sentOutQnames.push_back(domain);
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
if (domain == target) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ else if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::CNAME, cname.toString());
return LWResult::Result::Success;
}
}
- else if (ip == ComboAddress("192.0.2.2:53")) {
+ else if (address == ComboAddress("192.0.2.2:53")) {
if (domain == cname) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "127.0.0.1");
auto rootkey = keys.find(g_rootdnsname);
keys2.insert(*rootkey);
- sr->setAsyncCallback([target, keys, keys2](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
DNSName auth = domain;
auth.chopOff();
if (type == QType::DS || type == QType::DNSKEY) {
// But add the existing root key otherwise no RRSIG can be created
keys3.insert(*rootkey);
- sr->setAsyncCallback([target, keys, keys2, keys3](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
DNSName auth = domain;
auth.chopOff();
if (type == QType::DS || type == QType::DNSKEY) {
// But add the existing root key otherwise no RRSIG can be created
keys3.insert(*rootkey);
- sr->setAsyncCallback([target, keys, keys2, keys3](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([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
DNSName auth = domain;
auth.chopOff();
if (type == QType::DS || type == QType::DNSKEY) {
const DNSName cnameTarget("cname-target.powerdns.com");
size_t queriesCount = 0;
- sr->setAsyncCallback([target, cnameTarget, &queriesCount](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([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, DNSName("powerdns.com"), QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 42);
addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (address == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
addRecordToLW(res, DNSName("a.gtld-servers.net."), QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (domain == cnameTarget) {
+ if (domain == cnameTarget) {
setLWResult(res, 0, true, false, false);
addRecordToLW(res, domain, QType::A, "192.0.2.2");
}
generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
g_luaconfs.setState(luaconfsCopy);
- sr->setAsyncCallback([keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
if (type == QType::DS || type == QType::DNSKEY) {
if (domain == DNSName("cname.powerdns.com.")) {
return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false /* no cut */);
}
- else {
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
- }
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
}
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else {
+ {
setLWResult(res, 0, true, false, true);
if (domain == DNSName("powerdns.com.") && type == QType::A) {
addRecordToLW(res, domain, QType::A, "192.0.2.1");
const DNSName target("sanitization.powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.1");
/* should be scrubbed because it doesn't match the QType */
const DNSName target("sanitization.powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::A, "192.0.2.1");
addRecordToLW(res, domain, QType::AAAA, "2001:db8::1", DNSResourceRecord::ADDITIONAL);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount](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([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (isRootServer(ip)) {
+ if (isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
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;
}
- else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
return LWResult::Result::Success;
}
- else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.4");
addRecordToLW(res, "powerdns.com.", QType::DS, "2 8 2 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBB", DNSResourceRecord::AUTHORITY);
return LWResult::Result::Success;
}
- else {
- return LWResult::Result::Timeout;
- }
+ return LWResult::Result::Timeout;
});
const time_t now = sr->getNow().tv_sec;
const DNSName target("sanitization-ns-nxd.powerdns.com.");
- sr->setAsyncCallback([target](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([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
setLWResult(res, RCode::NXDomain, true, false, true);
addRecordToLW(res, "powerdns.com.", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY);
addRecordToLW(res, "powerdns.com.", QType::NS, "spoofed.ns.", DNSResourceRecord::AUTHORITY, 172800);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (domain.isPartOf(DNSName("signed.ds-ignorant.com.")) && ip == ComboAddress("192.0.2.1:53")) {
+ if (domain.isPartOf(DNSName("signed.ds-ignorant.com.")) && address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "signed.ds-ignorant.com.", QType::NS, "ns.signed.ds-ignorant.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "ns.signed.ds-ignorant.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
+ if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
DNSName auth(domain);
auth.chopOff();
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, false);
}
- else {
- if (domain.isPartOf(DNSName("ds-ignorant.com.")) && isRootServer(ip)) {
+ {
+ if (domain.isPartOf(DNSName("ds-ignorant.com.")) && isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "ds-ignorant.com.", QType::NS, "ns.ds-ignorant.com.", DNSResourceRecord::AUTHORITY, 3600);
/* no DS, insecure */
addRecordToLW(res, "ns.ds-ignorant.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (domain == target) {
+ if (domain == target) {
if (type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.200");
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else if (type == QType::DS) {
+ if (type == QType::DS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "signed.ds-ignorant.com. admin\\.signed.ds-ignorant.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, domain, 300);
size_t queriesCount = 0;
- sr->setAsyncCallback([target, &queriesCount, keys](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([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
queriesCount++;
- if (domain.isPartOf(DNSName("signed.ds-ignorant.com.")) && ip == ComboAddress("192.0.2.1:53")) {
+ if (domain.isPartOf(DNSName("signed.ds-ignorant.com.")) && address == ComboAddress("192.0.2.1:53")) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "signed.ds-ignorant.com.", QType::NS, "ns.signed.ds-ignorant.com.", DNSResourceRecord::AUTHORITY, 3600);
addRecordToLW(res, "ns.signed.ds-ignorant.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
+ if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
DNSName auth(domain);
auth.chopOff();
return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, false);
}
else {
- if (domain.isPartOf(DNSName("ds-ignorant.com.")) && isRootServer(ip)) {
+ if (domain.isPartOf(DNSName("ds-ignorant.com.")) && isRootServer(address)) {
setLWResult(res, 0, false, false, true);
addRecordToLW(res, "ds-ignorant.com.", QType::NS, "ns.ds-ignorant.com.", DNSResourceRecord::AUTHORITY, 3600);
addDS(DNSName("ds-ignorant.com."), 300, res->d_records, keys);
addRecordToLW(res, "ns.ds-ignorant.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
return LWResult::Result::Success;
}
- else if (domain == target) {
+ if (domain == target) {
if (type == QType::A) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.200");
addRRSIG(keys, res->d_records, domain, 300);
return LWResult::Result::Success;
}
- else if (type == QType::DS) {
+ if (type == QType::DS) {
setLWResult(res, 0, true, false, true);
addRecordToLW(res, domain, QType::SOA, "signed.ds-ignorant.com. admin\\.signed.ds-ignorant.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, domain, 300);
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
{
map<DNSName, dsmap_t> newDSAnchors;
try {
- auto zp = ZoneParserTNG(fname);
- zp.disableGenerate();
- DNSResourceRecord rr;
- DNSRecord dr;
- while (zp.get(rr)) {
- dr = DNSRecord(rr);
- if (rr.qtype == QType::DS) {
- auto dsr = getRR<DSRecordContent>(dr);
+ auto zoneParser = ZoneParserTNG(fname);
+ zoneParser.disableGenerate();
+ DNSResourceRecord resourceRecord;
+ DNSRecord dnsrecord;
+ while (zoneParser.get(resourceRecord)) {
+ dnsrecord = DNSRecord(resourceRecord);
+ if (resourceRecord.qtype == QType::DS) {
+ auto dsr = getRR<DSRecordContent>(dnsrecord);
if (dsr == nullptr) {
- throw PDNSException("Unable to parse DS record '" + rr.qname.toString() + " " + rr.getZoneRepresentation() + "'");
+ throw PDNSException("Unable to parse DS record '" + resourceRecord.qname.toString() + " " + resourceRecord.getZoneRepresentation() + "'");
}
- newDSAnchors[rr.qname].insert(*dsr);
+ newDSAnchors[resourceRecord.qname].insert(*dsr);
}
- if (rr.qtype == QType::DNSKEY) {
- auto dnskeyr = getRR<DNSKEYRecordContent>(dr);
+ if (resourceRecord.qtype == QType::DNSKEY) {
+ auto dnskeyr = getRR<DNSKEYRecordContent>(dnsrecord);
if (dnskeyr == nullptr) {
- throw PDNSException("Unable to parse DNSKEY record '" + rr.qname.toString() + " " + rr.getZoneRepresentation() + "'");
+ throw PDNSException("Unable to parse DNSKEY record '" + resourceRecord.qname.toString() + " " + resourceRecord.getZoneRepresentation() + "'");
}
- auto dsr = makeDSFromDNSKey(rr.qname, *dnskeyr, DNSSECKeeper::DIGEST_SHA256);
- newDSAnchors[rr.qname].insert(dsr);
+ auto dsr = makeDSFromDNSKey(resourceRecord.qname, *dnskeyr, DNSSECKeeper::DIGEST_SHA256);
+ newDSAnchors[resourceRecord.qname].insert(dsr);
}
}
if (dsAnchors == newDSAnchors) {
}
SLOG(g_log << Logger::Info << "Read changed Trust Anchors from file, updating" << endl,
log->info(Logr::Info, "Read changed Trust Anchors from file, updating"));
- dsAnchors = newDSAnchors;
+ dsAnchors = std::move(newDSAnchors);
return true;
}
catch (const std::exception& e) {
--- /dev/null
+../views.hh
\ No newline at end of file
#include "uuid-utils.hh"
#include "tcpiohandler.hh"
#include "rec-main.hh"
+#include "settings/cxxsettings.hh"
using json11::Json;
throw ApiException("Config Option \"api-config-dir\" must be set");
}
- string filename = ::arg()["api-config-dir"] + "/" + filebasename + ".conf";
- ofstream ofconf(filename.c_str());
+ string filename = ::arg()["api-config-dir"] + "/" + filebasename;
+ if (g_yamlSettings) {
+ filename += ".yml";
+ }
+ else {
+ filename += ".conf";
+ }
+ ofstream ofconf(filename);
if (!ofconf) {
throw ApiException("Could not open config fragment file '" + filename + "' for writing: " + stringerror());
}
ofconf.close();
}
-static void apiServerConfigACL(const std::string& aclType, HttpRequest* req, HttpResponse* resp)
+static void apiServerConfigACLGET(const std::string& aclType, HttpRequest* /* req */, HttpResponse* resp)
+{
+ // Return currently configured ACLs
+ vector<string> entries;
+ if (t_allowFrom && aclType == "allow-from") {
+ entries = t_allowFrom->toStringVector();
+ }
+ else if (t_allowNotifyFrom && aclType == "allow-notify-from") {
+ entries = t_allowNotifyFrom->toStringVector();
+ }
+
+ resp->setJsonBody(Json::object{
+ {"name", aclType},
+ {"value", entries},
+ });
+}
+
+static void apiServerConfigACLPUT(const std::string& aclType, HttpRequest* req, HttpResponse* resp)
{
- if (req->method == "PUT") {
- Json document = req->json();
+ const auto& document = req->json();
+
+ const auto& jlist = document["value"];
- auto jlist = document["value"];
- if (!jlist.is_array()) {
- throw ApiException("'value' must be an array");
+ if (!jlist.is_array()) {
+ throw ApiException("'value' must be an array");
+ }
+
+ if (g_yamlSettings) {
+ ::rust::Vec<::rust::String> vec;
+ for (const auto& value : jlist.array_items()) {
+ vec.emplace_back(value.string_value());
}
+ try {
+ ::pdns::rust::settings::rec::validate_allow_from(aclType, vec);
+ }
+ catch (const ::rust::Error& e) {
+ throw ApiException(string("Unable to convert: ") + e.what());
+ }
+ ::rust::String yaml;
+ if (aclType == "allow-from") {
+ yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming("allow_from", "allow_from_file", vec);
+ }
+ else {
+ yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming("allow_notify_from", "allow_notify_from_file", vec);
+ }
+ apiWriteConfigFile(aclType, string(yaml));
+ }
+ else {
NetmaskGroup nmg;
for (const auto& value : jlist.array_items()) {
try {
}
}
- ostringstream ss;
+ ostringstream strStream;
// Clear <foo>-from-file if set, so our changes take effect
- ss << aclType << "-file=" << endl;
+ strStream << aclType << "-file=" << endl;
// Clear ACL setting, and provide a "parent" value
- ss << aclType << "=" << endl;
- ss << aclType << "+=" << nmg.toString() << endl;
+ strStream << aclType << "=" << endl;
+ strStream << aclType << "+=" << nmg.toString() << endl;
- apiWriteConfigFile(aclType, ss.str());
+ apiWriteConfigFile(aclType, strStream.str());
+ }
- parseACLs();
+ parseACLs();
- // fall through to GET
- }
- else if (req->method != "GET") {
- throw HttpMethodNotAllowedException();
- }
+ apiServerConfigACLGET(aclType, req, resp);
+}
- // Return currently configured ACLs
- vector<string> entries;
- if (t_allowFrom && aclType == "allow-from") {
- t_allowFrom->toStringVector(&entries);
- }
- else if (t_allowNotifyFrom && aclType == "allow-notify-from") {
- t_allowNotifyFrom->toStringVector(&entries);
- }
+static void apiServerConfigAllowFromGET(HttpRequest* req, HttpResponse* resp)
+{
+ apiServerConfigACLGET("allow-from", req, resp);
+}
- resp->setJsonBody(Json::object{
- {"name", aclType},
- {"value", entries},
- });
+static void apiServerConfigAllowNotifyFromGET(HttpRequest* req, HttpResponse* resp)
+{
+ apiServerConfigACLGET("allow-notify-from", req, resp);
}
-static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp)
+static void apiServerConfigAllowFromPUT(HttpRequest* req, HttpResponse* resp)
{
- apiServerConfigACL("allow-from", req, resp);
+ apiServerConfigACLPUT("allow-from", req, resp);
}
-static void apiServerConfigAllowNotifyFrom(HttpRequest* req, HttpResponse* resp)
+static void apiServerConfigAllowNotifyFromPUT(HttpRequest* req, HttpResponse* resp)
{
- apiServerConfigACL("allow-notify-from", req, resp);
+ apiServerConfigACLPUT("allow-notify-from", req, resp);
}
static void fillZone(const DNSName& zonename, HttpResponse* resp)
{
auto iter = SyncRes::t_sstorage.domainmap->find(zonename);
- if (iter == SyncRes::t_sstorage.domainmap->end())
+ if (iter == SyncRes::t_sstorage.domainmap->end()) {
throw ApiException("Could not find domain '" + zonename.toLogString() + "'");
+ }
const SyncRes::AuthDomain& zone = iter->second;
Json::array servers;
for (const ComboAddress& server : zone.d_servers) {
- servers.push_back(server.toStringWithPort());
+ servers.emplace_back(server.toStringWithPort());
}
Json::array records;
- for (const SyncRes::AuthDomain::records_t::value_type& dr : zone.d_records) {
+ for (const SyncRes::AuthDomain::records_t::value_type& record : zone.d_records) {
records.push_back(Json::object{
- {"name", dr.d_name.toString()},
- {"type", DNSRecordContent::NumberToType(dr.d_type)},
- {"ttl", (double)dr.d_ttl},
- {"content", dr.getContent()->getZoneRepresentation()}});
+ {"name", record.d_name.toString()},
+ {"type", DNSRecordContent::NumberToType(record.d_type)},
+ {"ttl", (double)record.d_ttl},
+ {"content", record.getContent()->getZoneRepresentation()}});
}
// id is the canonical lookup key, which doesn't actually match the name (in some cases)
resp->setJsonBody(doc);
}
-static void doCreateZone(const Json document)
+static void doCreateZone(const Json& document)
{
if (::arg()["api-config-dir"].empty()) {
throw ApiException("Config Option \"api-config-dir\" must be set");
string singleIPTarget = document["single_target_ip"].string_value();
string kind = toUpper(stringFromJson(document, "kind"));
- bool rd = boolFromJson(document, "recursion_desired");
+ bool rdFlag = boolFromJson(document, "recursion_desired");
string confbasename = "zone-" + apiZoneNameToId(zone);
+ const string yamlAPiZonesFile = ::arg()["api-config-dir"] + "/apizones";
+
if (kind == "NATIVE") {
- if (rd)
+ if (rdFlag) {
throw ApiException("kind=Native and recursion_desired are mutually exclusive");
+ }
if (!singleIPTarget.empty()) {
try {
ComboAddress rem(singleIPTarget);
- if (rem.sin4.sin_family != AF_INET)
+ if (rem.sin4.sin_family != AF_INET) {
throw ApiException("");
+ }
singleIPTarget = rem.toString();
}
catch (...) {
}
ofzone.close();
- apiWriteConfigFile(confbasename, "auth-zones+=" + zonename + "=" + zonefilename);
+ if (g_yamlSettings) {
+ pdns::rust::settings::rec::AuthZone authzone;
+ authzone.zone = zonename;
+ authzone.file = zonefilename;
+ pdns::rust::settings::rec::api_add_auth_zone(yamlAPiZonesFile, std::move(authzone));
+ }
+ else {
+ apiWriteConfigFile(confbasename, "auth-zones+=" + zonename + "=" + zonefilename);
+ }
}
else if (kind == "FORWARDED") {
- string serverlist;
- for (const auto& value : document["servers"].array_items()) {
- string server = value.string_value();
- if (server == "") {
- throw ApiException("Forwarded-to server must not be an empty string");
+ if (g_yamlSettings) {
+ pdns::rust::settings::rec::ForwardZone forward;
+ forward.zone = zonename;
+ forward.recurse = rdFlag;
+ forward.notify_allowed = false;
+ for (const auto& value : document["servers"].array_items()) {
+ forward.forwarders.emplace_back(value.string_value());
}
- try {
- ComboAddress ca = parseIPAndPort(server, 53);
- if (!serverlist.empty()) {
- serverlist += ";";
+ pdns::rust::settings::rec::api_add_forward_zone(yamlAPiZonesFile, std::move(forward));
+ }
+ else {
+ string serverlist;
+ for (const auto& value : document["servers"].array_items()) {
+ const string& server = value.string_value();
+ if (server.empty()) {
+ throw ApiException("Forwarded-to server must not be an empty string");
+ }
+ try {
+ ComboAddress address = parseIPAndPort(server, 53);
+ if (!serverlist.empty()) {
+ serverlist += ";";
+ }
+ serverlist += address.toStringWithPort();
+ }
+ catch (const PDNSException& e) {
+ throw ApiException(e.reason);
}
- serverlist += ca.toStringWithPort();
}
- catch (const PDNSException& e) {
- throw ApiException(e.reason);
+ if (serverlist.empty()) {
+ throw ApiException("Need at least one upstream server when forwarding");
}
- }
- if (serverlist == "")
- throw ApiException("Need at least one upstream server when forwarding");
- if (rd) {
- apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename + "=" + serverlist);
- }
- else {
- apiWriteConfigFile(confbasename, "forward-zones+=" + zonename + "=" + serverlist);
+ if (rdFlag) {
+ apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename + "=" + serverlist);
+ }
+ else {
+ apiWriteConfigFile(confbasename, "forward-zones+=" + zonename + "=" + serverlist);
+ }
}
}
else {
}
string filename;
-
- // this one must exist
- filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
- if (unlink(filename.c_str()) != 0) {
- return false;
+ if (g_yamlSettings) {
+ const string yamlAPiZonesFile = ::arg()["api-config-dir"] + "/apizones";
+ pdns::rust::settings::rec::api_delete_zone(yamlAPiZonesFile, zonename.toString());
+ }
+ else {
+ // this one must exist
+ filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
+ if (unlink(filename.c_str()) != 0) {
+ return false;
+ }
}
-
// .zone file is optional
filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".zone";
unlink(filename.c_str());
return true;
}
-static void apiServerZones(HttpRequest* req, HttpResponse* resp)
+static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp)
{
- if (req->method == "POST") {
- if (::arg()["api-config-dir"].empty()) {
- throw ApiException("Config Option \"api-config-dir\" must be set");
- }
-
- Json document = req->json();
+ if (::arg()["api-config-dir"].empty()) {
+ throw ApiException("Config Option \"api-config-dir\" must be set");
+ }
- DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
+ Json document = req->json();
- auto iter = SyncRes::t_sstorage.domainmap->find(zonename);
- if (iter != SyncRes::t_sstorage.domainmap->end())
- throw ApiException("Zone already exists");
+ DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
- doCreateZone(document);
- reloadZoneConfiguration();
- fillZone(zonename, resp);
- resp->status = 201;
- return;
+ const auto& iter = SyncRes::t_sstorage.domainmap->find(zonename);
+ if (iter != SyncRes::t_sstorage.domainmap->cend()) {
+ throw ApiException("Zone already exists");
}
- if (req->method != "GET")
- throw HttpMethodNotAllowedException();
+ doCreateZone(document);
+ reloadZoneConfiguration(g_yamlSettings);
+ fillZone(zonename, resp);
+ resp->status = 201;
+}
+static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp)
+{
Json::array doc;
- for (const SyncRes::domainmap_t::value_type& val : *SyncRes::t_sstorage.domainmap) {
+ for (const auto& val : *SyncRes::t_sstorage.domainmap) {
const SyncRes::AuthDomain& zone = val.second;
Json::array servers;
- for (const ComboAddress& server : zone.d_servers) {
- servers.push_back(server.toStringWithPort());
+ for (const auto& server : zone.d_servers) {
+ servers.emplace_back(server.toStringWithPort());
}
// id is the canonical lookup key, which doesn't actually match the name (in some cases)
string zoneId = apiZoneNameToId(val.first);
resp->setJsonBody(doc);
}
-static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
+static inline DNSName findZoneById(HttpRequest* req)
{
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
- SyncRes::domainmap_t::const_iterator iter = SyncRes::t_sstorage.domainmap->find(zonename);
- if (iter == SyncRes::t_sstorage.domainmap->end())
+ auto zonename = apiZoneIdToName(req->parameters["id"]);
+ if (SyncRes::t_sstorage.domainmap->find(zonename) == SyncRes::t_sstorage.domainmap->end()) {
throw ApiException("Could not find domain '" + zonename.toLogString() + "'");
+ }
+ return zonename;
+}
- if (req->method == "PUT") {
- Json document = req->json();
+static void apiServerZoneDetailPUT(HttpRequest* req, HttpResponse* resp)
+{
+ auto zonename = findZoneById(req);
+ const auto& document = req->json();
+
+ doDeleteZone(zonename);
+ doCreateZone(document);
+ reloadZoneConfiguration(g_yamlSettings);
+ resp->body = "";
+ resp->status = 204; // No Content, but indicate success
+}
- doDeleteZone(zonename);
- doCreateZone(document);
- reloadZoneConfiguration();
- resp->body = "";
- resp->status = 204; // No Content, but indicate success
+static void apiServerZoneDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+ auto zonename = findZoneById(req);
+ if (!doDeleteZone(zonename)) {
+ throw ApiException("Deleting domain failed");
}
- else if (req->method == "DELETE") {
- if (!doDeleteZone(zonename)) {
- throw ApiException("Deleting domain failed");
- }
- reloadZoneConfiguration();
- // empty body on success
- resp->body = "";
- resp->status = 204; // No Content: declare that the zone is gone now
- }
- else if (req->method == "GET") {
- fillZone(zonename, resp);
- }
- else {
- throw HttpMethodNotAllowedException();
- }
+ reloadZoneConfiguration(g_yamlSettings);
+ // empty body on success
+ resp->body = "";
+ resp->status = 204; // No Content: declare that the zone is gone now
}
-static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
+static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp)
{
- if (req->method != "GET")
- throw HttpMethodNotAllowedException();
+ auto zonename = findZoneById(req);
+ fillZone(zonename, resp);
+}
- string q = req->getvars["q"];
- if (q.empty())
+static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
+{
+ string qVar = req->getvars["q"];
+ if (qVar.empty()) {
throw ApiException("Query q can't be blank");
+ }
Json::array doc;
for (const SyncRes::domainmap_t::value_type& val : *SyncRes::t_sstorage.domainmap) {
string zoneId = apiZoneNameToId(val.first);
string zoneName = val.first.toString();
- if (pdns_ci_find(zoneName, q) != string::npos) {
+ if (pdns_ci_find(zoneName, qVar) != string::npos) {
doc.push_back(Json::object{
{"type", "zone"},
{"zone_id", zoneId},
}
// if zone name is an exact match, don't bother with returning all records/comments in it
- if (val.first == DNSName(q)) {
+ if (val.first == DNSName(qVar)) {
continue;
}
const SyncRes::AuthDomain& zone = val.second;
- for (const SyncRes::AuthDomain::records_t::value_type& rr : zone.d_records) {
- if (pdns_ci_find(rr.d_name.toString(), q) == string::npos && pdns_ci_find(rr.getContent()->getZoneRepresentation(), q) == string::npos)
+ for (const SyncRes::AuthDomain::records_t::value_type& resourceRec : zone.d_records) {
+ if (pdns_ci_find(resourceRec.d_name.toString(), qVar) == string::npos && pdns_ci_find(resourceRec.getContent()->getZoneRepresentation(), qVar) == string::npos) {
continue;
+ }
doc.push_back(Json::object{
{"type", "record"},
{"zone_id", zoneId},
{"zone_name", zoneName},
- {"name", rr.d_name.toString()},
- {"content", rr.getContent()->getZoneRepresentation()}});
+ {"name", resourceRec.d_name.toString()},
+ {"content", resourceRec.getContent()->getZoneRepresentation()}});
}
}
resp->setJsonBody(doc);
static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp)
{
- if (req->method != "PUT")
- throw HttpMethodNotAllowedException();
-
DNSName canon = apiNameToDNSName(req->getvars["domain"]);
- bool subtree = (req->getvars.count("subtree") > 0 && req->getvars["subtree"].compare("true") == 0);
+ bool subtree = req->getvars.count("subtree") > 0 && req->getvars["subtree"] == "true";
uint16_t qtype = 0xffff;
- if (req->getvars.count("type")) {
+ if (req->getvars.count("type") != 0) {
qtype = QType::chartocode(req->getvars["type"].c_str());
}
{"result", "Flushed cache."}});
}
-static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp)
+static void apiServerRPZStats(HttpRequest* /* req */, HttpResponse* resp)
{
- if (req->method != "GET")
- throw HttpMethodNotAllowedException();
-
auto luaconf = g_luaconfs.getLocal();
auto numZones = luaconf->dfe.size();
for (size_t i = 0; i < numZones; i++) {
auto zone = luaconf->dfe.getZone(i);
- if (zone == nullptr)
+ if (zone == nullptr) {
continue;
+ }
const auto& name = zone->getName();
auto stats = getRPZZoneStats(name);
- if (stats == nullptr)
+ if (stats == nullptr) {
continue;
+ }
Json::object zoneInfo = {
{"transfers_failed", (double)stats->d_failedTransfers},
{"transfers_success", (double)stats->d_successfulTransfers},
resp->setJsonBody(ret);
}
-static void prometheusMetrics(HttpRequest* req, HttpResponse* resp)
+static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp)
{
static MetricDefinitionStorage s_metricDefinitions;
- if (req->method != "GET")
- throw HttpMethodNotAllowedException();
-
std::ostringstream output;
// Argument controls disabling of any stats. So
MetricDefinition metricDetails;
if (s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
- std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(
+ std::string prometheusTypeName = MetricDefinitionStorage::getPrometheusStringMetricType(
metricDetails.d_prometheusType);
if (prometheusTypeName.empty()) {
{
resp->headers["Cache-Control"] = "max-age=86400";
- if (req->url.path == "/")
+ if (req->url.path == "/") {
req->url.path = "/index.html";
+ }
const string charset = "; charset=utf-8";
- if (boost::ends_with(req->url.path, ".html"))
+ if (boost::ends_with(req->url.path, ".html")) {
resp->headers["Content-Type"] = "text/html" + charset;
- else if (boost::ends_with(req->url.path, ".css"))
+ }
+ else if (boost::ends_with(req->url.path, ".css")) {
resp->headers["Content-Type"] = "text/css" + charset;
- else if (boost::ends_with(req->url.path, ".js"))
+ }
+ else if (boost::ends_with(req->url.path, ".js")) {
resp->headers["Content-Type"] = "application/javascript" + charset;
- else if (boost::ends_with(req->url.path, ".png"))
+ }
+ else if (boost::ends_with(req->url.path, ".png")) {
resp->headers["Content-Type"] = "image/png";
+ }
resp->headers["X-Content-Type-Options"] = "nosniff";
resp->headers["X-Frame-Options"] = "deny";
resp->headers["X-XSS-Protection"] = "1; mode=block";
// resp->headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
- if (!req->url.path.empty() && g_urlmap.count(req->url.path.c_str() + 1)) {
- resp->body = g_urlmap.at(req->url.path.c_str() + 1);
+ if (!req->url.path.empty() && (g_urlmap.count(req->url.path.substr(1)) != 0)) {
+ resp->body = g_urlmap.at(req->url.path.substr(1));
resp->status = 200;
}
else {
const std::map<std::string, MetricDefinition> MetricDefinitionStorage::d_metrics = {
{"all-outqueries",
MetricDefinition(PrometheusMetricType::counter,
- "Number of outgoing UDP queries since starting")},
+ "Number of outgoing queries since starting")},
{"answers-slow",
MetricDefinition(PrometheusMetricType::counter,
{"remote-logger-count-o-0",
MetricDefinition(PrometheusMetricType::multicounter,
"Number of remote logging events")},
+ {"nod-events",
+ MetricDefinition(PrometheusMetricType::counter,
+ "Count of NOD events")},
+
+ {"udr-events",
+ MetricDefinition(PrometheusMetricType::counter,
+ "Count of UDR events")},
};
-#define CHECK_PROMETHEUS_METRICS 0
+constexpr bool CHECK_PROMETHEUS_METRICS = false;
-#if CHECK_PROMETHEUS_METRICS
static void validatePrometheusMetrics()
{
MetricDefinitionStorage s_metricDefinitions;
if (metricName.find("cumul-") == 0) {
continue;
}
+ if (metricName.find("auth-") == 0 && metricName.find("-answers") != string::npos) {
+ continue;
+ }
MetricDefinition metricDetails;
if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
}
}
}
-#endif
RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
{
-#if CHECK_PROMETHEUS_METRICS
- validatePrometheusMetrics();
-#endif
+ if (CHECK_PROMETHEUS_METRICS) {
+ validatePrometheusMetrics();
+ }
d_ws = make_unique<AsyncWebServer>(fdm, arg()["webserver-address"], arg().asNum("webserver-port"));
d_ws->setSLog(g_slog->withName("webserver"));
// legacy dispatch
d_ws->registerApiHandler(
- "/jsonstat", [this](HttpRequest* req, HttpResponse* resp) { jsonstat(req, resp); }, true);
- d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush);
- d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFrom);
- d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", &apiServerConfigAllowNotifyFrom);
- d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig);
- d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", apiServerRPZStats);
- d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData);
- d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, true);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetail);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones);
- d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, true);
- d_ws->registerApiHandler("/api/v1/servers", apiServer);
- d_ws->registerApiHandler("/api/v1", apiDiscoveryV1);
- d_ws->registerApiHandler("/api", apiDiscovery);
-
- for (const auto& u : g_urlmap) {
- d_ws->registerWebHandler("/" + u.first, serveStuff);
+ "/jsonstat", [](HttpRequest* req, HttpResponse* resp) { jsonstat(req, resp); }, "GET", true);
+ d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFromPUT, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFromGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFromGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFromPUT, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", apiServerRPZStats, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, "GET", true);
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailPUT, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailDELETE, "DELETE");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesPOST, "POST");
+ d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, "GET", true);
+ d_ws->registerApiHandler("/api/v1/servers", apiServer, "GET");
+ d_ws->registerApiHandler("/api/v1", apiDiscoveryV1, "GET");
+ d_ws->registerApiHandler("/api", apiDiscovery, "GET");
+
+ for (const auto& url : g_urlmap) {
+ d_ws->registerWebHandler("/" + url.first, serveStuff, "GET");
}
- d_ws->registerWebHandler("/", serveStuff);
- d_ws->registerWebHandler("/metrics", prometheusMetrics);
+ d_ws->registerWebHandler("/", serveStuff, "GET");
+ d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET");
d_ws->go();
}
{
string command;
- if (req->getvars.count("command")) {
+ if (req->getvars.count("command") != 0) {
command = req->getvars["command"];
req->getvars.erase("command");
}
vector<query_t> queries;
bool filter = !req->getvars["public-filtered"].empty();
- if (req->getvars["name"] == "servfail-queries")
+ if (req->getvars["name"] == "servfail-queries") {
queries = broadcastAccFunction<vector<query_t>>(pleaseGetServfailQueryRing);
- else if (req->getvars["name"] == "bogus-queries")
+ }
+ else if (req->getvars["name"] == "bogus-queries") {
queries = broadcastAccFunction<vector<query_t>>(pleaseGetBogusQueryRing);
- else if (req->getvars["name"] == "queries")
+ }
+ else if (req->getvars["name"] == "queries") {
queries = broadcastAccFunction<vector<query_t>>(pleaseGetQueryRing);
+ }
typedef map<query_t, unsigned int> counts_t;
counts_t counts;
- unsigned int total = 0;
- for (const query_t& q : queries) {
- total++;
- if (filter)
- counts[pair(getRegisteredName(q.first), q.second)]++;
- else
- counts[pair(q.first, q.second)]++;
+ for (const query_t& count : queries) {
+ if (filter) {
+ counts[pair(getRegisteredName(count.first), count.second)]++;
+ }
+ else {
+ counts[pair(count.first, count.second)]++;
+ }
}
typedef std::multimap<int, query_t> rcounts_t;
rcounts_t rcounts;
- for (counts_t::const_iterator i = counts.begin(); i != counts.end(); ++i)
- rcounts.emplace(-i->second, i->first);
+ for (const auto& count : counts) {
+ rcounts.emplace(-count.second, count.first);
+ }
Json::array entries;
- unsigned int tot = 0, totIncluded = 0;
- for (const rcounts_t::value_type& q : rcounts) {
- totIncluded -= q.first;
+ unsigned int tot = 0;
+ unsigned int totIncluded = 0;
+ for (const rcounts_t::value_type& count : rcounts) {
+ totIncluded -= count.first;
entries.push_back(Json::array{
- -q.first, q.second.first.toLogString(), DNSRecordContent::NumberToType(q.second.second)});
- if (tot++ >= 100)
+ -count.first, count.second.first.toLogString(), DNSRecordContent::NumberToType(count.second.second)});
+ if (tot++ >= 100) {
break;
+ }
}
if (queries.size() != totIncluded) {
entries.push_back(Json::array{
resp->setJsonBody(Json::object{{"entries", entries}});
return;
}
- else if (command == "get-remote-ring") {
+ if (command == "get-remote-ring") {
vector<ComboAddress> queries;
- if (req->getvars["name"] == "remotes")
+ if (req->getvars["name"] == "remotes") {
queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetRemotes);
- else if (req->getvars["name"] == "servfail-remotes")
+ }
+ else if (req->getvars["name"] == "servfail-remotes") {
queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetServfailRemotes);
- else if (req->getvars["name"] == "bogus-remotes")
+ }
+ else if (req->getvars["name"] == "bogus-remotes") {
queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetBogusRemotes);
- else if (req->getvars["name"] == "large-answer-remotes")
+ }
+ else if (req->getvars["name"] == "large-answer-remotes") {
queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetLargeAnswerRemotes);
- else if (req->getvars["name"] == "timeouts")
+ }
+ else if (req->getvars["name"] == "timeouts") {
queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetTimeouts);
-
+ }
typedef map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts_t;
counts_t counts;
- unsigned int total = 0;
- for (const ComboAddress& q : queries) {
- total++;
- counts[q]++;
+ for (const ComboAddress& query : queries) {
+ counts[query]++;
}
typedef std::multimap<int, ComboAddress> rcounts_t;
rcounts_t rcounts;
- for (counts_t::const_iterator i = counts.begin(); i != counts.end(); ++i)
- rcounts.emplace(-i->second, i->first);
+ for (const auto& count : counts) {
+ rcounts.emplace(-count.second, count.first);
+ }
Json::array entries;
- unsigned int tot = 0, totIncluded = 0;
- for (const rcounts_t::value_type& q : rcounts) {
- totIncluded -= q.first;
+ unsigned int tot = 0;
+ unsigned int totIncluded = 0;
+ for (const rcounts_t::value_type& count : rcounts) {
+ totIncluded -= count.first;
entries.push_back(Json::array{
- -q.first, q.second.toString()});
- if (tot++ >= 100)
+ -count.first, count.second.toString()});
+ if (tot++ >= 100) {
break;
+ }
}
if (queries.size() != totIncluded) {
entries.push_back(Json::array{
resp->setJsonBody(Json::object{{"entries", entries}});
return;
}
- else {
- resp->setErrorResult("Command '" + command + "' not found", 404);
- }
+ resp->setErrorResult("Command '" + command + "' not found", 404);
}
-void AsyncServerNewConnectionMT(void* p)
+void AsyncServerNewConnectionMT(void* arg)
{
- AsyncServer* server = (AsyncServer*)p;
+ auto* server = static_cast<AsyncServer*>(arg);
try {
auto socket = server->accept(); // this is actually a shared_ptr
}
// This is an entry point from FDM, so it needs to catch everything.
-void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const
+void AsyncWebServer::serveConnection(const std::shared_ptr<Socket>& socket) const // NOLINT(readability-function-cognitive-complexity) #12791 Remove NOLINT(readability-function-cognitive-complexity) omoerbeek
{
- if (!client->acl(d_acl)) {
+ if (!socket->acl(d_acl)) {
return;
}
try {
YaHTTP::AsyncRequestLoader yarl;
yarl.initialize(&req);
- client->setNonBlocking();
+ socket->setNonBlocking();
const struct timeval timeout
{
};
std::shared_ptr<TLSCtx> tlsCtx{nullptr};
if (d_loglevel > WebServer::LogLevel::None) {
- client->getRemote(remote);
+ socket->getRemote(remote);
}
- auto handler = std::make_shared<TCPIOHandler>("", false, client->releaseHandle(), timeout, tlsCtx);
+ auto handler = std::make_shared<TCPIOHandler>("", false, socket->releaseHandle(), timeout, tlsCtx);
PacketBuffer data;
try {
while (!req.complete) {
auto ret = arecvtcp(data, 16384, handler, true);
if (ret == LWResult::Result::Success) {
- string str(reinterpret_cast<const char*>(data.data()), data.size());
+ string str(reinterpret_cast<const char*>(data.data()), data.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): safe cast, data.data() returns unsigned char *
req.complete = yarl.feed(str);
}
else {
req.d_slog->error(Logr::Warning, e.what(), "Unable to parse request"));
}
+ if (!validURL(req.url)) {
+ throw PDNSException("Received request with invalid URL");
+ }
logRequest(req, remote);
WebServer::handleRequest(req, resp);
- ostringstream ss;
- resp.write(ss);
- const string& s = ss.str();
- reply.insert(reply.end(), s.cbegin(), s.cend());
+ ostringstream stringStream;
+ resp.write(stringStream);
+ const string& str = stringStream.str();
+ reply.insert(reply.end(), str.cbegin(), str.cend());
logResponse(resp, remote, logprefix);
req.d_slog->info(Logr::Error, "Failed sending reply to HTTP client"));
}
handler->close(); // needed to signal "done" to client
+ if (d_loglevel >= WebServer::LogLevel::Normal) {
+ SLOG(g_log << Logger::Notice << logprefix << remote << " \"" << req.method << " " << req.url.path << " HTTP/" << req.versionStr(req.version) << "\" " << resp.status << " " << reply.size() << endl,
+ req.d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
+ "urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
+ "status", Logging::Loggable(resp.status), "respsize", Logging::Loggable(reply.size())));
+ }
}
catch (PDNSException& e) {
SLOG(g_log << Logger::Error << logprefix << "Exception: " << e.reason << endl,
req.d_slog->error(Logr::Error, e.reason, "Exception handing request", "exception", Logging::Loggable("PDNSException")));
}
catch (std::exception& e) {
- if (strstr(e.what(), "timeout") == 0)
+ if (strstr(e.what(), "timeout") == nullptr) {
SLOG(g_log << Logger::Error << logprefix << "STL Exception: " << e.what() << endl,
req.d_slog->error(Logr::Error, e.what(), "Exception handing request", "exception", Logging::Loggable("std::exception")));
+ }
}
catch (...) {
SLOG(g_log << Logger::Error << logprefix << "Unknown exception" << endl,
- req.d_slog->error(Logr::Error, "Exception handing request"))
- }
-
- if (d_loglevel >= WebServer::LogLevel::Normal) {
- SLOG(g_log << Logger::Notice << logprefix << remote << " \"" << req.method << " " << req.url.path << " HTTP/" << req.versionStr(req.version) << "\" " << resp.status << " " << reply.size() << endl,
- req.d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
- "urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
- "status", Logging::Loggable(resp.status), "respsize", Logging::Loggable(reply.size())));
+ req.d_slog->error(Logr::Error, "Exception handing request"));
}
}
void AsyncWebServer::go()
{
- if (!d_server)
+ if (!d_server) {
return;
+ }
auto server = std::dynamic_pointer_cast<AsyncServer>(d_server);
- if (!server)
+ if (!server) {
return;
- server->asyncWaitForConnections(d_fdm, [this](const std::shared_ptr<Socket>& c) { serveConnection(c); });
+ }
+ server->asyncWaitForConnections(d_fdm, [this](const std::shared_ptr<Socket>& socket) { serveConnection(socket); });
}
d_server_socket.setNonBlocking();
};
- friend void AsyncServerNewConnectionMT(void* p);
+ friend void AsyncServerNewConnectionMT(void* arg);
- typedef std::function<void(std::shared_ptr<Socket>)> newconnectioncb_t;
+ using newconnectioncb_t = std::function<void(const std::shared_ptr<Socket>&)>;
void asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback);
private:
private:
FDMultiplexer* d_fdm;
- void serveConnection(std::shared_ptr<Socket> socket) const;
+ void serveConnection(const std::shared_ptr<Socket>& socket) const;
protected:
- virtual std::shared_ptr<Server> createServer() override
+ std::shared_ptr<Server> createServer() override
{
return std::make_shared<AsyncServer>(d_listenaddress, d_port);
};
{
public:
explicit RecursorWebServer(FDMultiplexer* fdm);
- void jsonstat(HttpRequest* req, HttpResponse* resp);
+ static void jsonstat(HttpRequest* req, HttpResponse* resp);
private:
std::unique_ptr<AsyncWebServer> d_ws{nullptr};
catch (const std::exception& e) {
#ifdef WE_ARE_RECURSOR
SLOG(g_log<<Logger::Warning<<"Error connecting to remote logger "<<d_remote.toStringWithPort()<<": "<<e.what()<<std::endl,
- g_slog->withName("protobuf")->error(Logr::Error, e.what(), "Exception while connection to remote logger", "address", Logging::Loggable(d_remote)));
+ g_slog->withName("protobuf")->error(Logr::Error, e.what(), "Exception while connecting to remote logger", "address", Logging::Loggable(d_remote)));
#else
warnlog("Error connecting to remote logger %s: %s", d_remote.toStringWithPort(), e.what());
#endif
// try to make socket
sock = makeQuerySocket(local, true);
if (sock < 0)
- throw ResolverException("Unable to create local socket on '"+lstr+"'' to '"+remote.toStringWithPort()+"': "+stringerror());
+ throw ResolverException("Unable to create local socket on '"+lstr+"'' to '"+remote.toLogString()+"': "+stringerror());
setNonBlocking( sock );
locals[lstr] = sock;
}
*localsock = sock;
}
if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
- throw ResolverException("Unable to ask query of '"+remote.toStringWithPort()+"': "+stringerror());
+ throw ResolverException("Unable to ask query of '"+remote.toLogString()+"': "+stringerror());
}
return randomid;
}
*domain = mdp.d_qname;
if(domain->empty())
- throw ResolverException("SOA query to '" + remote->toStringWithPort() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
+ throw ResolverException("SOA query to '" + remote->toLogString() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
if(mdp.d_answers.empty())
- throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
+ throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
if(mdp.d_qtype != QType::SOA)
- throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type");
+ throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type");
if(mdp.d_header.rcode != 0)
- throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned Rcode " + RCode::to_s(mdp.d_header.rcode));
+ throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' returned Rcode " + RCode::to_s(mdp.d_header.rcode));
*theirInception = *theirExpire = 0;
bool gotSOA=false;
}
}
if(!gotSOA)
- throw ResolverException("Query to '" + remote->toString() + "' for SOA of '" + domain->toLogString() + "' did not return a SOA");
+ throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' did not return a SOA");
return true;
}
throw ResolverException("recvfrom error waiting for answer: "+stringerror());
if (from != to) {
- throw ResolverException("Got answer from the wrong peer while resolving ('"+from.toStringWithPort()+"' instead of '"+to.toStringWithPort()+"', discarding");
+ throw ResolverException("Got answer from the wrong peer while resolving ('"+from.toLogString()+"' instead of '"+to.toLogString()+"', discarding");
}
MOADNSParser mdp(false, buffer, len);
// Method implements section 3.4.1 of RFC2136
int PacketHandler::checkUpdatePrescan(const DNSRecord *rr) {
// The RFC stats that d_class != ZCLASS, but we only support the IN class.
- if (rr->d_class != QClass::IN && rr->d_class != QClass::NONE && rr->d_class != QClass::ANY)
+ if (rr->d_class != QClass::IN && rr->d_class != QClass::NONE && rr->d_class != QClass::ANY) {
return RCode::FormErr;
+ }
QType qtype = QType(rr->d_type);
- if (! qtype.isSupportedType())
+ if (!qtype.isSupportedType()) {
return RCode::FormErr;
+ }
- if ((rr->d_class == QClass::NONE || rr->d_class == QClass::ANY) && rr->d_ttl != 0)
+ if ((rr->d_class == QClass::NONE || rr->d_class == QClass::ANY) && rr->d_ttl != 0) {
return RCode::FormErr;
+ }
- if (rr->d_class == QClass::ANY && rr->d_clen != 0)
+ if (rr->d_class == QClass::ANY && rr->d_clen != 0) {
return RCode::FormErr;
+ }
- if (qtype.isMetadataType())
- return RCode::FormErr;
+ if (qtype.isMetadataType()) {
+ return RCode::FormErr;
+ }
- if (rr->d_class != QClass::ANY && qtype.getCode() == QType::ANY)
+ if (rr->d_class != QClass::ANY && qtype.getCode() == QType::ANY) {
return RCode::FormErr;
+ }
return RCode::NoError;
}
auto repr = rec.getZoneRepresentation();
if (rec.qtype == QType::TXT) {
DLOG(g_log<<msgPrefix<<"Adjusting TXT content from ["<<repr<<"]"<<endl);
- auto drc = DNSRecordContent::mastermake(rec.qtype.getCode(), QClass::IN, repr);
+ auto drc = DNSRecordContent::make(rec.qtype.getCode(), QClass::IN, repr);
auto ser = drc->serialize(rec.qname, true, true);
auto rc = DNSRecordContent::deserialize(rec.qname, rec.qtype.getCode(), ser);
repr = rc->getZoneRepresentation(true);
B.getDomainMetadata(p.qdomain, "FORWARD-DNSUPDATE", forward);
if (forward.size() == 0 && ! ::arg().mustDo("forward-dnsupdate")) {
- g_log<<Logger::Notice<<msgPrefix<<"Not configured to forward to master, returning Refused."<<endl;
+ g_log << Logger::Notice << msgPrefix << "Not configured to forward to primary, returning Refused." << endl;
return RCode::Refused;
}
- for(const auto& remote : di.masters) {
- g_log<<Logger::Notice<<msgPrefix<<"Forwarding packet to master "<<remote<<endl;
+ for (const auto& remote : di.primaries) {
+ g_log << Logger::Notice << msgPrefix << "Forwarding packet to primary " << remote << endl;
if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
continue;
closesocket(sock);
}
catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error closing master forwarding socket after connect() failed: "<<e.reason<<endl;
+ g_log << Logger::Error << "Error closing primary forwarding socket after connect() failed: " << e.reason << endl;
}
continue;
}
closesocket(sock);
}
catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error closing master forwarding socket after write() failed: "<<e.reason<<endl;
+ g_log << Logger::Error << "Error closing primary forwarding socket after write() failed: " << e.reason << endl;
}
continue;
}
int res = waitForData(sock, 10, 0);
if (!res) {
- g_log<<Logger::Error<<msgPrefix<<"Timeout waiting for reply from master at "<<remote.toStringWithPort()<<endl;
+ g_log << Logger::Error << msgPrefix << "Timeout waiting for reply from primary at " << remote.toStringWithPort() << endl;
try {
closesocket(sock);
}
catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error closing master forwarding socket after a timeout occurred: "<<e.reason<<endl;
+ g_log << Logger::Error << "Error closing primary forwarding socket after a timeout occurred: " << e.reason << endl;
}
continue;
}
if (res < 0) {
- g_log<<Logger::Error<<msgPrefix<<"Error waiting for answer from master at "<<remote.toStringWithPort()<<", error:"<<stringerror()<<endl;
+ g_log << Logger::Error << msgPrefix << "Error waiting for answer from primary at " << remote.toStringWithPort() << ", error:" << stringerror() << endl;
try {
closesocket(sock);
}
catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error closing master forwarding socket after an error occurred: "<<e.reason<<endl;
+ g_log << Logger::Error << "Error closing primary forwarding socket after an error occurred: " << e.reason << endl;
}
continue;
}
ssize_t recvRes;
recvRes = recv(sock, &lenBuf, sizeof(lenBuf), 0);
if (recvRes < 0 || static_cast<size_t>(recvRes) < sizeof(lenBuf)) {
- g_log<<Logger::Error<<msgPrefix<<"Could not receive data (length) from master at "<<remote.toStringWithPort()<<", error:"<<stringerror()<<endl;
+ g_log << Logger::Error << msgPrefix << "Could not receive data (length) from primary at " << remote.toStringWithPort() << ", error:" << stringerror() << endl;
try {
closesocket(sock);
}
catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error closing master forwarding socket after recv() failed: "<<e.reason<<endl;
+ g_log << Logger::Error << "Error closing primary forwarding socket after recv() failed: " << e.reason << endl;
}
continue;
}
buffer.resize(packetLen);
recvRes = recv(sock, &buffer.at(0), packetLen, 0);
if (recvRes < 0) {
- g_log<<Logger::Error<<msgPrefix<<"Could not receive data (dnspacket) from master at "<<remote.toStringWithPort()<<", error:"<<stringerror()<<endl;
+ g_log << Logger::Error << msgPrefix << "Could not receive data (dnspacket) from primary at " << remote.toStringWithPort() << ", error:" << stringerror() << endl;
try {
closesocket(sock);
}
catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error closing master forwarding socket after recv() failed: "<<e.reason<<endl;
+ g_log << Logger::Error << "Error closing primary forwarding socket after recv() failed: " << e.reason << endl;
}
continue;
}
closesocket(sock);
}
catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error closing master forwarding socket: "<<e.reason<<endl;
+ g_log << Logger::Error << "Error closing primary forwarding socket: " << e.reason << endl;
}
try {
return mdp.d_header.rcode;
}
catch (...) {
- g_log<<Logger::Error<<msgPrefix<<"Failed to parse response packet from master at "<<remote.toStringWithPort()<<endl;
+ g_log << Logger::Error << msgPrefix << "Failed to parse response packet from primary at " << remote.toStringWithPort() << endl;
continue;
}
}
- g_log<<Logger::Error<<msgPrefix<<"Failed to forward packet to master(s). Returning ServFail."<<endl;
+ g_log << Logger::Error << msgPrefix << "Failed to forward packet to primary(s). Returning ServFail." << endl;
return RCode::ServFail;
}
return RCode::NotAuth;
}
- if (di.kind == DomainInfo::Slave)
+ if (di.kind == DomainInfo::Secondary)
return forwardPacket(msgPrefix, p, di);
// Check if all the records provided are within the zone
zone.append("$");
purgeAuthCaches(zone);
- // Notify slaves
- if (di.kind == DomainInfo::Master) {
+ // Notify secondaries
+ if (di.kind == DomainInfo::Primary) {
vector<string> notify;
B.getDomainMetadata(p.qdomain, "NOTIFY-DNSUPDATE", notify);
if (!notify.empty() && notify.front() == "1") {
DNSPacketWriter pwtkey(packet, gssctx.getLabel(), QType::TKEY, QClass::ANY);
TKEYRecordContent tkrc;
tkrc.d_algo = DNSName("gss-tsig.");
+ // coverity[store_truncates_time_t]
tkrc.d_inception = time((time_t*)NULL);
tkrc.d_expiration = tkrc.d_inception+15;
tkrc.d_mode = 3;
throw PDNSException("tcp read failed");
len=ntohs(len);
- std::unique_ptr<char[]> creply(new char[len]);
+ auto creply = std::make_unique<char[]>(len);
int n=0;
int numread;
while(n<len) {
#include "dnswriter.hh"
#include "ednsoptions.hh"
#include "ednssubnet.hh"
+#include "ednsextendederror.hh"
#include "misc.hh"
#include "proxy-protocol.hh"
#include "sstuff.hh"
// Vars below used by tcpiohandler.cc
bool g_verbose = true;
-bool g_syslog = false;
static bool hidettl = false;
cerr << "Syntax: sdig IP-ADDRESS-OR-DOH-URL PORT QNAME QTYPE "
"[dnssec] [ednssubnet SUBNET/MASK] [hidesoadetails] [hidettl] [recurse] [showflags] "
"[tcp] [dot] [insecure] [fastOpen] [subjectName name] [caStore file] [tlsProvider openssl|gnutls] "
- "[xpf XPFDATA] [class CLASSNUM] "
"[proxy UDP(0)/TCP(1) SOURCE-IP-ADDRESS-AND-PORT DESTINATION-IP-ADDRESS-AND-PORT] "
"[dumpluaraw] [opcode OPNUM]"
<< endl;
static std::unordered_set<uint16_t> s_expectedIDs;
static void fillPacket(vector<uint8_t>& packet, const string& q, const string& t,
- bool dnssec, const boost::optional<Netmask> ednsnm,
- bool recurse, uint16_t xpfcode, uint16_t xpfversion,
- uint64_t xpfproto, char* xpfsrc, char* xpfdst,
- QClass qclass, uint8_t opcode, uint16_t qid)
+ bool dnssec, const boost::optional<Netmask>& ednsnm,
+ bool recurse, QClass qclass, uint8_t opcode, uint16_t qid)
{
DNSPacketWriter pw(packet, DNSName(q), DNSRecordContent::TypeToNumber(t), qclass, opcode);
pw.commit();
}
- if (xpfcode) {
- ComboAddress src(xpfsrc), dst(xpfdst);
- pw.startRecord(g_rootdnsname, xpfcode, 0, QClass::IN, DNSResourceRecord::ADDITIONAL);
- // xpf->toPacket(pw);
- pw.xfr8BitInt(xpfversion);
- pw.xfr8BitInt(xpfproto);
- pw.xfrCAWithoutPort(xpfversion, src);
- pw.xfrCAWithoutPort(xpfversion, dst);
- pw.xfrCAPort(src);
- pw.xfrCAPort(dst);
- pw.commit();
- }
-
if (recurse) {
pw.getHeader()->rd = true;
}
}
} else if (iter->first == EDNSOptionCode::PADDING) {
cerr << "EDNS Padding size: " << (iter->second.size()) << endl;
+ } else if (iter->first == EDNSOptionCode::EXTENDEDERROR) {
+ EDNSExtendedError eee;
+ if (getEDNSExtendedErrorOptFromString(iter->second, eee)) {
+ cerr << "EDNS Extended Error response: " << eee.infoCode << "/" << eee.extraText << endl;
+ }
} else {
cerr << "Have unknown option " << (int)iter->first << endl;
}
bool insecureDoT = false;
bool fromstdin = false;
boost::optional<Netmask> ednsnm;
- uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
- char *xpfsrc = NULL, *xpfdst = NULL;
QClass qclass = QClass::IN;
uint8_t opcode = 0;
string proxyheader;
}
ednsnm = Netmask(argv[++i]);
}
- else if (strcmp(argv[i], "xpf") == 0) {
- if (argc < i + 6) {
- cerr << "xpf needs five arguments" << endl;
- exit(EXIT_FAILURE);
- }
- xpfcode = atoi(argv[++i]);
- xpfversion = atoi(argv[++i]);
- xpfproto = atoi(argv[++i]);
- xpfsrc = argv[++i];
- xpfdst = argv[++i];
- }
else if (strcmp(argv[i], "class") == 0) {
if (argc < i+2) {
cerr << "class needs an argument"<<endl;
#ifdef HAVE_LIBCURL
vector<uint8_t> packet;
s_expectedIDs.insert(0);
- fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
- xpfproto, xpfsrc, xpfdst, qclass, opcode, 0);
+ fillPacket(packet, name, type, dnssec, ednsnm, recurse, qclass, opcode, 0);
MiniCurl mc;
MiniCurl::MiniCurlHeaders mch;
mch.emplace("Content-Type", "application/dns-message");
Socket sock(dest.sin4.sin_family, SOCK_STREAM);
sock.setNonBlocking();
setTCPNoDelay(sock.getHandle()); // disable NAGLE, which does not play nicely with delayed ACKs
- TCPIOHandler handler(subjectName, false, sock.releaseHandle(), timeout, tlsCtx);
+ TCPIOHandler handler(subjectName, false, sock.releaseHandle(), timeout, std::move(tlsCtx));
handler.connect(fastOpen, dest, timeout);
// we are writing the proxyheader inside the TLS connection. Is that right?
if (proxyheader.size() > 0 && handler.write(proxyheader.data(), proxyheader.size(), timeout) != proxyheader.size()) {
for (const auto& it : questions) {
vector<uint8_t> packet;
s_expectedIDs.insert(counter);
- fillPacket(packet, it.first, it.second, dnssec, ednsnm, recurse, xpfcode,
- xpfversion, xpfproto, xpfsrc, xpfdst, qclass, opcode, counter);
+ fillPacket(packet, it.first, it.second, dnssec, ednsnm, recurse, qclass, opcode, counter);
counter++;
// Prefer to do a single write, so that fastopen can send all the data on SYN
{
vector<uint8_t> packet;
s_expectedIDs.insert(0);
- fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
- xpfproto, xpfsrc, xpfdst, qclass, opcode, 0);
+ fillPacket(packet, name, type, dnssec, ednsnm, recurse, qclass, opcode, 0);
string question(packet.begin(), packet.end());
Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
question = proxyheader + question;
}
static void setSecPollToUnknownOnOK(int &secPollStatus) {
- if(secPollStatus == 1) // it was ok, now it is unknown
+ if (secPollStatus == 1) { // it was ok, now it is unknown
secPollStatus = 0;
+ }
}
void processSecPoll(const int res, const std::vector<DNSRecord> &ret, int &secPollStatus, std::string &secPollMessage) {
}
if (ret.empty()) { // empty NOERROR... wat?
- if(secPollStatus == 1) // it was ok, now it is unknown
+ if (secPollStatus == 1) { // it was ok, now it is unknown
secPollStatus = 0;
+ }
throw PDNSException("Had empty answer on NOERROR RCODE");
}
DNSRecord record;
- for (auto const &r: ret) {
- if (r.d_type == QType::TXT && r.d_place == DNSResourceRecord::Place::ANSWER) {
- record = r;
+ for (auto const &records: ret) {
+ if (records.d_type == QType::TXT && records.d_place == DNSResourceRecord::Place::ANSWER) {
+ record = records;
break;
}
}
return (old_serial + (inception / (7*86400)));
}
else if(pdns_iequals(kind,"EPOCH")) {
+ // coverity[store_truncates_time_t]
return time(nullptr);
}
else if(pdns_iequals(kind,"INCEPTION-EPOCH")) {
return old_serial + 1;
}
else if (pdns_iequals(increaseKind, "EPOCH")) {
+ // coverity[store_truncates_time_t]
return time(nullptr);
}
else if (pdns_iequals(increaseKind, "DEFAULT")) {
*/
#pragma once
+#include "config.h"
+#include <array>
+#include <memory>
+#include <stdexcept>
#include <string>
#include <openssl/sha.h>
#include <openssl/evp.h>
-inline std::string pdns_sha1sum(const std::string& input)
+namespace pdns
+{
+inline std::string sha1sum(const std::string& input)
{
- unsigned char result[20] = {0};
- SHA1(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
- return std::string(result, result + sizeof result);
+ std::array<unsigned char, 20> result{};
+ // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+ SHA1(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result.data());
+ return {result.begin(), result.end()};
}
-inline std::string pdns_sha256sum(const std::string& input)
+inline std::string sha256sum(const std::string& input)
{
- unsigned char result[32] = {0};
- SHA256(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
- return std::string(result, result + sizeof result);
+ std::array<unsigned char, 32> result{};
+ // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+ SHA256(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result.data());
+ return {result.begin(), result.end()};
}
-inline std::string pdns_sha384sum(const std::string& input)
+inline std::string sha384sum(const std::string& input)
{
- unsigned char result[48] = {0};
- SHA384(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
- return std::string(result, result + sizeof result);
+ std::array<unsigned char, 48> result{};
+ // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+ SHA384(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result.data());
+ return {result.begin(), result.end()};
}
-inline std::string pdns_sha512sum(const std::string& input)
+inline std::string sha512sum(const std::string& input)
{
- unsigned char result[64] = {0};
- SHA512(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
- return std::string(result, result + sizeof result);
+ std::array<unsigned char, 64> result{};
+ // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+ SHA512(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result.data());
+ return {result.begin(), result.end()};
}
-namespace pdns
-{
class SHADigest
{
public:
default:
throw std::invalid_argument("SHADigest: unsupported size");
}
- if (EVP_DigestInit_ex(mdctx.get(), md, NULL) == 0) {
+ if (EVP_DigestInit_ex(mdctx.get(), md, nullptr) == 0) {
throw std::runtime_error("SHADigest: init error");
}
}
- ~SHADigest()
- {
- // No free of md needed and mdctx is cleaned up by unique_ptr
- }
+ // No free of md needed and mdctx is cleaned up by unique_ptr
+ ~SHADigest() = default;
void process(const std::string& msg)
{
{
std::string md_value;
md_value.resize(EVP_MD_size(md));
- unsigned int md_len;
+ unsigned int md_len = 0;
if (EVP_DigestFinal_ex(mdctx.get(), reinterpret_cast<unsigned char*>(md_value.data()), &md_len) == 0) {
throw std::runtime_error("SHADigest: finalize error");
}
void pdns::orderAndShuffle(vector<DNSRecord>& rrs, bool includingAdditionals)
{
std::stable_sort(rrs.begin(), rrs.end(), [](const DNSRecord& a, const DNSRecord& b) {
- return std::make_tuple(a.d_place, mapTypesToOrder(a.d_type)) < std::make_tuple(b.d_place, mapTypesToOrder(b.d_type));
+ return std::tuple(a.d_place, mapTypesToOrder(a.d_type)) < std::tuple(b.d_place, mapTypesToOrder(b.d_type));
});
shuffle(rrs, includingAdditionals);
}
return nullptr;
}
-ChunkedSigningPipe::ChunkedSigningPipe(DNSName signerName, bool mustSign, unsigned int workers)
+ChunkedSigningPipe::ChunkedSigningPipe(DNSName signerName, bool mustSign, unsigned int workers, unsigned int maxChunkRecords)
: d_signed(0), d_queued(0), d_outstanding(0), d_numworkers(workers), d_submitted(0), d_signer(std::move(signerName)),
- d_maxchunkrecords(100), d_threads(d_numworkers), d_mustSign(mustSign), d_final(false)
+ d_maxchunkrecords(maxChunkRecords), d_threads(d_numworkers), d_mustSign(mustSign), d_final(false)
{
d_rrsetToSign = make_unique<rrset_t>();
d_chunks.push_back(vector<DNSZoneRecord>()); // load an empty chunk
bool
dedupLessThan(const DNSZoneRecord& a, const DNSZoneRecord &b)
{
- return std::make_tuple(a.dr.getContent()->getZoneRepresentation(), a.dr.d_ttl) < std::make_tuple(b.dr.getContent()->getZoneRepresentation(), b.dr.d_ttl); // XXX SLOW SLOW SLOW
+ return std::tuple(a.dr.getContent()->getZoneRepresentation(), a.dr.d_ttl) < std::tuple(b.dr.getContent()->getZoneRepresentation(), b.dr.d_ttl); // XXX SLOW SLOW SLOW
}
bool dedupEqual(const DNSZoneRecord& a, const DNSZoneRecord &b)
{
- return std::make_tuple(a.dr.getContent()->getZoneRepresentation(), a.dr.d_ttl) == std::make_tuple(b.dr.getContent()->getZoneRepresentation(), b.dr.d_ttl); // XXX SLOW SLOW SLOW
+ return std::tuple(a.dr.getContent()->getZoneRepresentation(), a.dr.d_ttl) == std::tuple(b.dr.getContent()->getZoneRepresentation(), b.dr.d_ttl); // XXX SLOW SLOW SLOW
}
}
ChunkedSigningPipe(const ChunkedSigningPipe&) = delete;
void operator=(const ChunkedSigningPipe&) = delete;
- ChunkedSigningPipe(DNSName signerName, bool mustSign, unsigned int numWorkers=3);
+ ChunkedSigningPipe(DNSName signerName, bool mustSign, unsigned int numWorkers, unsigned int maxChunkRecords);
~ChunkedSigningPipe();
bool submit(const DNSZoneRecord& rr);
chunk_t getChunk(bool final=false);
# include <net-snmp/library/large_fd_set.h>
#endif
-const oid SNMPAgent::snmpTrapOID[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
-const size_t SNMPAgent::snmpTrapOIDLen = OID_LENGTH(SNMPAgent::snmpTrapOID);
+const std::array<oid, 11> SNMPAgent::snmpTrapOID = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
int SNMPAgent::setCounter64Value(netsnmp_request_info* request,
uint64_t value)
return SNMP_ERR_NOERROR;
}
-bool SNMPAgent::sendTrap(int fd,
+bool SNMPAgent::sendTrap(pdns::channel::Sender<netsnmp_variable_list, void(*)(netsnmp_variable_list*)>& sender,
netsnmp_variable_list* varList)
{
- ssize_t written = write(fd, &varList, sizeof(varList));
-
- if (written != sizeof(varList)) {
- snmp_free_varbind(varList);
+ try {
+ auto obj = std::unique_ptr<netsnmp_variable_list, void(*)(netsnmp_variable_list*)>(varList, snmp_free_varbind);
+ return sender.send(std::move(obj));
+ }
+ catch (...) {
return false;
}
- return true;
}
void SNMPAgent::handleTrapsEvent()
{
- netsnmp_variable_list* varList = nullptr;
- ssize_t got = 0;
-
- do {
- got = read(d_trapPipe[0], &varList, sizeof(varList));
-
- if (got == sizeof(varList)) {
- send_v2trap(varList);
- snmp_free_varbind(varList);
+ try {
+ while (true) {
+ auto obj = d_receiver.receive(snmp_free_varbind);
+ if (!obj) {
+ break;
+ }
+ send_v2trap(obj->get());
}
}
- while (got > 0);
+ catch (const std::exception& e) {
+ }
}
void SNMPAgent::handleSNMPQueryEvent(int fd)
/* we want to be notified if a trap is waiting
to be sent */
- mplexer->addReadFD(d_trapPipe[0], &handleTrapsCB, this);
+ mplexer->addReadFD(d_receiver.getDescriptor(), &handleTrapsCB, this);
while(true) {
netsnmp_large_fd_set_init(&fdset, FD_SETSIZE);
#endif /* HAVE_NET_SNMP */
}
-SNMPAgent::SNMPAgent(const std::string& name, const std::string& daemonSocket)
+SNMPAgent::SNMPAgent([[maybe_unused]] const std::string& name, [[maybe_unused]] const std::string& daemonSocket)
{
#ifdef HAVE_NET_SNMP
netsnmp_enable_subagent();
init_snmp(name.c_str());
- if (pipe(d_trapPipe) < 0)
- unixDie("Creating pipe");
-
- if (!setNonBlocking(d_trapPipe[0])) {
- close(d_trapPipe[0]);
- close(d_trapPipe[1]);
- unixDie("Setting pipe non-blocking");
- }
-
- if (!setNonBlocking(d_trapPipe[1])) {
- close(d_trapPipe[0]);
- close(d_trapPipe[1]);
- unixDie("Setting pipe non-blocking");
- }
-
+ auto [sender, receiver] = pdns::channel::createObjectQueue<netsnmp_variable_list, void(*)(netsnmp_variable_list*)>();
+ d_sender = std::move(sender);
+ d_receiver = std::move(receiver);
#endif /* HAVE_NET_SNMP */
}
#endif /* HAVE_NET_SNMP */
#include "mplexer.hh"
+#include "channel.hh"
class SNMPAgent
{
SNMPAgent(const std::string& name, const std::string& daemonSocket);
virtual ~SNMPAgent()
{
-#ifdef HAVE_NET_SNMP
-
- close(d_trapPipe[0]);
- close(d_trapPipe[1]);
-#endif /* HAVE_NET_SNMP */
}
void run()
protected:
#ifdef HAVE_NET_SNMP
/* OID for snmpTrapOID.0 */
- static const oid snmpTrapOID[];
- static const size_t snmpTrapOIDLen;
+ static const std::array<oid, 11> snmpTrapOID;
- static bool sendTrap(int fd,
+ static bool sendTrap(pdns::channel::Sender<netsnmp_variable_list, void(*)(netsnmp_variable_list*)>& sender,
netsnmp_variable_list* varList);
- int d_trapPipe[2] = { -1, -1};
+ pdns::channel::Sender<netsnmp_variable_list, void(*)(netsnmp_variable_list*)> d_sender;
+ pdns::channel::Receiver<netsnmp_variable_list, void(*)(netsnmp_variable_list*)> d_receiver;
#endif /* HAVE_NET_SNMP */
private:
void worker();
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#include <iostream>
-#include "namespaces.hh"
-#include "noinitvector.hh"
-#include "misc.hh"
-#include "base64.hh"
-#include "sodcrypto.hh"
-
-
-#ifdef HAVE_LIBSODIUM
-
-string newKey()
-{
- std::string key;
- key.resize(crypto_secretbox_KEYBYTES);
-
- randombytes_buf(reinterpret_cast<unsigned char*>(&key.at(0)), key.size());
-
- return "\""+Base64Encode(key)+"\"";
-}
-
-bool sodIsValidKey(const std::string& key)
-{
- return key.size() == crypto_secretbox_KEYBYTES;
-}
-
-std::string sodEncryptSym(const std::string& msg, const std::string& key, SodiumNonce& nonce)
-{
- if (!sodIsValidKey(key)) {
- throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
- }
-
- std::string ciphertext;
- ciphertext.resize(msg.length() + crypto_secretbox_MACBYTES);
- crypto_secretbox_easy(reinterpret_cast<unsigned char*>(&ciphertext.at(0)),
- reinterpret_cast<const unsigned char*>(msg.c_str()),
- msg.length(),
- nonce.value,
- reinterpret_cast<const unsigned char*>(key.c_str()));
-
- nonce.increment();
- return ciphertext;
-}
-
-std::string sodDecryptSym(const std::string& msg, const std::string& key, SodiumNonce& nonce)
-{
- std::string decrypted;
-
- if (msg.length() < crypto_secretbox_MACBYTES) {
- throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
- }
-
- if (!sodIsValidKey(key)) {
- throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
- }
-
- decrypted.resize(msg.length() - crypto_secretbox_MACBYTES);
-
- if (crypto_secretbox_open_easy(reinterpret_cast<unsigned char*>(const_cast<char *>(decrypted.data())),
- reinterpret_cast<const unsigned char*>(msg.c_str()),
- msg.length(),
- nonce.value,
- reinterpret_cast<const unsigned char*>(key.c_str())) != 0) {
- throw std::runtime_error("Could not decrypt message, please check that the key configured with setKey() is correct");
- }
-
- nonce.increment();
- return decrypted;
-}
-#else
-std::string sodEncryptSym(const std::string& msg, const std::string& key, SodiumNonce& nonce)
-{
- return msg;
-}
-std::string sodDecryptSym(const std::string& msg, const std::string& key, SodiumNonce& nonce)
-{
- return msg;
-}
-
-string newKey()
-{
- return "\"plaintext\"";
-}
-
-bool sodIsValidKey(const std::string& key)
-{
- return true;
-}
-
-#endif
-
-
-#include "base64.hh"
-#include <inttypes.h>
-
-namespace anonpdns {
-static char B64Decode1(char cInChar)
-{
- // The incoming character will be A-Z, a-z, 0-9, +, /, or =.
- // The idea is to quickly determine which grouping the
- // letter belongs to and return the associated value
- // without having to search the global encoding string
- // (the value we're looking for would be the resulting
- // index into that string).
- //
- // To do that, we'll play some tricks...
- unsigned char iIndex = '\0';
- switch ( cInChar ) {
- case '+':
- iIndex = 62;
- break;
-
- case '/':
- iIndex = 63;
- break;
-
- case '=':
- iIndex = 0;
- break;
-
- default:
- // Must be 'A'-'Z', 'a'-'z', '0'-'9', or an error...
- //
- // Numerically, small letters are "greater" in value than
- // capital letters and numerals (ASCII value), and capital
- // letters are "greater" than numerals (again, ASCII value),
- // so we check for numerals first, then capital letters,
- // and finally small letters.
- iIndex = '9' - cInChar;
- if ( iIndex > 0x3F ) {
- // Not from '0' to '9'...
- iIndex = 'Z' - cInChar;
- if ( iIndex > 0x3F ) {
- // Not from 'A' to 'Z'...
- iIndex = 'z' - cInChar;
- if ( iIndex > 0x3F ) {
- // Invalid character...cannot
- // decode!
- iIndex = 0x80; // set the high bit
- } // if
- else {
- // From 'a' to 'z'
- iIndex = (('z' - iIndex) - 'a') + 26;
- } // else
- } // if
- else {
- // From 'A' to 'Z'
- iIndex = ('Z' - iIndex) - 'A';
- } // else
- } // if
- else {
- // Adjust the index...
- iIndex = (('9' - iIndex) - '0') + 52;
- } // else
- break;
-
- } // switch
-
- return iIndex;
-}
-
-static inline char B64Encode1(unsigned char uc)
-{
- if (uc < 26)
- {
- return 'A'+uc;
- }
- if (uc < 52)
- {
- return 'a'+(uc-26);
- }
- if (uc < 62)
- {
- return '0'+(uc-52);
- }
- if (uc == 62)
- {
- return '+';
- }
- return '/';
-};
-
-
-
-}
-using namespace anonpdns;
-
-template<typename Container> int B64Decode(const std::string& strInput, Container& strOutput)
-{
- // Set up a decoding buffer
- long cBuf = 0;
- char* pBuf = (char*)&cBuf;
-
- // Decoding management...
- int iBitGroup = 0, iInNum = 0;
-
- // While there are characters to process...
- //
- // We'll decode characters in blocks of 4, as
- // there are 4 groups of 6 bits in 3 bytes. The
- // incoming Base64 character is first decoded, and
- // then it is inserted into the decode buffer
- // (with any relevant shifting, as required).
- // Later, after all 3 bytes have been reconstituted,
- // we assign them to the output string, ultimately
- // to be returned as the original message.
- int iInSize = strInput.size();
- unsigned char cChar = '\0';
- uint8_t pad = 0;
- while ( iInNum < iInSize ) {
- // Fill the decode buffer with 4 groups of 6 bits
- cBuf = 0; // clear
- pad = 0;
- for ( iBitGroup = 0; iBitGroup < 4; ++iBitGroup ) {
- if ( iInNum < iInSize ) {
- // Decode a character
- if(strInput.at(iInNum)=='=')
- pad++;
- while(isspace(strInput.at(iInNum)))
- iInNum++;
- cChar = B64Decode1(strInput.at(iInNum++));
-
- } // if
- else {
- // Decode a padded zero
- cChar = '\0';
- } // else
-
- // Check for valid decode
- if ( cChar > 0x7F )
- return -1;
-
- // Adjust the bits
- switch ( iBitGroup ) {
- case 0:
- // The first group is copied into
- // the least significant 6 bits of
- // the decode buffer...these 6 bits
- // will eventually shift over to be
- // the most significant bits of the
- // third byte.
- cBuf = cBuf | cChar;
- break;
-
- default:
- // For groupings 1-3, simply shift
- // the bits in the decode buffer over
- // by 6 and insert the 6 from the
- // current decode character.
- cBuf = (cBuf << 6) | cChar;
- break;
-
- } // switch
- } // for
-
- // Interpret the resulting 3 bytes...note there
- // may have been padding, so those padded bytes
- // are actually ignored.
-#if BYTE_ORDER == BIG_ENDIAN
- strOutput.push_back(pBuf[sizeof(long)-3]);
- strOutput.push_back(pBuf[sizeof(long)-2]);
- strOutput.push_back(pBuf[sizeof(long)-1]);
-#else
- strOutput.push_back(pBuf[2]);
- strOutput.push_back(pBuf[1]);
- strOutput.push_back(pBuf[0]);
-#endif
- } // while
- if(pad)
- strOutput.resize(strOutput.size()-pad);
-
- return 1;
-}
-
-template int B64Decode<std::vector<uint8_t>>(const std::string& strInput, std::vector<uint8_t>& strOutput);
-template int B64Decode<PacketBuffer>(const std::string& strInput, PacketBuffer& strOutput);
-template int B64Decode<std::string>(const std::string& strInput, std::string& strOutput);
-
-/*
-www.kbcafe.com
-Copyright 2001-2002 Randy Charles Morin
-The Encode static method takes an array of 8-bit values and returns a base-64 stream.
-*/
-
-
-std::string Base64Encode (const std::string& vby)
-{
- std::string retval;
- if (vby.size () == 0)
- {
- return retval;
- };
- for (unsigned int i = 0; i < vby.size (); i += 3)
- {
- unsigned char by1 = 0, by2 = 0, by3 = 0;
- by1 = vby[i];
- if (i + 1 < vby.size ())
- {
- by2 = vby[i + 1];
- };
- if (i + 2 < vby.size ())
- {
- by3 = vby[i + 2];
- }
- unsigned char by4 = 0, by5 = 0, by6 = 0, by7 = 0;
- by4 = by1 >> 2;
- by5 = ((by1 & 0x3) << 4) | (by2 >> 4);
- by6 = ((by2 & 0xf) << 2) | (by3 >> 6);
- by7 = by3 & 0x3f;
- retval += B64Encode1 (by4);
- retval += B64Encode1 (by5);
- if (i + 1 < vby.size ())
- {
- retval += B64Encode1 (by6);
- }
- else
- {
- retval += "=";
- };
- if (i + 2 < vby.size ())
- {
- retval += B64Encode1 (by7);
- }
- else
- {
- retval += "=";
- };
- /* if ((i % (76 / 4 * 3)) == 0)
- {
- retval += "\r\n";
- }*/
- };
- return retval;
-};
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#pragma once
-#include "config.h"
-#include <string>
-#include <stdint.h>
-
-#include <arpa/inet.h>
-
-#ifndef HAVE_LIBSODIUM
-struct SodiumNonce
-{
- void init(){};
- void merge(const SodiumNonce& lower, const SodiumNonce& higher) {};
- void increment(){};
- unsigned char value[1]{0};
-};
-#else
-#include <sodium.h>
-
-struct SodiumNonce
-{
- SodiumNonce()
- {
- memset(&value, 0, sizeof(value));
- }
-
- void init()
- {
- randombytes_buf(value, sizeof value);
- }
-
- void merge(const SodiumNonce& lower, const SodiumNonce& higher)
- {
- static const size_t halfSize = (sizeof value) / 2;
- memcpy(value, lower.value, halfSize);
- memcpy(value + halfSize, higher.value + halfSize, halfSize);
- }
-
- void increment()
- {
- uint32_t* p = (uint32_t*)value;
- uint32_t count=htonl(*p);
- *p=ntohl(++count);
- }
-
- string toString() const
- {
- return string((const char*)value, crypto_secretbox_NONCEBYTES);
- }
-
- unsigned char value[crypto_secretbox_NONCEBYTES];
-};
-#endif
-std::string newKeypair();
-std::string sodEncryptSym(const std::string& msg, const std::string& key, SodiumNonce&);
-std::string sodDecryptSym(const std::string& msg, const std::string& key, SodiumNonce&);
-std::string newKey();
-bool sodIsValidKey(const std::string& key);
for(char c='a'; c<= 'm';++c) {
pw.startRecord(DNSName("com"), QType::NS, 3600, 1, DNSResourceRecord::AUTHORITY);
gtld[0]=c;
- auto drc = DNSRecordContent::mastermake(QType::NS, 1, gtld);
+ auto drc = DNSRecordContent::make(QType::NS, 1, gtld);
drc->toPacket(pw);
}
for(char c='a'; c<= 'k';++c) {
gtld[0]=c;
pw.startRecord(DNSName(gtld), QType::A, 3600, 1, DNSResourceRecord::ADDITIONAL);
- auto drc = DNSRecordContent::mastermake(QType::A, 1, "1.2.3.4");
+ auto drc = DNSRecordContent::make(QType::A, 1, "1.2.3.4");
drc->toPacket(pw);
}
pw.startRecord(DNSName("a.gtld-servers.net"), QType::AAAA, 3600, 1, DNSResourceRecord::ADDITIONAL);
- auto aaaarc = DNSRecordContent::mastermake(QType::AAAA, 1, "2001:503:a83e::2:30");
+ auto aaaarc = DNSRecordContent::make(QType::AAAA, 1, "2001:503:a83e::2:30");
aaaarc->toPacket(pw);
pw.startRecord(DNSName("b.gtld-servers.net"), QType::AAAA, 3600, 1, DNSResourceRecord::ADDITIONAL);
- aaaarc = DNSRecordContent::mastermake(QType::AAAA, 1, "2001:503:231d::2:30");
+ aaaarc = DNSRecordContent::make(QType::AAAA, 1, "2001:503:231d::2:30");
aaaarc->toPacket(pw);
// shuffle(records);
for(const auto& rec : records) {
pw.startRecord(rec.qname, rec.qtype.getCode(), rec.ttl, 1, DNSResourceRecord::ADDITIONAL);
- auto drc = DNSRecordContent::mastermake(rec.qtype.getCode(), 1, rec.content);
+ auto drc = DNSRecordContent::make(rec.qtype.getCode(), 1, rec.content);
drc->toPacket(pw);
}
void operator()() const
{
- auto drc = DNSRecordContent::mastermake(QType::A, 1,
- "1.2.3.4");
+ auto drc = DNSRecordContent::make(QType::A, 1,
+ "1.2.3.4");
}
};
DNSPacketWriter pw(packet, DNSName("outpost.ds9a.nl"), d_type);
for(int records = 0; records < d_records; records++) {
pw.startRecord(DNSName("outpost.ds9a.nl"), d_type);
- auto drc = DNSRecordContent::mastermake(d_type, 1,
- d_content);
+ auto drc = DNSRecordContent::make(d_type, 1,
+ d_content);
drc->toPacket(pw);
}
pw.commit();
DNSPacketWriter pw(packet, DNSName("outpost.ds9a.nl"), QType::AAAA);
for(int records = 0; records < d_records; records++) {
pw.startRecord(DNSName("outpost.ds9a.nl"), QType::AAAA);
- auto drc = DNSRecordContent::mastermake(QType::AAAA, 1, "fe80::21d:92ff:fe6d:8441");
+ auto drc = DNSRecordContent::make(QType::AAAA, 1, "fe80::21d:92ff:fe6d:8441");
drc->toPacket(pw);
}
pw.commit();
for(int records = 0; records < d_records; records++) {
pw.startRecord(DNSName("outpost.ds9a.nl"), QType::SOA);
- auto drc = DNSRecordContent::mastermake(QType::SOA, 1, "a0.org.afilias-nst.info. noc.afilias-nst.info. 2008758137 1800 900 604800 86400");
+ auto drc = DNSRecordContent::make(QType::SOA, 1, "a0.org.afilias-nst.info. noc.afilias-nst.info. 2008758137 1800 900 604800 86400");
drc->toPacket(pw);
}
pw.commit();
DNSPacketWriter pw(packet, DNSName("outpost.ds9a.nl"), QType::A);
pw.startRecord(DNSName("ds9a.nl"), QType::NS, 3600, 1, DNSResourceRecord::AUTHORITY);
- auto drc = DNSRecordContent::mastermake(QType::NS, 1, "ns1.ds9a.nl");
+ auto drc = DNSRecordContent::make(QType::NS, 1, "ns1.ds9a.nl");
drc->toPacket(pw);
pw.startRecord(DNSName("ds9a.nl"), QType::NS, 3600, 1, DNSResourceRecord::AUTHORITY);
- drc = DNSRecordContent::mastermake(QType::NS, 1, "ns2.ds9a.nl");
+ drc = DNSRecordContent::make(QType::NS, 1, "ns2.ds9a.nl");
drc->toPacket(pw);
pw.startRecord(DNSName("ns1.ds9a.nl"), QType::A, 3600, 1, DNSResourceRecord::ADDITIONAL);
- drc = DNSRecordContent::mastermake(QType::A, 1, "1.2.3.4");
+ drc = DNSRecordContent::make(QType::A, 1, "1.2.3.4");
drc->toPacket(pw);
pw.startRecord(DNSName("ns2.ds9a.nl"), QType::A, 3600, 1, DNSResourceRecord::ADDITIONAL);
- drc = DNSRecordContent::mastermake(QType::A, 1, "4.3.2.1");
+ drc = DNSRecordContent::make(QType::A, 1, "4.3.2.1");
drc->toPacket(pw);
pw.commit();
rr.qname=i->first.d_name;
rr.ttl=i->first.d_ttl;
- rr.content=i->first.d_content->getZoneRepresentation(); // this should be the serialised form
+ rr.content=i->first.getContent()->getZoneRepresentation(); // this should be the serialised form
lwr.d_result.push_back(rr);
}
struct NSEC3HashTest
{
- explicit NSEC3HashTest(int iterations, string salt) : d_iterations(iterations), d_salt(salt) {}
+ explicit NSEC3HashTest(int iterations, string salt) : d_iterations(iterations), d_salt(std::move(salt)) {}
string getName() const
{
explicit RndSpeedTest(std::string which) : name(which){
::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
::arg().set("rng", "") = which;
- dns_random_init("", true);
}
string getName() const
{
};
#endif
-int main(int argc, char** argv)
-try
+int main()
{
- reportAllTypes();
+ try {
+ reportAllTypes();
- doRun(NOPTest());
+ doRun(NOPTest());
- doRun(IEqualsTest());
- doRun(MyIEqualsTest());
- doRun(StrcasecmpTest());
- doRun(Base64EncodeTest());
- doRun(B64DecodeTest());
+ doRun(IEqualsTest());
+ doRun(MyIEqualsTest());
+ doRun(StrcasecmpTest());
+ doRun(Base64EncodeTest());
+ doRun(B64DecodeTest());
- doRun(StackMallocTest());
+ doRun(StackMallocTest());
- doRun(EmptyQueryTest());
- doRun(TypicalRefTest());
- doRun(BigRefTest());
- doRun(BigDNSPacketRefTest());
+ doRun(EmptyQueryTest());
+ doRun(TypicalRefTest());
+ doRun(BigRefTest());
+ doRun(BigDNSPacketRefTest());
- auto packet = makeEmptyQuery();
- doRun(ParsePacketTest(packet, "empty-query"));
+ auto packet = makeEmptyQuery();
+ doRun(ParsePacketTest(packet, "empty-query"));
- packet = makeTypicalReferral();
- cerr<<"typical referral size: "<<packet.size()<<endl;
- doRun(ParsePacketBareTest(packet, "typical-referral"));
+ packet = makeTypicalReferral();
+ cerr<<"typical referral size: "<<packet.size()<<endl;
+ doRun(ParsePacketBareTest(packet, "typical-referral"));
- doRun(ParsePacketTest(packet, "typical-referral"));
+ doRun(ParsePacketTest(packet, "typical-referral"));
- doRun(SimpleCompressTest("www.france.ds9a.nl"));
+ doRun(SimpleCompressTest("www.france.ds9a.nl"));
- doRun(VectorExpandTest());
+ doRun(VectorExpandTest());
- doRun(GetTimeTest());
+ doRun(GetTimeTest());
- doRun(GetLockUncontendedTest());
- doRun(GetUniqueLockUncontendedTest());
- doRun(GetLockGuardUncontendedTest());
- doRun(GetLockGuardedUncontendedTest());
- doRun(SharedLockTest());
+ doRun(GetLockUncontendedTest());
+ doRun(GetUniqueLockUncontendedTest());
+ doRun(GetLockGuardUncontendedTest());
+ doRun(GetLockGuardedUncontendedTest());
+ doRun(SharedLockTest());
- {
- ReadWriteLock rwlock;
- doRun(ReadWriteLockSharedTest(rwlock));
- doRun(ReadWriteLockExclusiveTest(rwlock));
- doRun(ReadWriteLockExclusiveTryTest(rwlock, false));
- {
- ReadLock rl(rwlock);
- doRun(ReadWriteLockExclusiveTryTest(rwlock, true));
- doRun(ReadWriteLockSharedTryTest(rwlock, false));
- }
{
- WriteLock wl(rwlock);
- doRun(ReadWriteLockSharedTryTest(rwlock, true));
+ ReadWriteLock rwlock;
+ doRun(ReadWriteLockSharedTest(rwlock));
+ doRun(ReadWriteLockExclusiveTest(rwlock));
+ doRun(ReadWriteLockExclusiveTryTest(rwlock, false));
+ {
+ ReadLock rl(rwlock);
+ doRun(ReadWriteLockExclusiveTryTest(rwlock, true));
+ doRun(ReadWriteLockSharedTryTest(rwlock, false));
+ }
+ {
+ WriteLock wl(rwlock);
+ doRun(ReadWriteLockSharedTryTest(rwlock, true));
+ }
}
- }
- doRun(StaticMemberTest());
+ doRun(StaticMemberTest());
- doRun(ARecordTest(1));
- doRun(ARecordTest(2));
- doRun(ARecordTest(4));
- doRun(ARecordTest(64));
+ doRun(ARecordTest(1));
+ doRun(ARecordTest(2));
+ doRun(ARecordTest(4));
+ doRun(ARecordTest(64));
- doRun(A2RecordTest(1));
- doRun(A2RecordTest(2));
- doRun(A2RecordTest(4));
- doRun(A2RecordTest(64));
+ doRun(A2RecordTest(1));
+ doRun(A2RecordTest(2));
+ doRun(A2RecordTest(4));
+ doRun(A2RecordTest(64));
- doRun(MakeStringFromCharStarTest());
- doRun(MakeARecordTest());
- doRun(MakeARecordTestMM());
+ doRun(MakeStringFromCharStarTest());
+ doRun(MakeARecordTest());
+ doRun(MakeARecordTestMM());
- doRun(AAAARecordTest(1));
- doRun(AAAARecordTest(2));
- doRun(AAAARecordTest(4));
- doRun(AAAARecordTest(64));
+ doRun(AAAARecordTest(1));
+ doRun(AAAARecordTest(2));
+ doRun(AAAARecordTest(4));
+ doRun(AAAARecordTest(64));
- doRun(TXTRecordTest(1));
- doRun(TXTRecordTest(2));
- doRun(TXTRecordTest(4));
- doRun(TXTRecordTest(64));
+ doRun(TXTRecordTest(1));
+ doRun(TXTRecordTest(2));
+ doRun(TXTRecordTest(4));
+ doRun(TXTRecordTest(64));
- doRun(GenericRecordTest(1, QType::NS, "powerdnssec1.ds9a.nl"));
- doRun(GenericRecordTest(2, QType::NS, "powerdnssec1.ds9a.nl"));
- doRun(GenericRecordTest(4, QType::NS, "powerdnssec1.ds9a.nl"));
- doRun(GenericRecordTest(64, QType::NS, "powerdnssec1.ds9a.nl"));
+ doRun(GenericRecordTest(1, QType::NS, "powerdnssec1.ds9a.nl"));
+ doRun(GenericRecordTest(2, QType::NS, "powerdnssec1.ds9a.nl"));
+ doRun(GenericRecordTest(4, QType::NS, "powerdnssec1.ds9a.nl"));
+ doRun(GenericRecordTest(64, QType::NS, "powerdnssec1.ds9a.nl"));
- doRun(SOARecordTest(1));
- doRun(SOARecordTest(2));
- doRun(SOARecordTest(4));
- doRun(SOARecordTest(64));
+ doRun(SOARecordTest(1));
+ doRun(SOARecordTest(2));
+ doRun(SOARecordTest(4));
+ doRun(SOARecordTest(64));
- doRun(StringtokTest());
- doRun(VStringtokTest());
- doRun(StringAppendTest());
- doRun(BoostStringAppendTest());
+ doRun(StringtokTest());
+ doRun(VStringtokTest());
+ doRun(StringAppendTest());
+ doRun(BoostStringAppendTest());
- doRun(DNSNameParseTest());
- doRun(DNSNameRootTest());
+ doRun(DNSNameParseTest());
+ doRun(DNSNameRootTest());
- doRun(SuffixMatchNodeTest());
+ doRun(SuffixMatchNodeTest());
- doRun(NetmaskTreeTest());
+ doRun(NetmaskTreeTest());
- doRun(UUIDGenTest());
+ doRun(UUIDGenTest());
#if defined(HAVE_GETRANDOM)
- doRun(RndSpeedTest("getrandom"));
+ doRun(RndSpeedTest("getrandom"));
#endif
#if defined(HAVE_ARC4RANDOM)
- doRun(RndSpeedTest("arc4random"));
+ doRun(RndSpeedTest("arc4random"));
#endif
#if defined(HAVE_RANDOMBYTES_STIR)
- doRun(RndSpeedTest("sodium"));
+ doRun(RndSpeedTest("sodium"));
#endif
#if defined(HAVE_RAND_BYTES)
- doRun(RndSpeedTest("openssl"));
+ doRun(RndSpeedTest("openssl"));
#endif
- doRun(RndSpeedTest("urandom"));
+ doRun(RndSpeedTest("urandom"));
- doRun(NSEC3HashTest(1, "ABCD"));
- doRun(NSEC3HashTest(10, "ABCD"));
- doRun(NSEC3HashTest(50, "ABCD"));
- doRun(NSEC3HashTest(150, "ABCD"));
- doRun(NSEC3HashTest(500, "ABCD"));
+ doRun(NSEC3HashTest(1, "ABCD"));
+ doRun(NSEC3HashTest(10, "ABCD"));
+ doRun(NSEC3HashTest(50, "ABCD"));
+ doRun(NSEC3HashTest(150, "ABCD"));
+ doRun(NSEC3HashTest(500, "ABCD"));
- doRun(NSEC3HashTest(1, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
- doRun(NSEC3HashTest(10, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
- doRun(NSEC3HashTest(50, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
- doRun(NSEC3HashTest(150, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
- doRun(NSEC3HashTest(500, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+ doRun(NSEC3HashTest(1, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+ doRun(NSEC3HashTest(10, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+ doRun(NSEC3HashTest(50, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+ doRun(NSEC3HashTest(150, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+ doRun(NSEC3HashTest(500, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
#if defined(HAVE_LIBSODIUM) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
- doRun(CredentialsHashTest());
- doRun(CredentialsVerifyTest());
+ doRun(CredentialsHashTest());
+ doRun(CredentialsVerifyTest());
#endif
#ifndef RECURSOR
- S.doRings();
+ S.doRings();
- S.declareRing("testring", "Just some ring where we'll account things");
- doRun(StatRingDNSNameQTypeToStringTest(DNSName("example.com"), QType(1)));
+ S.declareRing("testring", "Just some ring where we'll account things");
+ doRun(StatRingDNSNameQTypeToStringTest(DNSName("example.com"), QType(1)));
- S.declareDNSNameQTypeRing("testringdnsname", "Just some ring where we'll account things");
- doRun(StatRingDNSNameQTypeTest(DNSName("example.com"), QType(1)));
+ S.declareDNSNameQTypeRing("testringdnsname", "Just some ring where we'll account things");
+ doRun(StatRingDNSNameQTypeTest(DNSName("example.com"), QType(1)));
#endif
- doRun(BurtleHashTest("a string of chars"));
- doRun(BurtleHashCITest("A String Of Chars"));
+ doRun(BurtleHashTest("a string of chars"));
+ doRun(BurtleHashCITest("A String Of Chars"));
#ifdef HAVE_LIBSODIUM
- doRun(SipHashTest("a string of chars"));
+ doRun(SipHashTest("a string of chars"));
#endif
- cerr<<"Total runs: " << g_totalRuns<<endl;
-}
-catch(std::exception &e)
-{
- cerr<<"Fatal: "<<e.what()<<endl;
+ cerr<<"Total runs: " << g_totalRuns<<endl;
+ }
+ catch (std::exception &e) {
+ cerr<<"Fatal: "<<e.what()<<endl;
+ }
+ catch (...) {
+ cerr<<"Fatal: unexpected exception"<<endl;
+ }
}
ArgvMap& arg()
#include "ssqlite3.hh"
#include <iostream>
#include <fstream>
+#include <utility>
#include "pdns/logger.hh"
#include "misc.hh"
#include "utility.hh"
* copied from sqlite 3.3.6 // cmouse
*/
#if SQLITE_VERSION_NUMBER < 3003009
-static int pdns_sqlite3_clear_bindings(sqlite3_stmt *pStmt){
+static int pdns_sqlite3_clear_bindings(sqlite3_stmt* pStmt)
+{
int i;
int rc = SQLITE_OK;
- for(i=1; rc==SQLITE_OK && i<=sqlite3_bind_parameter_count(pStmt); i++){
+ for (i = 1; rc == SQLITE_OK && i <= sqlite3_bind_parameter_count(pStmt); i++) {
rc = sqlite3_bind_null(pStmt, i);
}
return rc;
}
#endif
-static string SSQLite3ErrorString(sqlite3 *db)
+static string SSQLite3ErrorString(sqlite3* database)
{
- return string(sqlite3_errmsg(db)+string(" (")+std::to_string(sqlite3_extended_errcode(db))+string(")"));
+ return string(sqlite3_errmsg(database) + string(" (") + std::to_string(sqlite3_extended_errcode(database)) + string(")"));
}
-class SSQLite3Statement: public SSqlStatement
+class SSQLite3Statement : public SSqlStatement
{
public:
- SSQLite3Statement(SSQLite3 *db, bool dolog, const string& query) :
- d_query(query),
- d_db(db),
+ SSQLite3Statement(SSQLite3* database, bool dolog, string query) :
+ d_query(std::move(query)),
+ d_db(database),
d_dolog(dolog)
{
}
- int name2idx(const string& name) {
- string zName = string(":")+name;
+ SSQLite3Statement(const SSQLite3Statement&) = delete;
+ SSQLite3Statement(SSQLite3Statement&&) = delete;
+ SSQLite3Statement& operator=(const SSQLite3Statement&) = delete;
+ SSQLite3Statement& operator=(SSQLite3Statement&&) = delete;
+
+ int name2idx(const string& name)
+ {
+ string zName = string(":") + name;
prepareStatement();
return sqlite3_bind_parameter_index(d_stmt, zName.c_str());
// XXX: support @ and $?
}
- SSqlStatement* bind(const string& name, bool value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int(d_stmt, idx, value ? 1 : 0); }; return this; }
- SSqlStatement* bind(const string& name, int value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int(d_stmt, idx, value); }; return this; }
- SSqlStatement* bind(const string& name, uint32_t value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; }
- SSqlStatement* bind(const string& name, long value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; }
- SSqlStatement* bind(const string& name, unsigned long value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; }
- SSqlStatement* bind(const string& name, long long value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; };
- SSqlStatement* bind(const string& name, unsigned long long value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; }
- SSqlStatement* bind(const string& name, const std::string& value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_text(d_stmt, idx, value.c_str(), value.size(), SQLITE_TRANSIENT); }; return this; }
- SSqlStatement* bindNull(const string& name) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_null(d_stmt, idx); }; return this; }
-
- SSqlStatement* execute() {
+ SSqlStatement* bind(const string& name, bool value) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_int(d_stmt, idx, value ? 1 : 0);
+ };
+ return this;
+ }
+
+ SSqlStatement* bind(const string& name, int value) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_int(d_stmt, idx, value);
+ };
+ return this;
+ }
+
+ SSqlStatement* bind(const string& name, uint32_t value) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_int64(d_stmt, idx, value);
+ };
+ return this;
+ }
+
+ SSqlStatement* bind(const string& name, long value) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_int64(d_stmt, idx, value);
+ };
+ return this;
+ }
+
+ SSqlStatement* bind(const string& name, unsigned long value) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_int64(d_stmt, idx, static_cast<sqlite3_int64>(value));
+ };
+ return this;
+ }
+
+ SSqlStatement* bind(const string& name, long long value) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_int64(d_stmt, idx, value);
+ };
+ return this;
+ };
+
+ SSqlStatement* bind(const string& name, unsigned long long value) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_int64(d_stmt, idx, static_cast<sqlite3_int64>(value));
+ };
+ return this;
+ }
+
+ SSqlStatement* bind(const string& name, const std::string& value) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_text(d_stmt, idx, value.c_str(), static_cast<int>(value.size()), SQLITE_TRANSIENT);
+ };
+ return this;
+ }
+
+ SSqlStatement* bindNull(const string& name) override
+ {
+ int idx = name2idx(name);
+ if (idx > 0) {
+ sqlite3_bind_null(d_stmt, idx);
+ };
+ return this;
+ }
+
+ SSqlStatement* execute() override
+ {
prepareStatement();
if (d_dolog) {
- g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": " << d_query << endl;
+ g_log << Logger::Warning << "Query " << this << ": " << d_query << endl;
d_dtime.set();
}
- int attempts = d_db->inTransaction(); // try only once
- while(attempts < 2 && (d_rc = sqlite3_step(d_stmt)) == SQLITE_BUSY) attempts++;
+
+ int attempts = d_db->inTransaction() ? 1 : 0; // try only once
+ while (attempts < 2 && (d_rc = sqlite3_step(d_stmt)) == SQLITE_BUSY) {
+ attempts++;
+ }
if (d_rc != SQLITE_ROW && d_rc != SQLITE_DONE) {
// failed.
releaseStatement();
- if (d_rc == SQLITE_CANTOPEN)
- throw SSqlException(string("CANTOPEN error in sqlite3, often caused by unwritable sqlite3 db *directory*: ")+SSQLite3ErrorString(d_db->db()));
- throw SSqlException(string("Error while retrieving SQLite query results: ")+SSQLite3ErrorString(d_db->db()));
+ if (d_rc == SQLITE_CANTOPEN) {
+ throw SSqlException(string("CANTOPEN error in sqlite3, often caused by unwritable sqlite3 db *directory*: ") + SSQLite3ErrorString(d_db->db()));
+ }
+ throw SSqlException(string("Error while retrieving SQLite query results: ") + SSQLite3ErrorString(d_db->db()));
+ }
+ if (d_dolog) {
+ g_log << Logger::Warning << "Query " << this << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
}
- if(d_dolog)
- g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": "<<d_dtime.udiffNoReset()<<" us to execute"<<endl;
return this;
}
- bool hasNextRow() {
- if(d_dolog && d_rc != SQLITE_ROW) {
- g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": "<<d_dtime.udiffNoReset()<<" us total to last row"<<endl;
+
+ bool hasNextRow() override
+ {
+ if (d_dolog && d_rc != SQLITE_ROW) {
+ g_log << Logger::Warning << "Query " << this << ": " << d_dtime.udiffNoReset() << " us total to last row" << endl;
}
return d_rc == SQLITE_ROW;
}
- SSqlStatement* nextRow(row_t& row) {
+ SSqlStatement* nextRow(row_t& row) override
+ {
row.clear();
int numCols = sqlite3_column_count(d_stmt);
row.reserve(numCols); // preallocate memory
// Another row received, process it.
- for ( int i=0; i<numCols; i++)
- {
- if (sqlite3_column_type(d_stmt,i) == SQLITE_NULL) {
+ for (int i = 0; i < numCols; i++) {
+ if (sqlite3_column_type(d_stmt, i) == SQLITE_NULL) {
row.emplace_back("");
- } else {
- const char *pData = (const char*) sqlite3_column_text(d_stmt, i);
+ }
+ else {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): SQLite API returns unsigned char strings and std::strings are signed char.
+ const char* pData = (const char*)sqlite3_column_text(d_stmt, i);
row.emplace_back(pData, sqlite3_column_bytes(d_stmt, i));
}
}
return this;
}
- SSqlStatement* getResult(result_t& result) {
+ SSqlStatement* getResult(result_t& result) override
+ {
result.clear();
- while(hasNextRow()) {
+ while (hasNextRow()) {
row_t row;
nextRow(row);
result.push_back(std::move(row));
return this;
}
- SSqlStatement* reset() {
+ SSqlStatement* reset() override
+ {
sqlite3_reset(d_stmt);
#if SQLITE_VERSION_NUMBER >= 3003009
sqlite3_clear_bindings(d_stmt);
return this;
}
- ~SSQLite3Statement() {
+ ~SSQLite3Statement() override
+ {
// deallocate if necessary
releaseStatement();
}
- const string& getQuery() { return d_query; };
+ const string& getQuery() override { return d_query; };
+
private:
string d_query;
DTime d_dtime;
bool d_dolog;
bool d_prepared{false};
- void prepareStatement() {
- const char *pTail;
+ void prepareStatement()
+ {
+ const char* pTail = nullptr;
- if (d_prepared) return;
+ if (d_prepared) {
+ return;
+ }
#if SQLITE_VERSION_NUMBER >= 3003009
- if (sqlite3_prepare_v2(d_db->db(), d_query.c_str(), -1, &d_stmt, &pTail ) != SQLITE_OK)
+ if (sqlite3_prepare_v2(d_db->db(), d_query.c_str(), -1, &d_stmt, &pTail) != SQLITE_OK)
#else
- if (sqlite3_prepare(d_db->db(), d_query.c_str(), -1, &d_stmt, &pTail ) != SQLITE_OK)
+ if (sqlite3_prepare(d_db->db(), d_query.c_str(), -1, &d_stmt, &pTail) != SQLITE_OK)
#endif
{
releaseStatement();
- throw SSqlException(string("Unable to compile SQLite statement : '")+d_query+"': "+SSQLite3ErrorString(d_db->db()));
+ throw SSqlException(string("Unable to compile SQLite statement : '") + d_query + "': " + SSQLite3ErrorString(d_db->db()));
+ }
+ if ((pTail != nullptr) && strlen(pTail) > 0) {
+ g_log << Logger::Warning << "Sqlite3 command partially processed. Unprocessed part: " << pTail << endl;
}
- if (pTail && strlen(pTail)>0)
- g_log<<Logger::Warning<<"Sqlite3 command partially processed. Unprocessed part: "<<pTail<<endl;
d_prepared = true;
}
- void releaseStatement() {
- if (d_stmt)
+ void releaseStatement()
+ {
+ if (d_stmt != nullptr) {
sqlite3_finalize(d_stmt);
+ }
d_stmt = nullptr;
d_prepared = false;
}
};
// Constructor.
-SSQLite3::SSQLite3( const std::string & database, const std::string & journalmode, bool creat )
+SSQLite3::SSQLite3(const std::string& database, const std::string& journalmode, bool creat)
{
- if (access( database.c_str(), F_OK ) == -1){
- if (!creat)
- throw sPerrorException( "SQLite database '"+database+"' does not exist yet" );
- } else {
- if (creat)
- throw sPerrorException( "SQLite database '"+database+"' already exists" );
+ if (access(database.c_str(), F_OK) == -1) {
+ if (!creat) {
+ throw SSqlException("SQLite database '" + database + "' does not exist yet");
+ }
+ }
+ else {
+ if (creat) {
+ throw SSqlException("SQLite database '" + database + "' already exists");
+ }
}
- if ( sqlite3_open( database.c_str(), &m_pDB)!=SQLITE_OK )
- throw sPerrorException( "Could not connect to the SQLite database '" + database + "'" );
- m_dolog = 0;
+ if (sqlite3_open(database.c_str(), &m_pDB) != SQLITE_OK) {
+ throw SSqlException("Could not connect to the SQLite database '" + database + "'");
+ }
+ m_dolog = false;
m_in_transaction = false;
- sqlite3_busy_handler(m_pDB, busyHandler, 0);
+ sqlite3_busy_handler(m_pDB, busyHandler, nullptr);
- if(journalmode.length())
- execute("PRAGMA journal_mode="+journalmode);
+ if (journalmode.length() != 0) {
+ executeImpl("PRAGMA journal_mode=" + journalmode);
+ }
}
void SSQLite3::setLog(bool state)
{
- m_dolog=state;
+ m_dolog = state;
}
// Destructor.
SSQLite3::~SSQLite3()
{
- int ret;
- for(int n = 0; ; ++n) {
- if((ret =sqlite3_close( m_pDB )) != SQLITE_OK) {
- if(n || ret != SQLITE_BUSY) { // if we have SQLITE_BUSY, and a working m_Pstmt, try finalize
- cerr<<"Unable to close down sqlite connection: "<<ret<<endl;
- abort();
- }
- }
- else
+ for (int tried = 0;; ++tried) {
+ int ret = sqlite3_close(m_pDB);
+ if (ret == SQLITE_OK) {
break;
+ }
+ cerr << "SQLite3 error state while tearing down database: " << SSQLite3ErrorString(m_pDB) << endl;
+ if (tried == 0 && ret == SQLITE_BUSY) {
+ continue;
+ }
+ cerr << "Unable to close down sqlite connection: " << ret << endl;
+ abort();
}
}
-std::unique_ptr<SSqlStatement> SSQLite3::prepare(const string& query, int nparams __attribute__((unused))) {
+std::unique_ptr<SSqlStatement> SSQLite3::prepare(const string& query, int nparams __attribute__((unused)))
+{
return std::make_unique<SSQLite3Statement>(this, m_dolog, query);
}
-void SSQLite3::execute(const string& query) {
- char *errmsg;
+void SSQLite3::executeImpl(const string& query)
+{
+ char* errmsg = nullptr;
std::string errstr1;
- int rc = sqlite3_exec(m_pDB, query.c_str(), nullptr, nullptr, &errmsg);
- if (rc != SQLITE_OK) {
+ int execRet = sqlite3_exec(m_pDB, query.c_str(), nullptr, nullptr, &errmsg);
+ if (execRet != SQLITE_OK) {
errstr1 = errmsg;
sqlite3_free(errmsg);
}
- if (rc == SQLITE_BUSY) {
+ if (execRet == SQLITE_BUSY) {
if (m_in_transaction) {
throw SSqlException("Failed to execute query: " + errstr1);
- } else {
- rc = sqlite3_exec(m_pDB, query.c_str(), NULL, NULL, &errmsg);
- std::string errstr2;
- if (rc != SQLITE_OK) {
- errstr2 = errmsg;
- sqlite3_free(errmsg);
- throw SSqlException("Failed to execute query: " + errstr2);
- }
}
- } else if (rc != SQLITE_OK) {
+ execRet = sqlite3_exec(m_pDB, query.c_str(), nullptr, nullptr, &errmsg);
+ std::string errstr2;
+ if (execRet != SQLITE_OK) {
+ errstr2 = errmsg;
+ sqlite3_free(errmsg);
+ throw SSqlException("Failed to execute query: " + errstr2);
+ }
+ }
+ else if (execRet != SQLITE_OK) {
throw SSqlException("Failed to execute query: " + errstr1);
}
}
-int SSQLite3::busyHandler(void*, int)
+void SSQLite3::execute(const string& query)
+{
+ executeImpl(query);
+}
+
+int SSQLite3::busyHandler([[maybe_unused]] void* userData, [[maybe_unused]] int invocationsSoFar)
{
Utility::usleep(1000);
return 1;
}
-void SSQLite3::startTransaction() {
+void SSQLite3::startTransaction()
+{
execute("begin");
m_in_transaction = true;
}
-void SSQLite3::rollback() {
+void SSQLite3::rollback()
+{
execute("rollback");
m_in_transaction = false;
}
-void SSQLite3::commit() {
+void SSQLite3::commit()
+{
execute("commit");
m_in_transaction = false;
}
// Constructs a SSqlException object.
-SSqlException SSQLite3::sPerrorException( const std::string & reason )
+SSqlException SSQLite3::sPerrorException(const std::string& reason)
{
- return SSqlException( reason );
+ return {reason};
}
{
private:
//! Pointer to the SQLite database instance.
- sqlite3 *m_pDB{nullptr};
+ sqlite3* m_pDB{nullptr};
bool m_dolog;
bool m_in_transaction;
static int busyHandler(void*, int);
-protected:
+
+ void executeImpl(const string& query);
+
public:
//! Constructor.
- SSQLite3( const std::string & database, const std::string & journalmode, bool creat=false);
+ SSQLite3(const std::string& database, const std::string& journalmode, bool creat = false);
//! Destructor.
- ~SSQLite3();
+ ~SSQLite3() override;
std::unique_ptr<SSqlStatement> prepare(const string& query, int nparams) override;
void execute(const string& query) override;
void commit() override;
void rollback() override;
- sqlite3 *db() { return this->m_pDB; };
+ sqlite3* db() { return this->m_pDB; };
- bool inTransaction() { return m_in_transaction; };
+ [[nodiscard]] bool inTransaction() const { return m_in_transaction; };
//! Used to create an backend specific exception message.
- SSqlException sPerrorException( const std::string & reason ) override;
+ SSqlException sPerrorException(const std::string& reason) override;
};
#include <boost/utility.hpp>
#include <csignal>
#include "namespaces.hh"
+#include "noinitvector.hh"
-
-typedef int ProtocolType; //!< Supported protocol types
+using ProtocolType = int; //!< Supported protocol types
//! Representation of a Socket and many of the Berkeley functions available
class Socket : public boost::noncopyable
setCloseOnExec(d_socket);
}
- Socket(Socket&& rhs): d_buffer(std::move(rhs.d_buffer)), d_socket(rhs.d_socket)
+ Socket(Socket&& rhs) noexcept :
+ d_buffer(std::move(rhs.d_buffer)), d_socket(rhs.d_socket)
{
rhs.d_socket = -1;
}
- Socket& operator=(Socket&& rhs)
+ Socket& operator=(Socket&& rhs) noexcept
{
if (d_socket != -1) {
close(d_socket);
/** For datagram sockets, receive a datagram and learn where it came from
\param dgram Will be filled with the datagram
\param ep Will be filled with the origin of the datagram */
- void recvFrom(string &dgram, ComboAddress &ep)
+ void recvFrom(string &dgram, ComboAddress& remote)
{
- socklen_t remlen = sizeof(ep);
- ssize_t bytes;
- d_buffer.resize(s_buflen);
- if((bytes=recvfrom(d_socket, &d_buffer[0], s_buflen, 0, reinterpret_cast<sockaddr *>(&ep) , &remlen)) <0)
- throw NetworkError("After recvfrom: "+stringerror());
-
- dgram.assign(d_buffer, 0, static_cast<size_t>(bytes));
+ socklen_t remlen = sizeof(remote);
+ if (dgram.size() < s_buflen) {
+ dgram.resize(s_buflen);
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ auto bytes = recvfrom(d_socket, dgram.data(), dgram.size(), 0, reinterpret_cast<sockaddr *>(&remote) , &remlen);
+ if (bytes < 0) {
+ throw NetworkError("After recvfrom: " + stringerror());
+ }
+ dgram.resize(static_cast<size_t>(bytes));
}
- bool recvFromAsync(string &dgram)
+ bool recvFromAsync(PacketBuffer& dgram, ComboAddress& remote)
{
- struct sockaddr_in remote;
socklen_t remlen = sizeof(remote);
- ssize_t bytes;
- d_buffer.resize(s_buflen);
- if((bytes=recvfrom(d_socket, &d_buffer[0], s_buflen, 0, reinterpret_cast<sockaddr *>(&remote), &remlen))<0) {
- if(errno!=EAGAIN) {
- throw NetworkError("After async recvfrom: "+stringerror());
+ if (dgram.size() < s_buflen) {
+ dgram.resize(s_buflen);
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ auto bytes = recvfrom(d_socket, dgram.data(), dgram.size(), 0, reinterpret_cast<sockaddr *>(&remote), &remlen);
+ if (bytes < 0) {
+ if (errno != EAGAIN) {
+ throw NetworkError("After async recvfrom: " + stringerror());
}
else {
return false;
}
}
- dgram.assign(d_buffer, 0, static_cast<size_t>(bytes));
+ dgram.resize(static_cast<size_t>(bytes));
return true;
}
-
//! For datagram sockets, send a datagram to a destination
- void sendTo(const char* msg, size_t len, const ComboAddress &ep)
+ void sendTo(const char* msg, size_t len, const ComboAddress& remote)
{
- if(sendto(d_socket, msg, len, 0, reinterpret_cast<const sockaddr *>(&ep), ep.getSocklen())<0)
- throw NetworkError("After sendto: "+stringerror());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ if (sendto(d_socket, msg, len, 0, reinterpret_cast<const sockaddr *>(&remote), remote.getSocklen()) < 0) {
+ throw NetworkError("After sendto: " + stringerror());
+ }
}
//! For connected datagram sockets, send a datagram
void send(const std::string& msg)
{
- if(::send(d_socket, msg.c_str(), msg.size(), 0)<0)
+ if (::send(d_socket, msg.data(), msg.size(), 0) < 0) {
throw NetworkError("After send: "+stringerror());
+ }
}
/** For datagram sockets, send a datagram to a destination
\param dgram The datagram
- \param ep The intended destination of the datagram */
- void sendTo(const string &dgram, const ComboAddress &ep)
+ \param remote The intended destination of the datagram */
+ void sendTo(const string& dgram, const ComboAddress& remote)
{
- sendTo(dgram.c_str(), dgram.length(), ep);
+ sendTo(dgram.data(), dgram.length(), remote);
}
return static_cast<size_t>(res);
}
+ /** Read a bock of data from the socket to a block of memory,
+ * waiting at most 'timeout' seconds for the data to become
+ * available. Be aware that this does _NOT_ handle partial reads
+ * for you.
+ */
ssize_t readWithTimeout(char* buffer, size_t n, int timeout)
{
int err = waitForRWData(d_socket, true, timeout, 0);
-
+#include <cstdint>
#include <fstream>
#include <iostream>
#include <stdexcept>
}
private:
- typename std::aligned_storage<sizeof(base_t), CPU_LEVEL1_DCACHE_LINESIZE>::type counter;
+ typename std::aligned_storage_t<sizeof(base_t), CPU_LEVEL1_DCACHE_LINESIZE> counter;
};
typedef stat_t_trait<uint64_t> stat_t;
return d_stats[key].get();
}
-StatBag::~StatBag()
-{
-}
+StatBag::~StatBag() = default;
template<typename T, typename Comp>
StatRing<T,Comp>::StatRing(unsigned int size)
vector<pair<string, unsigned int> > ret;
if (d_comboRings.count(name)) {
- typedef pair<SComboAddress, unsigned int> stor_t;
- vector<stor_t> raw =d_comboRings[name].lock()->get();
- for(const stor_t& stor : raw) {
- ret.emplace_back(stor.first.ca.toString(), stor.second);
+ for (const auto& [addr, num] : d_comboRings[name].lock()->get()) {
+ ret.emplace_back(addr.ca.toString(), num);
}
} else if (d_dnsnameqtyperings.count(name)) {
- auto raw = d_dnsnameqtyperings[name].lock()->get();
- for (auto const &e : raw) {
- ret.emplace_back(std::get<0>(e.first).toLogString() + "/" + std::get<1>(e.first).toString(), e.second);
+ for (auto const& [d, t] : d_dnsnameqtyperings[name].lock()->get()) {
+ ret.emplace_back(std::get<0>(d).toLogString() + "/" + std::get<1>(d).toString(), t);
}
}
return ret;
StatRing& operator=(const StatRing&) = delete;
StatRing& operator=(StatRing&&) = delete;
StatRing(StatRing&&) = default;
-
+
void account(const T &item);
uint64_t getSize() const;
uint64_t getEntriesCount() const;
- void resize(unsigned int newsize);
+ void resize(unsigned int newsize);
void reset();
void setHelp(const string &str);
string getHelp() const;
vector<pair<T, unsigned int> > get() const;
private:
- static bool popisort(const pair<T,int> &a, const pair<T,int> &b)
+ static bool popisort(const pair<T,int> &a, const pair<T,int> &b)
{
return (a.second > b.second);
}
if (it == d_dnsnameqtyperings.end()) {
throw runtime_error("Attempting to account to nonexistent dnsname+qtype ring '"+std::string(name)+"'");
}
- it->second.lock()->account(std::make_tuple(dnsname, qtype));
+ it->second.lock()->account(std::tuple(dnsname, qtype));
}
}
return newstat;
}
-
-void StatNode::visit(visitor_t visitor, Stat &newstat, unsigned int depth) const
+void StatNode::visit(const visitor_t& visitor, Stat& newstat, unsigned int depth) const
{
Stat childstat(s);
newstat += childstat;
}
-
-void StatNode::submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, boost::optional<const ComboAddress&> remote)
+void StatNode::submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, const boost::optional<const ComboAddress&>& remote)
{
// cerr<<"FIRST submit called on '"<<domain<<"'"<<endl;
std::vector<string> tmp = domain.getRawLabels();
www.powerdns.com.
*/
-void StatNode::submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, boost::optional<const ComboAddress&> remote, unsigned int count, bool hit)
+void StatNode::submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, const boost::optional<const ComboAddress&>& remote, unsigned int count, bool hit)
{
// cerr<<"Submit called for domain='"<<domain<<"': ";
// for(const std::string& n : labels)
struct Stat
{
- Stat()
- {
- }
-
+ Stat() {};
uint64_t queries{0};
uint64_t noerrors{0};
uint64_t nxdomains{0};
std::string fullname;
uint8_t labelsCount{0};
- void submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, boost::optional<const ComboAddress&> remote);
+ void submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, const boost::optional<const ComboAddress&>& remote);
Stat print(unsigned int depth=0, Stat newstat=Stat(), bool silent=false) const;
- void visit(visitor_t visitor, Stat& newstat, unsigned int depth=0) const;
+ void visit(const visitor_t& visitor, Stat& newstat, unsigned int depth = 0) const;
bool empty() const
{
return children.empty() && s.remotes.empty();
children_t children;
private:
- void submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, boost::optional<const ComboAddress&> remote, unsigned int count, bool hit);
+ void submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, const boost::optional<const ComboAddress&>& remote, unsigned int count, bool hit);
};
#include "namespaces.hh"
#include "statbag.hh"
#include "stubresolver.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
#define LOCAL_RESOLV_CONF_PATH "/etc/resolv.conf"
// don't stat() for local resolv.conf more than once every INTERVAL secs.
bool resolversDefined()
{
if (s_resolversForStub.read_lock()->empty()) {
- g_log<<Logger::Warning<<logPrefix<<"No upstream resolvers configured, stub resolving (including secpoll and ALIAS) impossible."<<endl;
+ g_log << Logger::Warning << logPrefix << "No upstream resolvers configured, stub resolving (including secpoll and ALIAS) impossible." << endl;
return false;
}
return true;
*/
static void parseLocalResolvConf_locked(vector<ComboAddress>& resolversForStub, const time_t& now)
{
- struct stat st;
+ struct stat statResult
+ {
+ };
s_localResolvConfLastCheck = now;
- if (stat(LOCAL_RESOLV_CONF_PATH, &st) != -1) {
- if (st.st_mtime != s_localResolvConfMtime) {
+ if (stat(LOCAL_RESOLV_CONF_PATH, &statResult) != -1) {
+ if (statResult.st_mtime != s_localResolvConfMtime) {
std::vector<ComboAddress> resolvers = getResolvers(LOCAL_RESOLV_CONF_PATH);
- s_localResolvConfMtime = st.st_mtime;
+ s_localResolvConfMtime = statResult.st_mtime;
if (resolvers.empty()) {
return;
static void parseLocalResolvConf()
{
const time_t now = time(nullptr);
- if ((s_localResolvConfLastCheck + LOCAL_RESOLV_CONF_MAX_CHECK_INTERVAL) > now)
- return ;
+ if ((s_localResolvConfLastCheck + LOCAL_RESOLV_CONF_MAX_CHECK_INTERVAL) > now) {
+ return;
+ }
parseLocalResolvConf_locked(*(s_resolversForStub.write_lock()), now);
}
-
/*
* Fill the s_resolversForStub vector with addresses for the upstream resolvers.
* First, parse the `resolver` configuration option for IP addresses to use.
*/
void stubParseResolveConf()
{
- if(::arg().mustDo("resolver")) {
+ if (::arg().mustDo("resolver")) {
auto resolversForStub = s_resolversForStub.write_lock();
vector<string> parts;
stringtok(parts, ::arg()["resolver"], " ,\t");
- for (const auto& addr : parts)
+ for (const auto& addr : parts) {
resolversForStub->push_back(ComboAddress(addr, 53));
+ }
}
if (s_resolversForStub.read_lock()->empty()) {
}
// s_resolversForStub contains the ComboAddresses that are used to resolve the
-int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret)
+int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret, const EDNSSubnetOpts* d_eso)
{
// ensure resolver gets always configured
if (!s_stubResolvConfigured) {
}
// only check if resolvers come from local resolv.conf in the first place
if (s_localResolvConfMtime != 0) {
- parseLocalResolvConf();
+ parseLocalResolvConf();
}
- if (!resolversDefined())
+ if (!resolversDefined()) {
return RCode::ServFail;
+ }
auto resolversForStub = s_resolversForStub.read_lock();
vector<uint8_t> packet;
- DNSPacketWriter pw(packet, qname, qtype);
- pw.getHeader()->id=dns_random_uint16();
- pw.getHeader()->rd=1;
+ DNSPacketWriter packetWriter(packet, qname, qtype);
+ packetWriter.getHeader()->id = dns_random_uint16();
+ packetWriter.getHeader()->rd = 1;
+
+ if (d_eso != nullptr) {
+ // pass along EDNS subnet from client if given - issue #5469
+ string origECSOptionStr = makeEDNSSubnetOptsString(*d_eso);
+ DNSPacketWriter::optvect_t opts;
+ opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+ packetWriter.addOpt(512, 0, 0, opts);
+ packetWriter.commit();
+ }
string queryNameType = qname.toString() + "|" + QType(qtype).toString();
- string msg ="Doing stub resolving for '" + queryNameType + "', using resolvers: ";
+ string msg = "Doing stub resolving for '" + queryNameType + "', using resolvers: ";
for (const auto& server : *resolversForStub) {
msg += server.toString() + ", ";
}
- g_log<<Logger::Debug<<logPrefix<<msg.substr(0, msg.length() - 2)<<endl;
+ g_log << Logger::Debug << logPrefix << msg.substr(0, msg.length() - 2) << endl;
- for(const ComboAddress& dest : *resolversForStub) {
+ for (const ComboAddress& dest : *resolversForStub) {
Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
sock.setNonBlocking();
sock.connect(dest);
try {
retry:
sock.read(reply); // this calls recv
- if(reply.size() > sizeof(struct dnsheader)) {
- struct dnsheader d;
- memcpy(&d, reply.c_str(), sizeof(d));
- if(d.id != pw.getHeader()->id)
+ if (reply.size() > sizeof(struct dnsheader)) {
+ struct dnsheader dHeader
+ {
+ };
+ memcpy(&dHeader, reply.c_str(), sizeof(dHeader));
+ if (dHeader.id != packetWriter.getHeader()->id) {
goto retry;
+ }
}
}
- catch(...) {
+ catch (...) {
continue;
}
MOADNSParser mdp(false, reply);
- if(mdp.d_header.rcode == RCode::ServFail)
+ if (mdp.d_header.rcode == RCode::ServFail) {
continue;
+ }
- for(const auto & answer : mdp.d_answers) {
- if(answer.first.d_place == 1 && answer.first.d_type==qtype) {
+ for (const auto& answer : mdp.d_answers) {
+ if (answer.first.d_place == 1 && answer.first.d_type == qtype) {
DNSZoneRecord zrr;
zrr.dr = answer.first;
- zrr.auth=true;
+ zrr.auth = true;
ret.push_back(zrr);
}
}
- g_log<<Logger::Debug<<logPrefix<<"Question for '"<<queryNameType<<"' got answered by "<<dest.toString()<<endl;
+ g_log << Logger::Debug << logPrefix << "Question for '" << queryNameType << "' got answered by " << dest.toString() << endl;
return mdp.d_header.rcode;
}
return RCode::ServFail;
}
-int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSRecord>& ret) {
+int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSRecord>& ret, const EDNSSubnetOpts* d_eso)
+{
vector<DNSZoneRecord> ret2;
- int res = stubDoResolve(qname, qtype, ret2);
- for (const auto &r : ret2) {
- ret.push_back(r.dr);
+ int res = stubDoResolve(qname, qtype, ret2, d_eso);
+ for (const auto& record : ret2) {
+ ret.push_back(record.dr);
}
return res;
}
#pragma once
#include "namespaces.hh"
#include "dnsparser.hh"
+#include "ednssubnet.hh"
void stubParseResolveConf();
bool resolversDefined();
-int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret);
-int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSRecord>& ret);
+int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret, const EDNSSubnetOpts* d_eso = nullptr);
+int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSRecord>& ret, const EDNSSubnetOpts* d_eso = nullptr);
class TLocalCounters
{
public:
- static const suseconds_t defaultSnapUpdatePeriodus = 100000;
+ static constexpr suseconds_t defaultSnapUpdatePeriodus = 100000;
TLocalCounters(GlobalCounters<Counters>& collector, timeval interval = timeval{0, defaultSnapUpdatePeriodus}) :
d_collector(collector), d_interval(interval)
{
}
template <typename Enum>
+ // coverity[auto_causes_copy]
auto snapAt(Enum index)
{
return d_snapshot.lock()->at(index);
#include <sodium.h>
#endif /* HAVE_LIBSODIUM */
-#ifdef HAVE_DNS_OVER_TLS
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
#ifdef HAVE_LIBSSL
#include <openssl/conf.h>
OpenSSLTLSTicketKeysRing d_ticketKeys;
std::map<int, std::string> d_ocspResponses;
std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> d_tlsCtx{nullptr, SSL_CTX_free};
- std::unique_ptr<FILE, int(*)(FILE*)> d_keyLogFile{nullptr, fclose};
+ pdns::UniqueFilePtr d_keyLogFile{nullptr};
};
class OpenSSLSession : public TLSSession
{
}
- virtual ~OpenSSLSession()
- {
- }
-
std::unique_ptr<SSL_SESSION, void(*)(SSL_SESSION*)> getNative()
{
return std::move(d_sess);
{
public:
/* server side connection */
- OpenSSLTLSConnection(int socket, const struct timeval& timeout, std::shared_ptr<OpenSSLFrontendContext> feContext): d_feContext(feContext), d_conn(std::unique_ptr<SSL, void(*)(SSL*)>(SSL_new(d_feContext->d_tlsCtx.get()), SSL_free)), d_timeout(timeout)
+ OpenSSLTLSConnection(int socket, const struct timeval& timeout, std::shared_ptr<OpenSSLFrontendContext> feContext): d_feContext(std::move(feContext)), d_conn(std::unique_ptr<SSL, void(*)(SSL*)>(SSL_new(d_feContext->d_tlsCtx.get()), SSL_free)), d_timeout(timeout)
{
d_socket = socket;
- if (!s_initTLSConnIndex.test_and_set()) {
- /* not initialized yet */
- s_tlsConnIndex = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
- if (s_tlsConnIndex == -1) {
- throw std::runtime_error("Error getting an index for TLS connection data");
- }
- }
-
if (!d_conn) {
vinfolog("Error creating TLS object");
if (g_verbose) {
throw std::runtime_error("Error assigning socket");
}
- SSL_set_ex_data(d_conn.get(), s_tlsConnIndex, this);
+ SSL_set_ex_data(d_conn.get(), getConnectionIndex(), this);
}
/* client-side connection */
{
d_socket = socket;
- if (!s_initTLSConnIndex.test_and_set()) {
- /* not initialized yet */
- s_tlsConnIndex = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
- if (s_tlsConnIndex == -1) {
- throw std::runtime_error("Error getting an index for TLS connection data");
- }
- }
-
if (!d_conn) {
vinfolog("Error creating TLS object");
if (g_verbose) {
#endif
}
else {
-#if (OPENSSL_VERSION_NUMBER >= 0x1010000fL) && HAVE_SSL_SET_HOSTFLAGS // grrr libressl
+#if (OPENSSL_VERSION_NUMBER >= 0x1010000fL) && defined(HAVE_SSL_SET_HOSTFLAGS) // grrr libressl
SSL_set_hostflags(d_conn.get(), X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
if (SSL_set1_host(d_conn.get(), d_hostname.c_str()) != 1) {
throw std::runtime_error("Error setting TLS hostname for certificate validation");
#endif
}
- SSL_set_ex_data(d_conn.get(), s_tlsConnIndex, this);
+ SSL_set_ex_data(d_conn.get(), getConnectionIndex(), this);
}
std::vector<int> getAsyncFDs() override
return got;
}
- bool hasBufferedData() const override
- {
- if (d_conn) {
- return SSL_pending(d_conn.get()) > 0;
- }
-
- return false;
- }
-
bool isUsable() const override
{
if (!d_conn) {
d_ktls = true;
}
- static int s_tlsConnIndex;
+ static void generateConnectionIndexIfNeeded()
+ {
+ auto init = s_initTLSConnIndex.lock();
+ if (*init == true) {
+ return;
+ }
-private:
- static std::atomic_flag s_initTLSConnIndex;
+ /* not initialized yet */
+ s_tlsConnIndex = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+ if (s_tlsConnIndex == -1) {
+ throw std::runtime_error("Error getting an index for TLS connection data");
+ }
+
+ *init = true;
+ }
+
+ static int getConnectionIndex()
+ {
+ return s_tlsConnIndex;
+ }
+private:
+ static LockGuarded<bool> s_initTLSConnIndex;
+ static int s_tlsConnIndex;
std::vector<std::unique_ptr<TLSSession>> d_tlsSessions;
/* server context */
std::shared_ptr<OpenSSLFrontendContext> d_feContext;
bool d_ktls{false};
};
-std::atomic_flag OpenSSLTLSConnection::s_initTLSConnIndex = ATOMIC_FLAG_INIT;
-int OpenSSLTLSConnection::s_tlsConnIndex = -1;
+LockGuarded<bool> OpenSSLTLSConnection::s_initTLSConnIndex{false};
+int OpenSSLTLSConnection::s_tlsConnIndex{-1};
class OpenSSLTLSIOCtx: public TLSCtx
{
/* server side context */
OpenSSLTLSIOCtx(TLSFrontend& fe): d_feContext(std::make_shared<OpenSSLFrontendContext>(fe.d_addr, fe.d_tlsConfig))
{
+ OpenSSLTLSConnection::generateConnectionIndexIfNeeded();
+
d_ticketsKeyRotationDelay = fe.d_tlsConfig.d_ticketsKeyRotationDelay;
if (fe.d_tlsConfig.d_enableTickets && fe.d_tlsConfig.d_numberOfTicketsKeys > 0) {
}
#endif /* DISABLE_OCSP_STAPLING */
+ if (fe.d_tlsConfig.d_readAhead) {
+ SSL_CTX_set_read_ahead(d_feContext->d_tlsCtx.get(), 1);
+ }
+
libssl_set_error_counters_callback(d_feContext->d_tlsCtx, &fe.d_tlsCounters);
if (!fe.d_tlsConfig.d_keyLogFile.empty()) {
registerOpenSSLUser();
+ OpenSSLTLSConnection::generateConnectionIndexIfNeeded();
+
#ifdef HAVE_TLS_CLIENT_METHOD
d_tlsCtx = std::shared_ptr<SSL_CTX>(SSL_CTX_new(TLS_client_method()), SSL_CTX_free);
#else
int ret = libssl_ticket_key_callback(s, ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc);
if (enc == 0) {
if (ret == 0 || ret == 2) {
- auto* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(s, OpenSSLTLSConnection::s_tlsConnIndex));
+ auto* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(s, OpenSSLTLSConnection::getConnectionIndex()));
if (conn != nullptr) {
if (ret == 0) {
conn->setUnknownTicketKey();
static int newTicketFromServerCb(SSL* ssl, SSL_SESSION* session)
{
- OpenSSLTLSConnection* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(ssl, OpenSSLTLSConnection::s_tlsConnIndex));
+ OpenSSLTLSConnection* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(ssl, OpenSSLTLSConnection::getConnectionIndex()));
if (session == nullptr || conn == nullptr) {
return 0;
}
}
}
- void loadTicketsKeys(const std::string& keyFile) override final
+ void loadTicketsKeys(const std::string& keyFile) final
{
d_feContext->d_ticketKeys.loadTicketsKeys(keyFile);
if (!arg) {
return SSL_TLSEXT_ERR_ALERT_WARNING;
}
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): OpenSSL's API
OpenSSLTLSIOCtx* obj = reinterpret_cast<OpenSSLTLSIOCtx*>(arg);
- size_t pos = 0;
- while (pos < inlen) {
- size_t protoLen = in[pos];
- pos++;
- if (protoLen > (inlen - pos)) {
- /* something is very wrong */
- return SSL_TLSEXT_ERR_ALERT_WARNING;
- }
+ const pdns::views::UnsignedCharView inView(in, inlen);
+ // Server preference algorithm as per RFC 7301 section 3.2
+ for (const auto& tentative : obj->d_alpnProtos) {
+ size_t pos = 0;
+ while (pos < inView.size()) {
+ size_t protoLen = inView.at(pos);
+ pos++;
+ if (protoLen > (inlen - pos)) {
+ /* something is very wrong */
+ return SSL_TLSEXT_ERR_ALERT_WARNING;
+ }
- for (const auto& tentative : obj->d_alpnProtos) {
- if (tentative.size() == protoLen && memcmp(in + pos, tentative.data(), tentative.size()) == 0) {
- *out = in + pos;
+ if (tentative.size() == protoLen && memcmp(&inView.at(pos), tentative.data(), tentative.size()) == 0) {
+ *out = &inView.at(pos);
*outlen = protoLen;
return SSL_TLSEXT_ERR_OK;
}
+ pos += protoLen;
}
- pos += protoLen;
}
return SSL_TLSEXT_ERR_NOACK;
sess.size = 0;
}
- virtual ~GnuTLSSession()
+ ~GnuTLSSession() override
{
if (d_sess.data != nullptr && d_sess.size > 0) {
safe_memory_release(d_sess.data, d_sess.size);
gnutls_handshake_set_timeout(d_conn.get(), timeout.tv_sec * 1000 + timeout.tv_usec / 1000);
gnutls_record_set_timeout(d_conn.get(), timeout.tv_sec * 1000 + timeout.tv_usec / 1000);
-#if HAVE_GNUTLS_SESSION_SET_VERIFY_CERT
+#ifdef HAVE_GNUTLS_SESSION_SET_VERIFY_CERT
if (validateCerts && !d_host.empty()) {
gnutls_session_set_verify_cert(d_conn.get(), d_host.c_str(), GNUTLS_VERIFY_ALLOW_UNSORTED_CHAIN);
rc = gnutls_server_name_set(d_conn.get(), GNUTLS_NAME_DNS, d_host.c_str(), d_host.size());
/* The callback prototype changed in 3.4.0. */
#if GNUTLS_VERSION_NUMBER >= 0x030400
- static int newTicketFromServerCb(gnutls_session_t session, unsigned int htype, unsigned post, unsigned int incoming, const gnutls_datum_t* msg)
+ static int newTicketFromServerCb(gnutls_session_t session, unsigned int htype, unsigned post, unsigned int /* incoming */, const gnutls_datum_t* /* msg */)
#else
- static int newTicketFromServerCb(gnutls_session_t session, unsigned int htype, unsigned post, unsigned int incoming)
+ static int newTicketFromServerCb(gnutls_session_t session, unsigned int htype, unsigned post, unsigned int /* incoming */)
#endif /* GNUTLS_VERSION_NUMBER >= 0x030400 */
{
if (htype != GNUTLS_HANDSHAKE_NEW_SESSION_TICKET || post != GNUTLS_HOOK_POST || session == nullptr) {
return 0;
}
- IOState tryConnect(bool fastOpen, const ComboAddress& remote) override
+ IOState tryConnect(bool fastOpen, [[maybe_unused]] const ComboAddress& remote) override
{
int ret = 0;
else if (gnutls_error_is_fatal(ret) || ret == GNUTLS_E_WARNING_ALERT_RECEIVED) {
if (d_client) {
std::string error;
-#if HAVE_GNUTLS_SESSION_GET_VERIFY_CERT_STATUS
+#ifdef HAVE_GNUTLS_SESSION_GET_VERIFY_CERT_STATUS
if (ret == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) {
gnutls_datum_t out;
if (gnutls_certificate_verification_status_print(gnutls_session_get_verify_cert_status(d_conn.get()), gnutls_certificate_type_get(d_conn.get()), &out, 0) == 0) {
else if (res == GNUTLS_E_AGAIN) {
return IOState::NeedWrite;
}
- warnlog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
+ vinfolog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
}
}
while (pos < toWrite);
else if (res == GNUTLS_E_AGAIN) {
return IOState::NeedRead;
}
- warnlog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
+ vinfolog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
}
}
while (pos < toRead);
return got;
}
- bool hasBufferedData() const override
- {
- if (d_conn) {
- return gnutls_record_check_pending(d_conn.get()) > 0;
- }
-
- return false;
- }
-
bool isUsable() const override
{
if (!d_conn) {
}
}
- virtual ~GnuTLSIOCtx() override
+ ~GnuTLSIOCtx() override
{
d_creds.reset();
auto newKey = std::make_shared<GnuTLSTicketsKey>();
{
- *(d_ticketsKey.write_lock()) = newKey;
+ *(d_ticketsKey.write_lock()) = std::move(newKey);
}
if (d_ticketsKeyRotationDelay > 0) {
}
}
- void loadTicketsKeys(const std::string& file) override final
+ void loadTicketsKeys(const std::string& file) final
{
if (!d_enableTickets) {
return;
auto newKey = std::make_shared<GnuTLSTicketsKey>(file);
{
- *(d_ticketsKey.write_lock()) = newKey;
+ *(d_ticketsKey.write_lock()) = std::move(newKey);
}
if (d_ticketsKeyRotationDelay > 0) {
#endif /* HAVE_GNUTLS */
-#endif /* HAVE_DNS_OVER_TLS */
+#endif /* HAVE_DNS_OVER_TLS || HAVE_DNS_OVER_HTTPS */
bool setupDoTProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx)
{
return true;
}
+bool setupDoHProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx)
+{
+ if (ctx == nullptr) {
+ return false;
+ }
+ /* This code is only called for incoming/server TLS contexts (not outgoing/client),
+ and h2o sets it own ALPN values.
+ We want to set the ALPN for DoH:
+ - HTTP/1.1 so that the OpenSSL callback ALPN accepts it, letting us later return a static response
+ - HTTP/2
+ */
+ const std::vector<std::vector<uint8_t>> dohAlpns{{'h', '2'},{'h', 't', 't', 'p', '/', '1', '.', '1'}};
+ ctx->setALPNProtos(dohAlpns);
+
+ return true;
+}
+
bool TLSFrontend::setupTLS()
{
-#ifdef HAVE_DNS_OVER_TLS
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
std::shared_ptr<TLSCtx> newCtx{nullptr};
/* get the "best" available provider */
- if (!d_provider.empty()) {
-#ifdef HAVE_GNUTLS
- if (d_provider == "gnutls") {
- newCtx = std::make_shared<GnuTLSIOCtx>(*this);
- setupDoTProtocolNegotiation(newCtx);
- std::atomic_store_explicit(&d_ctx, newCtx, std::memory_order_release);
- return true;
- }
-#endif /* HAVE_GNUTLS */
-#ifdef HAVE_LIBSSL
- if (d_provider == "openssl") {
- newCtx = std::make_shared<OpenSSLTLSIOCtx>(*this);
- setupDoTProtocolNegotiation(newCtx);
- std::atomic_store_explicit(&d_ctx, newCtx, std::memory_order_release);
- return true;
- }
-#endif /* HAVE_LIBSSL */
+#if defined(HAVE_GNUTLS)
+ if (d_provider == "gnutls") {
+ newCtx = std::make_shared<GnuTLSIOCtx>(*this);
}
-#ifdef HAVE_LIBSSL
- newCtx = std::make_shared<OpenSSLTLSIOCtx>(*this);
-#else /* HAVE_LIBSSL */
-#ifdef HAVE_GNUTLS
- newCtx = std::make_shared<GnuTLSIOCtx>(*this);
#endif /* HAVE_GNUTLS */
+#if defined(HAVE_LIBSSL)
+ if (d_provider == "openssl") {
+ newCtx = std::make_shared<OpenSSLTLSIOCtx>(*this);
+ }
#endif /* HAVE_LIBSSL */
- setupDoTProtocolNegotiation(newCtx);
- std::atomic_store_explicit(&d_ctx, newCtx, std::memory_order_release);
-#endif /* HAVE_DNS_OVER_TLS */
+ if (!newCtx) {
+#if defined(HAVE_LIBSSL)
+ newCtx = std::make_shared<OpenSSLTLSIOCtx>(*this);
+#elif defined(HAVE_GNUTLS)
+ newCtx = std::make_shared<GnuTLSIOCtx>(*this);
+#else
+#error "TLS support needed but neither libssl nor GnuTLS were selected"
+#endif
+ }
+
+ if (d_alpn == ALPN::DoT) {
+ setupDoTProtocolNegotiation(newCtx);
+ }
+ else if (d_alpn == ALPN::DoH) {
+ setupDoHProtocolNegotiation(newCtx);
+ }
+
+ std::atomic_store_explicit(&d_ctx, std::move(newCtx), std::memory_order_release);
+#endif /* HAVE_DNS_OVER_TLS || HAVE_DNS_OVER_HTTPS */
return true;
}
-std::shared_ptr<TLSCtx> getTLSContext(const TLSContextParameters& params)
+std::shared_ptr<TLSCtx> getTLSContext([[maybe_unused]] const TLSContextParameters& params)
{
#ifdef HAVE_DNS_OVER_TLS
/* get the "best" available provider */
if (!params.d_provider.empty()) {
-#ifdef HAVE_GNUTLS
+#if defined(HAVE_GNUTLS)
if (params.d_provider == "gnutls") {
return std::make_shared<GnuTLSIOCtx>(params);
}
#endif /* HAVE_GNUTLS */
-#ifdef HAVE_LIBSSL
+#if defined(HAVE_LIBSSL)
if (params.d_provider == "openssl") {
return std::make_shared<OpenSSLTLSIOCtx>(params);
}
#endif /* HAVE_LIBSSL */
}
-#ifdef HAVE_LIBSSL
+#if defined(HAVE_LIBSSL)
return std::make_shared<OpenSSLTLSIOCtx>(params);
-#else /* HAVE_LIBSSL */
-#ifdef HAVE_GNUTLS
+#elif defined(HAVE_GNUTLS)
return std::make_shared<GnuTLSIOCtx>(params);
-#endif /* HAVE_GNUTLS */
-#endif /* HAVE_LIBSSL */
+#else
+#error "DNS over TLS support needed but neither libssl nor GnuTLS were selected"
+#endif
#endif /* HAVE_DNS_OVER_TLS */
return nullptr;
class TLSSession
{
public:
- virtual ~TLSSession()
- {
- }
+ virtual ~TLSSession() = default;
};
class TLSConnection
{
public:
- virtual ~TLSConnection() { }
+ virtual ~TLSConnection() = default;
virtual void doHandshake() = 0;
virtual IOState tryConnect(bool fastOpen, const ComboAddress& remote) = 0;
virtual void connect(bool fastOpen, const ComboAddress& remote, const struct timeval& timeout) = 0;
virtual size_t write(const void* buffer, size_t bufferSize, const struct timeval& writeTimeout) = 0;
virtual IOState tryWrite(const PacketBuffer& buffer, size_t& pos, size_t toWrite) = 0;
virtual IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete=false) = 0;
- virtual bool hasBufferedData() const = 0;
virtual std::string getServerNameIndication() const = 0;
virtual std::vector<uint8_t> getNextProtocol() const = 0;
virtual LibsslTLSVersion getTLSVersion() const = 0;
{
d_rotatingTicketsKey.clear();
}
- virtual ~TLSCtx() {}
+ virtual ~TLSCtx() = default;
virtual std::unique_ptr<TLSConnection> getConnection(int socket, const struct timeval& timeout, time_t now) = 0;
virtual std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout) = 0;
virtual void rotateTicketsKey(time_t now) = 0;
class TLSFrontend
{
public:
- TLSFrontend()
+ enum class ALPN : uint8_t { Unset, DoT, DoH };
+
+ TLSFrontend(ALPN alpn): d_alpn(alpn)
{
}
TLSErrorCounters d_tlsCounters;
ComboAddress d_addr;
std::string d_provider;
-
+ ALPN d_alpn{ALPN::Unset};
+ /* whether the proxy protocol is inside or outside the TLS layer */
+ bool d_proxyProtocolOutsideTLS{false};
protected:
std::shared_ptr<TLSCtx> d_ctx{nullptr};
};
class TCPIOHandler
{
public:
- enum class Type : uint8_t { Client, Server };
-
- TCPIOHandler(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout, std::shared_ptr<TLSCtx> ctx): d_socket(socket)
+ TCPIOHandler(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout, const std::shared_ptr<TLSCtx>& ctx) :
+ d_socket(socket)
{
if (ctx) {
d_conn = ctx->getClientConnection(host, hostIsAddr, d_socket, timeout);
}
}
- TCPIOHandler(int socket, const struct timeval& timeout, std::shared_ptr<TLSCtx> ctx, time_t now): d_socket(socket)
+ TCPIOHandler(int socket, const struct timeval& timeout, const std::shared_ptr<TLSCtx>& ctx, time_t now) :
+ d_socket(socket)
{
if (ctx) {
d_conn = ctx->getConnection(d_socket, timeout, now);
return Done when toRead bytes have been read, needRead or needWrite if the IO operation
would block.
*/
- IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete=false)
+ IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete=false, bool bypassFilters=false)
{
if (buffer.size() < toRead || pos >= toRead) {
throw std::out_of_range("Calling tryRead() with a too small buffer (" + std::to_string(buffer.size()) + ") for a read of " + std::to_string(toRead - pos) + " bytes starting at " + std::to_string(pos));
}
- if (d_conn) {
+ if (!bypassFilters && d_conn) {
return d_conn->tryRead(buffer, pos, toRead, allowIncomplete);
}
return writen2WithTimeout(d_socket, buffer, bufferSize, writeTimeout);
}
- bool hasBufferedData() const
- {
- if (d_conn) {
- return d_conn->hasBufferedData();
- }
- return false;
- }
-
std::string getServerNameIndication() const
{
if (d_conn) {
std::shared_ptr<TLSCtx> getTLSContext(const TLSContextParameters& params);
bool setupDoTProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx);
+bool setupDoHProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx);
bytes -= ret;
if (totalTimeout) {
time_t now = time(nullptr);
- unsigned int elapsed = now - start;
- if (elapsed >= remainingTotal) {
+ const auto elapsed = now - start;
+ if (elapsed >= static_cast<time_t>(remainingTotal)) {
throw NetworkError("Timeout while reading data");
}
start = now;
- remainingTotal -= elapsed;
+ if (elapsed > 0) {
+ remainingTotal -= elapsed;
+ }
}
}
return n;
if (elapsed >= maxConnectionDuration) {
return true;
}
- remainingTime = maxConnectionDuration - elapsed;
+ if (elapsed > 0) {
+ remainingTime = static_cast<unsigned int>(maxConnectionDuration - elapsed);
+ }
}
return false;
}
}
catch(PDNSException &ae) {
s_P.lock()->reset(); // on next call, backend will be recycled
- g_log<<Logger::Error<<"TCP nameserver had error, cycling backend: "<<ae.reason<<endl;
+ g_log << Logger::Error << "TCP Connection Thread for client " << remote << " failed, cycling backend: " << ae.reason << endl;
}
catch(NetworkError &e) {
- g_log<<Logger::Info<<"TCP Connection Thread died because of network error: "<<e.what()<<endl;
+ g_log << Logger::Info << "TCP Connection Thread for client " << remote << " died because of network error: " << e.what() << endl;
}
catch(std::exception &e) {
s_P.lock()->reset(); // on next call, backend will be recycled
- g_log << Logger::Error << "TCP Connection Thread died because of STL error, cycling backend: " << e.what() << endl;
+ g_log << Logger::Error << "TCP Connection Thread for client " << remote << " died because of STL error, cycling backend: " << e.what() << endl;
}
catch( ... )
{
s_P.lock()->reset(); // on next call, backend will be recycled
- g_log << Logger::Error << "TCP Connection Thread caught unknown exception, cycling backend." << endl;
+ g_log << Logger::Error << "TCP Connection Thread for client " << remote << " caught unknown exception, cycling backend." << endl;
}
d_connectionroom_sem->post();
closesocket(fd);
}
catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error closing TCP socket: "<<e.reason<<endl;
+ g_log << Logger::Error << "Error closing TCP socket for client " << remote << ": " << e.reason << endl;
}
decrementClientCount(remote);
}
/** do the actual zone transfer. Return 0 in case of error, 1 in case of success */
-int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock)
+int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock) // NOLINT(readability-function-cognitive-complexity)
{
- string logPrefix="AXFR-out zone '"+target.toLogString()+"', client '"+q->getRemoteString()+"', ";
+ string logPrefix="AXFR-out zone '"+target.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', ";
std::unique_ptr<DNSPacket> outpacket= getFreshAXFRPacket(q);
if(q->d_dnssecOk)
}
zrr.dr.d_name.makeUsLowerCase();
if(zrr.dr.d_name.isPartOf(target)) {
- if (zrr.dr.d_type == QType::ALIAS && ::arg().mustDo("outgoing-axfr-expand-alias")) {
+ if (zrr.dr.d_type == QType::ALIAS && (::arg().mustDo("outgoing-axfr-expand-alias") || ::arg()["outgoing-axfr-expand-alias"] == "ignore-errors")) {
vector<DNSZoneRecord> ips;
int ret1 = stubDoResolve(getRR<ALIASRecordContent>(zrr.dr)->getContent(), QType::A, ips);
int ret2 = stubDoResolve(getRR<ALIASRecordContent>(zrr.dr)->getContent(), QType::AAAA, ips);
- if(ret1 != RCode::NoError || ret2 != RCode::NoError) {
- g_log<<Logger::Warning<<logPrefix<<"error resolving for ALIAS "<<zrr.dr.getContent()->getZoneRepresentation()<<", aborting AXFR"<<endl;
- outpacket->setRcode(RCode::ServFail);
- sendPacket(outpacket,outsock);
- return 0;
+ if (ret1 != RCode::NoError || ret2 != RCode::NoError) {
+ if (::arg()["outgoing-axfr-expand-alias"] == "ignore-errors") {
+ if (ret1 != RCode::NoError) {
+ g_log << Logger::Error << logPrefix << zrr.dr.d_name.toLogString() << ": error resolving A record for ALIAS target " << zrr.dr.getContent()->getZoneRepresentation() << ", continuing AXFR" << endl;
+ }
+ if (ret2 != RCode::NoError) {
+ g_log << Logger::Error << logPrefix << zrr.dr.d_name.toLogString() << ": error resolving AAAA record for ALIAS target " << zrr.dr.getContent()->getZoneRepresentation() << ", continuing AXFR" << endl;
+ }
+ }
+ else {
+ g_log << Logger::Warning << logPrefix << zrr.dr.d_name.toLogString() << ": error resolving for ALIAS " << zrr.dr.getContent()->getZoneRepresentation() << ", aborting AXFR" << endl;
+ outpacket->setRcode(RCode::ServFail);
+ sendPacket(outpacket, outsock);
+ return 0;
+ }
}
for (auto& ip: ips) {
zrr.dr.d_type = ip.dr.d_type;
typedef map<DNSName, NSECXEntry, CanonDNSNameCompare> nsecxrepo_t;
nsecxrepo_t nsecxrepo;
- ChunkedSigningPipe csp(target, (securedZone && !presignedZone), ::arg().asNum("signing-threads", 1));
+ ChunkedSigningPipe csp(target, (securedZone && !presignedZone), ::arg().asNum("signing-threads", 1), ::arg().mustDo("workaround-11804") ? 1 : 100);
DNSName keyname;
unsigned int udiff;
int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
{
- string logPrefix="IXFR-out zone '"+q->qdomain.toLogString()+"', client '"+q->getRemoteString()+"', ";
+ string logPrefix="IXFR-out zone '"+q->qdomain.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', ";
std::unique_ptr<DNSPacket> outpacket=getFreshAXFRPacket(q);
if(q->d_dnssecOk)
return doAXFR(q->qdomain, q, outsock);
}
-TCPNameserver::~TCPNameserver()
-{
-}
-
+TCPNameserver::~TCPNameserver() = default;
TCPNameserver::TCPNameserver()
{
d_maxTransactionsPerConn = ::arg().asNum("max-tcp-transactions-per-conn");
static bool canDoAXFR(std::unique_ptr<DNSPacket>& q, bool isAXFR, std::unique_ptr<PacketHandler>& packetHandler);
static void doConnection(int fd);
static void decrementClientCount(const ComboAddress& remote);
- void thread(void);
+ void thread();
static LockGuarded<std::map<ComboAddress,size_t,ComboAddress::addressOnlyLessThan>> s_clientsCount;
static LockGuarded<std::unique_ptr<PacketHandler>> s_P;
static std::unique_ptr<Semaphore> d_connectionroom_sem;
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
-
/*
PowerDNS Versatile Database Driven Nameserver
Copyright (C) 2021 PowerDNS.COM BV
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
vector<BindDomainInfo> domains = BP.getDomains();
BOOST_CHECK_EQUAL(domains.size(), 11U);
-#define checkzone(i, dname, fname, ztype, nmasters) \
- { \
- BOOST_CHECK(domains[i].name == DNSName(dname)); \
- BOOST_CHECK_EQUAL(domains[i].filename, fname); \
- BOOST_CHECK_EQUAL(domains[i].type, #ztype); \
- BOOST_CHECK_EQUAL(domains[i].masters.size(), nmasters); \
+#define checkzone(i, dname, fname, ztype, nprimaries) \
+ { \
+ BOOST_CHECK(domains[i].name == DNSName(dname)); \
+ BOOST_CHECK_EQUAL(domains[i].filename, fname); \
+ BOOST_CHECK_EQUAL(domains[i].type, #ztype); \
+ BOOST_CHECK_EQUAL(domains[i].primaries.size(), nprimaries); \
}
checkzone(0, "example.com", "./zones/example.com", master, 0U);
checkzone(1, "test.com", "./zones/test.com", slave, 1U);
- BOOST_CHECK_EQUAL(domains[1].masters[0].toString(), ComboAddress("1.2.3.4", 5678).toString());
+ BOOST_CHECK_EQUAL(domains[1].primaries[0].toString(), ComboAddress("1.2.3.4", 5678).toString());
checkzone(2, "test.dyndns", "./zones/test.dyndns", garblewarble, 0U);
- checkzone(3, "wtest.com", "./zones/wtest.com", master, 0U);
- checkzone(4, "nztest.com", "./zones/nztest.com", master, 0U);
- checkzone(5, "dnssec-parent.com", "./zones/dnssec-parent.com", master, 0U);
- checkzone(6, "delegated.dnssec-parent.com", "./zones/delegated.dnssec-parent.com", master, 0U);
- checkzone(7, "secure-delegated.dnssec-parent.com", "./zones/secure-delegated.dnssec-parent.com", master, 0U);
- checkzone(8, "minimal.com", "./zones/minimal.com", master, 0U);
- checkzone(9, "tsig.com", "./zones/tsig.com", master, 0U);
- checkzone(10, "stest.com", "./zones/stest.com", master, 0U);
+ checkzone(3, "wtest.com", "./zones/wtest.com", primary, 0U);
+ checkzone(4, "nztest.com", "./zones/nztest.com", secondary, 1U);
+ BOOST_CHECK_EQUAL(domains[1].primaries[0].toString(), ComboAddress("1.2.3.4", 5678).toString());
+ checkzone(5, "dnssec-parent.com", "./zones/dnssec-parent.com", primary, 0U);
+ checkzone(6, "delegated.dnssec-parent.com", "./zones/delegated.dnssec-parent.com", primary, 0U);
+ checkzone(7, "secure-delegated.dnssec-parent.com", "./zones/secure-delegated.dnssec-parent.com", primary, 0U);
+ checkzone(8, "minimal.com", "./zones/minimal.com", primary, 0U);
+ checkzone(9, "tsig.com", "./zones/tsig.com", primary, 0U);
+ checkzone(10, "stest.com", "./zones/stest.com", primary, 0U);
}
BOOST_AUTO_TEST_SUITE_END()
--- /dev/null
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "channel.hh"
+
+struct MyObject
+{
+ uint64_t a{0};
+};
+
+BOOST_AUTO_TEST_SUITE(test_channel)
+
+BOOST_AUTO_TEST_CASE(test_object_queue)
+{
+ auto [sender, receiver] = pdns::channel::createObjectQueue<MyObject>();
+
+ BOOST_CHECK(receiver.getDescriptor() != -1);
+ BOOST_CHECK_EQUAL(receiver.isClosed(), false);
+
+ auto got = receiver.receive();
+ BOOST_CHECK(!got);
+
+ auto obj = std::make_unique<MyObject>();
+ obj->a = 42U;
+ BOOST_CHECK_EQUAL(sender.send(std::move(obj)), true);
+ BOOST_CHECK(!obj);
+ got = receiver.receive();
+ BOOST_CHECK(got != std::nullopt && *got);
+ BOOST_CHECK_EQUAL((*got)->a, 42U);
+}
+
+BOOST_AUTO_TEST_CASE(test_object_queue_full)
+{
+ auto [sender, receiver] = pdns::channel::createObjectQueue<MyObject>();
+
+ {
+ auto got = receiver.receive();
+ BOOST_CHECK(!got);
+ }
+
+ /* add objects to the queue until it becomes full */
+ bool blocked = false;
+ size_t queued = 0;
+ while (!blocked) {
+ auto obj = std::make_unique<MyObject>();
+ obj->a = 42U;
+ blocked = !sender.send(std::move(obj));
+ if (blocked) {
+ BOOST_CHECK(obj);
+ }
+ else {
+ BOOST_CHECK(!obj);
+ ++queued;
+ }
+ }
+
+ BOOST_CHECK_GT(queued, 1U);
+
+ /* clear the queue */
+ blocked = false;
+ size_t received = 0;
+ while (!blocked) {
+ auto got = receiver.receive();
+ if (got) {
+ ++received;
+ }
+ else {
+ blocked = true;
+ }
+ }
+
+ BOOST_CHECK_EQUAL(queued, received);
+
+ /* we should be able to write again */
+ auto obj = std::make_unique<MyObject>();
+ obj->a = 42U;
+ BOOST_CHECK(sender.send(std::move(obj)));
+ /* and to get it */
+ {
+ auto got = receiver.receive();
+ BOOST_CHECK(got);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_object_queue_throw_on_eof)
+{
+ auto [sender, receiver] = pdns::channel::createObjectQueue<MyObject>();
+ sender.close();
+ BOOST_CHECK_THROW(receiver.receive(), std::runtime_error);
+ BOOST_CHECK_EQUAL(receiver.isClosed(), true);
+}
+
+BOOST_AUTO_TEST_CASE(test_object_queue_do_not_throw_on_eof)
+{
+ auto [sender, receiver] = pdns::channel::createObjectQueue<MyObject>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, 0U, false);
+ sender.close();
+ auto got = receiver.receive();
+ BOOST_CHECK(got == std::nullopt);
+ BOOST_CHECK_EQUAL(receiver.isClosed(), true);
+}
+
+BOOST_AUTO_TEST_CASE(test_notification_queue_full)
+{
+ auto [notifier, waiter] = pdns::channel::createNotificationQueue();
+
+ BOOST_CHECK(waiter.getDescriptor() != -1);
+ BOOST_CHECK_EQUAL(waiter.isClosed(), false);
+ waiter.clear();
+
+ /* add notifications until the queue becomes full */
+ bool blocked = false;
+ while (!blocked) {
+ blocked = notifier.notify();
+ }
+
+ /* clear the queue */
+ waiter.clear();
+
+ /* we should be able to write again */
+ BOOST_CHECK(notifier.notify());
+}
+
+BOOST_AUTO_TEST_CASE(test_notification_queue_throw_on_eof)
+{
+ auto [notifier, waiter] = pdns::channel::createNotificationQueue();
+
+ BOOST_CHECK(waiter.getDescriptor() != -1);
+ BOOST_CHECK_EQUAL(waiter.isClosed(), false);
+
+ BOOST_CHECK_EQUAL(notifier.notify(), true);
+ waiter.clear();
+
+ notifier = pdns::channel::Notifier();
+ BOOST_CHECK_THROW(waiter.clear(), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_notification_queue_do_not_throw_on_eof)
+{
+ auto [notifier, waiter] = pdns::channel::createNotificationQueue(true, 0, false);
+
+ BOOST_CHECK(waiter.getDescriptor() != -1);
+ BOOST_CHECK_EQUAL(waiter.isClosed(), false);
+
+ BOOST_CHECK_EQUAL(notifier.notify(), true);
+ waiter.clear();
+
+ notifier = pdns::channel::Notifier();
+ waiter.clear();
+ BOOST_CHECK_EQUAL(waiter.isClosed(), true);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
result = std::make_shared<OPTRecordContent>();
}
else {
- result = DNSRecordContent::mastermake(type, QClass::IN, content);
+ result = DNSRecordContent::make(type, QClass::IN, content);
}
return result;
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/algorithm/string.hpp>
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
BOOST_AUTO_TEST_CASE(test_pdns_md5sum)
{
- std::string result = "a3 24 8c e3 1a 88 a6 40 e6 30 73 98 57 6d 06 9e ";
- std::string sum = pdns_md5("a quick brown fox jumped over the lazy dog");
+ std::string result = "a3 24 8c e3 1a 88 a6 40 e6 30 73 98 57 6d 06 9e ";
+ std::string sum = pdns::md5("a quick brown fox jumped over the lazy dog");
- BOOST_CHECK_EQUAL(makeHexDump(sum), result);
+ BOOST_CHECK_EQUAL(makeHexDump(sum), result);
}
BOOST_AUTO_TEST_CASE(test_pdns_sha1sum)
{
- std::string result = "b9 37 10 0d c9 57 b3 86 d9 cb 77 fc 90 c0 18 22 fd eb 6e 7f ";
- std::string sum = pdns_sha1("a quick brown fox jumped over the lazy dog");
+ std::string result = "b9 37 10 0d c9 57 b3 86 d9 cb 77 fc 90 c0 18 22 fd eb 6e 7f ";
+ std::string sum = pdns::sha1("a quick brown fox jumped over the lazy dog");
- BOOST_CHECK_EQUAL(makeHexDump(sum), result);
+ BOOST_CHECK_EQUAL(makeHexDump(sum), result);
}
BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_NO_MAIN
+#endif
-// Disable this code for gcc 4.8 and lower
-#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ > 8) || !__GNUC__
+#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
#include <boost/test/unit_test.hpp>
#include <boost/assign/std/map.hpp>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wextra"
#include <boost/accumulators/statistics/median.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
+#pragma GCC diagnostic pop
#include "arguments.hh"
#include "dns_random.hh"
#include "namespaces.hh"
-
-using namespace boost;
using namespace boost::accumulators;
-typedef accumulator_set<
- double
- , stats<boost::accumulators::tag::median(with_p_square_quantile),
- boost::accumulators::tag::mean(immediate)
- >
- > acc_t;
-
-
+using acc_t = accumulator_set<double, stats<tag::median(with_p_square_quantile), tag::mean(immediate)>>;
BOOST_AUTO_TEST_SUITE(test_dns_random_hh)
-BOOST_AUTO_TEST_CASE(test_dns_random_auto_average) {
-
- ::arg().set("rng")="auto";
- ::arg().set("entropy-source")="/dev/urandom";
-
- dns_random_init("", true);
-
- acc_t acc;
-
- for(unsigned int n=0; n < 100000; ++n) {
- acc(dns_random(100000)/100000.0);
- }
- BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
- BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
-
- // please add covariance tests, chi-square, Kolmogorov-Smirnov
-}
-
-BOOST_AUTO_TEST_CASE(test_dns_random_urandom_average) {
-
- ::arg().set("rng")="urandom";
- ::arg().set("entropy-source")="/dev/urandom";
-
- dns_random_init("", true);
-
- acc_t acc;
-
- for(unsigned int n=0; n < 100000; ++n) {
- acc(dns_random(100000)/100000.0);
- }
- BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
- BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
-
- // please add covariance tests, chi-square, Kolmogorov-Smirnov
-}
-
-BOOST_AUTO_TEST_CASE(test_dns_random_garbage) {
-
- ::arg().set("rng")="garbage";
- ::arg().set("entropy-source")="/dev/urandom";
+const std::vector<string> rndSources = {
+ "auto",
+ "urandom",
+#if defined(HAVE_GETRANDOM)
+ "getrandom",
+#endif
+#if defined(HAVE_ARC4RANDOM)
+ "arc4random",
+#endif
+#if defined(HAVE_RANDOMBYTES_STIR)
+ "sodium",
+#endif
+#if defined(HAVE_RAND_BYTES)
+ "openssl",
+#endif
+#if defined(HAVE_KISS_RNG)
+ "kiss",
+#endif
+};
- BOOST_CHECK_THROW(dns_random_init("", true), std::runtime_error);
+BOOST_AUTO_TEST_CASE(test_dns_random_garbage)
+{
+ ::arg().set("rng") = "garbage";
+ ::arg().set("entropy-source") = "/dev/urandom";
}
-BOOST_AUTO_TEST_CASE(test_dns_random_upper_bound) {
- ::arg().set("rng")="auto";
- ::arg().set("entropy-source")="/dev/urandom";
-
- dns_random_init("", true);
+BOOST_AUTO_TEST_CASE(test_dns_random_upper_bound)
+{
+ ::arg().set("rng") = "auto";
+ ::arg().set("entropy-source") = "/dev/urandom";
- map<int, bool> seen;
- for(unsigned int n=0; n < 100000; ++n) {
+ map<unsigned int, bool> seen;
+ for (unsigned int iteration = 0; iteration < 100000; ++iteration) {
seen[dns_random(10)] = true;
}
BOOST_CHECK_EQUAL(seen[10], false);
}
-#if defined(HAVE_GETRANDOM)
-BOOST_AUTO_TEST_CASE(test_dns_random_getrandom_average) {
-
- ::arg().set("rng")="getrandom";
- ::arg().set("entropy-source")="/dev/urandom";
-
- dns_random_init("", true);
+static void test_dns_random_avg(const string& source)
+{
+ ::arg().set("rng") = source;
+ ::arg().set("entropy-source") = "/dev/urandom";
acc_t acc;
- for(unsigned int n=0; n < 100000; ++n) {
- acc(dns_random(100000)/100000.0);
+ for (unsigned int iteration = 0; iteration < 100000; ++iteration) {
+ acc(dns_random(100000) / 100000.0);
}
BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
// please add covariance tests, chi-square, Kolmogorov-Smirnov
}
-#endif
-
-#if defined(HAVE_ARC4RANDOM)
-BOOST_AUTO_TEST_CASE(test_dns_random_arc4random_average) {
-
- ::arg().set("rng")="arc4random";
- ::arg().set("entropy-source")="/dev/urandom";
-
- dns_random_init("", true);
-
- acc_t acc;
-
- for(unsigned int n=0; n < 100000; ++n) {
- acc(dns_random(100000)/100000.0);
- }
- BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
- BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
- // please add covariance tests, chi-square, Kolmogorov-Smirnov
-}
-#endif
-
-#if defined(HAVE_RANDOMBYTES_STIR)
-BOOST_AUTO_TEST_CASE(test_dns_random_sodium_average) {
-
- ::arg().set("rng")="sodium";
- ::arg().set("entropy-source")="/dev/urandom";
-
- dns_random_init("", true);
+static void test_dns_random_uint32_avg(const string& source)
+{
+ ::arg().set("rng") = source;
+ ::arg().set("entropy-source") = "/dev/urandom";
acc_t acc;
- for(unsigned int n=0; n < 100000; ++n) {
- acc(dns_random(100000)/100000.0);
+ for (unsigned int iteration = 0; iteration < 100000; ++iteration) {
+ acc(dns_random_uint32() / static_cast<double>(pdns::dns_random_engine::max()));
}
BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
// please add covariance tests, chi-square, Kolmogorov-Smirnov
}
-#endif
-
-#if defined(HAVE_RAND_BYTES)
-BOOST_AUTO_TEST_CASE(test_dns_random_openssl_average) {
-
- ::arg().set("rng")="openssl";
- ::arg().set("entropy-source")="/dev/urandom";
- dns_random_init("", true);
-
- acc_t acc;
-
- for(unsigned int n=0; n < 100000; ++n) {
- acc(dns_random(100000)/100000.0);
+BOOST_AUTO_TEST_CASE(test_dns_random_average)
+{
+ for (const auto& source : rndSources) {
+ test_dns_random_avg(source);
}
- BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
- BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
-
- // please add covariance tests, chi-square, Kolmogorov-Smirnov
}
-#endif
-
-#if defined(HAVE_KISS_RNG)
-BOOST_AUTO_TEST_CASE(test_dns_random_kiss_average) {
-
- ::arg().set("rng")="kiss";
- ::arg().set("entropy-source")="/dev/urandom";
- dns_random_init("", true);
-
- acc_t acc;
-
- for(unsigned int n=0; n < 100000; ++n) {
- acc(dns_random(100000)/100000.0);
+BOOST_AUTO_TEST_CASE(test_dns_random_uint32_average)
+{
+ for (const auto& source : rndSources) {
+ test_dns_random_uint32_avg(source);
}
- BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
- BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
-
- // please add covariance tests, chi-square, Kolmogorov-Smirnov
}
-#endif
-
BOOST_AUTO_TEST_SUITE_END()
-
-#endif
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
#include <unistd.h>
bool g_verbose{false};
-bool g_syslog{true};
-bool g_logtimestamps{false};
-std::optional<std::ofstream> g_verboseStream{std::nullopt};
BOOST_AUTO_TEST_SUITE(test_dnscrypt_cc)
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_NO_MAIN
-
-#include <boost/test/unit_test.hpp>
-#include <unistd.h>
-
-#include "dnsdist.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-internal-queries.hh"
-#include "dnsdist-tcp.hh"
-#include "dnsdist-xpf.hh"
-
-#include "dolog.hh"
-#include "dnsname.hh"
-#include "dnsparser.hh"
-#include "dnswriter.hh"
-#include "ednsoptions.hh"
-#include "ednscookies.hh"
-#include "ednssubnet.hh"
-
-ProcessQueryResult processQueryAfterRules(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
-{
- return ProcessQueryResult::Drop;
-}
-
-bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
-{
- return false;
-}
-
-bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
-{
- return false;
-}
-
-bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& ds, uint16_t queryID, DNSQuestion& dq, PacketBuffer& query, ComboAddress& dest)
-{
- return false;
-}
-
-namespace dnsdist {
-std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, bool isResponse)
-{
- return nullptr;
-}
-}
-
-bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(DownstreamState const&)
-{
- return false;
-}
-
-BOOST_AUTO_TEST_SUITE(test_dnsdist_cc)
-
-static const uint16_t ECSSourcePrefixV4 = 24;
-static const uint16_t ECSSourcePrefixV6 = 56;
-
-static void validateQuery(const PacketBuffer& packet, bool hasEdns=true, bool hasXPF=false, uint16_t additionals=0, uint16_t answers=0, uint16_t authorities=0)
-{
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, answers);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, authorities);
- uint16_t expectedARCount = additionals + (hasEdns ? 1U : 0U) + (hasXPF ? 1U : 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, expectedARCount);
-}
-
-static void validateECS(const PacketBuffer& packet, const ComboAddress& expected)
-{
- InternalQueryState ids;
- ids.protocol = dnsdist::Protocol::DoUDP;
- ids.origRemote = ComboAddress("::1");
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- DNSQuestion dq(ids, const_cast<PacketBuffer&>(packet));
- BOOST_CHECK(parseEDNSOptions(dq));
- BOOST_REQUIRE(dq.ednsOptions != nullptr);
- BOOST_CHECK_EQUAL(dq.ednsOptions->size(), 1U);
- const auto& ecsOption = dq.ednsOptions->find(EDNSOptionCode::ECS);
- BOOST_REQUIRE(ecsOption != dq.ednsOptions->cend());
-
- string expectedOption;
- generateECSOption(expected, expectedOption, expected.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
- /* we need to skip the option code and length, which are not included */
- BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U);
- BOOST_CHECK_EQUAL(expectedOption.substr(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
-}
-
-static void validateResponse(const PacketBuffer& packet, bool hasEdns, uint8_t additionalCount=0)
-{
- MOADNSParser mdp(false, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-
- BOOST_CHECK_EQUAL(mdp.d_header.qr, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, (hasEdns ? 1U : 0U) + additionalCount);
-}
-
-BOOST_AUTO_TEST_CASE(test_addXPF)
-{
- static const uint16_t xpfOptionCode = 65422;
-
- DNSName name("www.powerdns.com.");
- InternalQueryState ids;
- ids.protocol = dnsdist::Protocol::DoUDP;
- ids.origRemote = ComboAddress("::1");
- ids.origDest = ComboAddress("::1");
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- PacketBuffer queryWithXPF;
-
- {
- PacketBuffer packet = query;
-
- /* large enough packet */
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- DNSQuestion dq(ids, const_cast<PacketBuffer&>(packet));
- BOOST_CHECK_EQUAL(ids.qname, name);
- BOOST_CHECK(ids.qtype == QType::A);
-
- BOOST_CHECK(addXPF(dq, xpfOptionCode));
- BOOST_CHECK(packet.size() > query.size());
- validateQuery(packet, false, true);
- queryWithXPF = packet;
- }
-
- {
- PacketBuffer packet = query;
-
- /* packet is already too large for the 4096 limit over UDP */
- packet.resize(4096);
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- DNSQuestion dq(ids, const_cast<PacketBuffer&>(packet));
- BOOST_CHECK_EQUAL(ids.qname, name);
- BOOST_CHECK(ids.qtype == QType::A);
-
- BOOST_REQUIRE(!addXPF(dq, xpfOptionCode));
- BOOST_CHECK_EQUAL(packet.size(), 4096U);
- packet.resize(query.size());
- validateQuery(packet, false, false);
- }
-
- {
- PacketBuffer packet = query;
-
- /* packet with trailing data (overriding it) */
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- DNSQuestion dq(ids, const_cast<PacketBuffer&>(packet));
- BOOST_CHECK_EQUAL(ids.qname, name);
- BOOST_CHECK(ids.qtype == QType::A);
-
- /* add trailing data */
- const size_t trailingDataSize = 10;
- /* Making sure we have enough room to allow for fake trailing data */
- packet.resize(packet.size() + trailingDataSize);
- for (size_t idx = 0; idx < trailingDataSize; idx++) {
- packet.push_back('A');
- }
-
- BOOST_CHECK(addXPF(dq, xpfOptionCode));
- BOOST_CHECK_EQUAL(packet.size(), queryWithXPF.size());
- BOOST_CHECK_EQUAL(memcmp(queryWithXPF.data(), packet.data(), queryWithXPF.size()), 0);
- validateQuery(packet, false, true);
- }
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithoutEDNS)
-{
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.0.2.1");
- DNSName name("www.powerdns.com.");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- uint16_t len = query.size();
-
- /* large enough packet */
- PacketBuffer packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, true);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet);
- validateECS(packet, remote);
- PacketBuffer queryWithEDNS = packet;
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- packet.resize(query.size());
- validateQuery(packet, false);
-
- /* packet with trailing data (overriding it) */
- packet = query;
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
- /* add trailing data */
- const size_t trailingDataSize = 10;
- /* Making sure we have enough room to allow for fake trailing data */
- packet.resize(packet.size() + trailingDataSize);
- for (size_t idx = 0; idx < trailingDataSize; idx++) {
- packet[len + idx] = 'A';
- }
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
- BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size());
- BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0);
- BOOST_CHECK_EQUAL(ednsAdded, true);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet);
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithoutEDNSButWithAnswer)
-{
- /* this might happen for NOTIFY queries where, according to rfc1996:
- "If ANCOUNT>0, then the answer section represents an
- unsecure hint at the new RRset for this <QNAME,QCLASS,QTYPE>".
- */
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.0.2.1");
- DNSName name("www.powerdns.com.");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER, false);
- pw.xfrIP(remote.sin4.sin_addr.s_addr);
- pw.commit();
- uint16_t len = query.size();
-
- /* large enough packet */
- PacketBuffer packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, true);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet, true, false, 0, 1);
- validateECS(packet, remote);
- PacketBuffer queryWithEDNS = packet;
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- packet.resize(query.size());
- validateQuery(packet, false, false, 0, 1);
-
- /* packet with trailing data (overriding it) */
- packet = query;
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
- /* add trailing data */
- const size_t trailingDataSize = 10;
- /* Making sure we have enough room to allow for fake trailing data */
- packet.resize(packet.size() + trailingDataSize);
- for (size_t idx = 0; idx < trailingDataSize; idx++) {
- packet[len + idx] = 'A';
- }
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
- BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size());
- BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0);
- BOOST_CHECK_EQUAL(ednsAdded, true);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet, true, false, 0, 1);
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
-{
- InternalQueryState ids;
- ids.origRemote = ComboAddress("192.0.2.1");
- ids.protocol = dnsdist::Protocol::DoUDP;
- bool ednsAdded = false;
- bool ecsAdded = false;
- DNSName name("www.powerdns.com.");
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
-
- auto packet = query;
-
- ids.qname = DNSName(reinterpret_cast<const char *>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- BOOST_CHECK_EQUAL(ids.qname, name);
- BOOST_CHECK(ids.qtype == QType::A);
- BOOST_CHECK(ids.qclass == QClass::IN);
-
- DNSQuestion dq(ids, packet);
- /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
- BOOST_CHECK(!parseEDNSOptions(dq));
-
- /* And now we add our own ECS */
- BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded));
- BOOST_CHECK_GT(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, true);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet);
- validateECS(packet, ids.origRemote);
-
- /* trailing data */
- packet = query;
- packet.resize(2048);
-
- ednsAdded = false;
- ecsAdded = false;
-
- ids.qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- BOOST_CHECK_EQUAL(ids.qname, name);
- BOOST_CHECK(ids.qtype == QType::A);
- BOOST_CHECK(ids.qclass == QClass::IN);
- DNSQuestion dq2(ids, packet);
-
- BOOST_CHECK(handleEDNSClientSubnet(dq2, ednsAdded, ecsAdded));
- BOOST_CHECK_GT(packet.size(), query.size());
- BOOST_CHECK_LT(packet.size(), 2048U);
- BOOST_CHECK_EQUAL(ednsAdded, true);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet);
- validateECS(packet, ids.origRemote);
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote;
- DNSName name("www.powerdns.com.");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.addOpt(512, 0, 0);
- pw.commit();
-
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet);
- validateECS(packet, remote);
-
- /* not large enough packet */
- consumed = 0;
- ednsAdded = false;
- ecsAdded = false;
- packet = query;
-
- qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet);
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) {
- InternalQueryState ids;
- ids.origRemote = ComboAddress("2001:DB8::1");
- ids.protocol = dnsdist::Protocol::DoUDP;
- bool ednsAdded = false;
- bool ecsAdded = false;
- DNSName name("www.powerdns.com.");
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.addOpt(512, 0, 0);
- pw.commit();
-
- auto packet = query;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- BOOST_CHECK_EQUAL(ids.qname, name);
- BOOST_CHECK(ids.qtype == QType::A);
- BOOST_CHECK(ids.qclass == QClass::IN);
-
- DNSQuestion dq(ids, packet);
- /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
- BOOST_CHECK(parseEDNSOptions(dq));
-
- /* And now we add our own ECS */
- BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded));
- BOOST_CHECK_GT(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet);
- validateECS(packet, ids.origRemote);
-
- /* trailing data */
- packet = query;
- packet.resize(2048);
- ednsAdded = false;
- ecsAdded = false;
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
- BOOST_CHECK_EQUAL(ids.qname, name);
- BOOST_CHECK(ids.qtype == QType::A);
- BOOST_CHECK(ids.qclass == QClass::IN);
- DNSQuestion dq2(ids, packet);
-
- BOOST_CHECK(handleEDNSClientSubnet(dq2, ednsAdded, ecsAdded));
- BOOST_CHECK_GT(packet.size(), query.size());
- BOOST_CHECK_LT(packet.size(), 2048U);
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet);
- validateECS(packet, ids.origRemote);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet);
- validateECS(packet, remote);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- ComboAddress origRemote("127.0.0.1");
- InternalQueryState ids;
- ids.origRemote = remote;
- ids.protocol = dnsdist::Protocol::DoUDP;
- ids.qname = DNSName("www.powerdns.com.");
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- uint16_t qclass;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
- BOOST_CHECK_EQUAL(qname, ids.qname);
- BOOST_CHECK(qtype == QType::A);
- BOOST_CHECK(qclass == QClass::IN);
-
- DNSQuestion dq(ids, packet);
- dq.ecsOverride = true;
-
- /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
- BOOST_CHECK(parseEDNSOptions(dq));
-
- /* And now we add our own ECS */
- BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet);
- validateECS(packet, remote);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSWithSmaller) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, 32);
- string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK(packet.size() < query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet);
- validateECS(packet, remote);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSWithLarger) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- EDNSSubnetOpts ecsOpts;
- // smaller (less specific so less bits) option
- static_assert(8 < ECSSourcePrefixV4, "The ECS scope should be smaller");
- ecsOpts.source = Netmask(origRemote, 8);
- string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet);
- validateECS(packet, remote);
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSFollowedByTSIG) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, 8);
- string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
- pw.addOpt(512, 0, 0, opts);
- pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 1);
- validateECS(packet, remote);
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 1);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSAfterAN) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.commit();
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, 8);
- string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 0, 1, 0);
- validateECS(packet, remote);
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 0, 1, 0);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSAfterAuth) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::AUTHORITY, true);
- pw.commit();
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, 8);
- string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 0, 0, 1);
- validateECS(packet, remote);
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 0, 0, 1);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSBetweenTwoRecords) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, 8);
- string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
- pw.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
- pw.xfr32BitInt(0x01020304);
- pw.addOpt(512, 0, 0, opts);
- pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 2);
- validateECS(packet, remote);
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 2);
-}
-
-BOOST_AUTO_TEST_CASE(insertECSInEDNSBetweenTwoRecords) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
- pw.xfr32BitInt(0x01020304);
- pw.addOpt(512, 0, 0);
- pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- validateQuery(packet, true, false, 2);
- validateECS(packet, remote);
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(query, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false, 2);
-}
-
-BOOST_AUTO_TEST_CASE(insertECSAfterTSIG) {
- bool ednsAdded = false;
- bool ecsAdded = false;
- ComboAddress remote("192.168.1.25");
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
- string newECSOption;
- generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK(packet.size() > query.size());
- BOOST_CHECK_EQUAL(ednsAdded, true);
- BOOST_CHECK_EQUAL(ecsAdded, true);
- /* the MOADNSParser does not allow anything except XPF after a TSIG */
- BOOST_CHECK_THROW(validateQuery(packet, true, false, 1), MOADNSException);
- validateECS(packet, remote);
-
- /* not large enough packet */
- packet = query;
-
- ednsAdded = false;
- ecsAdded = false;
- consumed = 0;
- qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
- BOOST_CHECK_EQUAL(packet.size(), query.size());
- BOOST_CHECK_EQUAL(ednsAdded, false);
- BOOST_CHECK_EQUAL(ecsAdded, false);
- validateQuery(packet, true, false);
-}
-
-
-BOOST_AUTO_TEST_CASE(removeEDNSWhenFirst) {
- DNSName name("www.powerdns.com.");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
- pw.addOpt(512, 0, 0);
- pw.commit();
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- PacketBuffer newResponse;
- int res = rewriteResponseWithoutEDNS(response, newResponse);
- BOOST_CHECK_EQUAL(res, 0);
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
- size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
- BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
-
- validateResponse(newResponse, false, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeEDNSWhenIntermediary) {
- DNSName name("www.powerdns.com.");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
- pw.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
- pw.addOpt(512, 0, 0);
- pw.commit();
- pw.startRecord(DNSName("yetanother.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- PacketBuffer newResponse;
- int res = rewriteResponseWithoutEDNS(response, newResponse);
- BOOST_CHECK_EQUAL(res, 0);
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
- size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
- BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
-
- validateResponse(newResponse, false, 2);
-}
-
-BOOST_AUTO_TEST_CASE(removeEDNSWhenLast) {
- DNSName name("www.powerdns.com.");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
- pw.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
- pw.addOpt(512, 0, 0);
- pw.commit();
-
- PacketBuffer newResponse;
- int res = rewriteResponseWithoutEDNS(response, newResponse);
-
- BOOST_CHECK_EQUAL(res, 0);
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
- size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
- BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
-
- validateResponse(newResponse, false, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeECSWhenOnlyOption) {
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
-
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- uint16_t optStart;
- size_t optLen = 0;
- bool last = false;
-
- int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(last, true);
-
- size_t responseLen = response.size();
- size_t existingOptLen = optLen;
- BOOST_CHECK(existingOptLen < responseLen);
- res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
- responseLen -= (existingOptLen - optLen);
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- validateResponse(response, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeECSWhenFirstOption) {
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
-
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV6);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr = cookiesOpt.makeOptString();
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- uint16_t optStart;
- size_t optLen = 0;
- bool last = false;
-
- int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(last, true);
-
- size_t responseLen = response.size();
- size_t existingOptLen = optLen;
- BOOST_CHECK(existingOptLen < responseLen);
- res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
- responseLen -= (existingOptLen - optLen);
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- validateResponse(response, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeECSWhenIntermediaryOption) {
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
-
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr1 = cookiesOpt.makeOptString();
- string cookiesOptionStr2 = cookiesOpt.makeOptString();
-
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1);
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- uint16_t optStart;
- size_t optLen = 0;
- bool last = false;
-
- int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(last, true);
-
- size_t responseLen = response.size();
- size_t existingOptLen = optLen;
- BOOST_CHECK(existingOptLen < responseLen);
- res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
- responseLen -= (existingOptLen - optLen);
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- validateResponse(response, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeECSWhenLastOption) {
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
-
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr = cookiesOpt.makeOptString();
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- uint16_t optStart;
- size_t optLen = 0;
- bool last = false;
-
- int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(last, true);
-
- size_t responseLen = response.size();
- size_t existingOptLen = optLen;
- BOOST_CHECK(existingOptLen < responseLen);
- res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
- responseLen -= (existingOptLen - optLen);
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- validateResponse(response, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenOnlyOption) {
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
-
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- PacketBuffer newResponse;
- int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
- BOOST_CHECK_EQUAL(res, 0);
-
- BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- validateResponse(newResponse, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenFirstOption) {
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
-
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr = cookiesOpt.makeOptString();
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- PacketBuffer newResponse;
- int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
- BOOST_CHECK_EQUAL(res, 0);
-
- BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- validateResponse(newResponse, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenIntermediaryOption) {
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
-
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr1 = cookiesOpt.makeOptString();
- string cookiesOptionStr2 = cookiesOpt.makeOptString();
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1);
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- PacketBuffer newResponse;
- int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
- BOOST_CHECK_EQUAL(res, 0);
-
- BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- validateResponse(newResponse, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenLastOption) {
- DNSName name("www.powerdns.com.");
- ComboAddress origRemote("127.0.0.1");
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
- pw.getHeader()->qr = 1;
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
- pw.xfr32BitInt(0x01020304);
-
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr = cookiesOpt.makeOptString();
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
- pw.xfr32BitInt(0x01020304);
- pw.commit();
-
- PacketBuffer newResponse;
- int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
- BOOST_CHECK_EQUAL(res, 0);
-
- BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
-
- unsigned int consumed = 0;
- uint16_t qtype;
- DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
- BOOST_CHECK_EQUAL(qname, name);
- BOOST_CHECK(qtype == QType::A);
-
- validateResponse(newResponse, true, 1);
-}
-
-static DNSQuestion turnIntoResponse(InternalQueryState& ids, PacketBuffer& query, bool resizeBuffer=true)
-{
- if (resizeBuffer) {
- query.resize(4096);
- }
-
- auto dq = DNSQuestion(ids, query);
-
- BOOST_CHECK(addEDNSToQueryTurnedResponse(dq));
-
- return dq;
-}
-
-static int getZ(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, PacketBuffer& query)
-{
- InternalQueryState ids;
- ids.protocol = dnsdist::Protocol::DoUDP;
- ids.qname = qname;
- ids.qtype = qtype;
- ids.qclass = qclass;
- ids.origDest = ComboAddress("127.0.0.1");
- ids.origRemote = ComboAddress("127.0.0.1");
- ids.queryRealTime.start();
-
- auto dq = DNSQuestion(ids, query);
-
- return getEDNSZ(dq);
-}
-
-BOOST_AUTO_TEST_CASE(test_getEDNSZ) {
-
- uint16_t z;
- uint16_t udpPayloadSize;
- DNSName qname("www.powerdns.com.");
- uint16_t qtype = QType::A;
- uint16_t qclass = QClass::IN;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr = cookiesOpt.makeOptString();
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-
- {
- /* no EDNS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.commit();
-
- BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), false);
- BOOST_CHECK_EQUAL(z, 0);
- BOOST_CHECK_EQUAL(udpPayloadSize, 0);
- }
-
- {
- /* truncated EDNS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
- pw.commit();
-
- query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1));
- BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), false);
- BOOST_CHECK_EQUAL(z, 0);
- BOOST_CHECK_EQUAL(udpPayloadSize, 0);
- }
-
- {
- /* valid EDNS, no options, DO not set */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.addOpt(512, 0, 0);
- pw.commit();
-
- BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true);
- BOOST_CHECK_EQUAL(z, 0);
- BOOST_CHECK_EQUAL(udpPayloadSize, 512);
- }
-
- {
- /* valid EDNS, no options, DO set */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
- pw.commit();
-
- BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true);
- BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO);
- BOOST_CHECK_EQUAL(udpPayloadSize, 512);
- }
-
- {
- /* valid EDNS, options, DO not set */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true);
- BOOST_CHECK_EQUAL(z, 0);
- BOOST_CHECK_EQUAL(udpPayloadSize, 512);
- }
-
- {
- /* valid EDNS, options, DO set */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts);
- pw.commit();
-
- BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true);
- BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO);
- BOOST_CHECK_EQUAL(udpPayloadSize, 512);
- }
-
-}
-
-BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
- InternalQueryState ids;
- ids.qname = DNSName("www.powerdns.com.");
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.origDest = ComboAddress("127.0.0.1");
- ids.origRemote = ComboAddress("127.0.0.1");
- ids.queryRealTime.start();
- uint16_t z;
- uint16_t udpPayloadSize;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
- string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr = cookiesOpt.makeOptString();
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-
- {
- /* no EDNS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
- pw.getHeader()->qr = 1;
- pw.getHeader()->rcode = RCode::NXDomain;
- pw.commit();
-
- auto dq = turnIntoResponse(ids, query);
- BOOST_CHECK_EQUAL(getEDNSZ(dq), 0);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), false);
- BOOST_CHECK_EQUAL(z, 0);
- BOOST_CHECK_EQUAL(udpPayloadSize, 0);
- }
-
- {
- /* truncated EDNS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
- pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
- pw.commit();
-
- query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1));
- auto dq = turnIntoResponse(ids, query, false);
- BOOST_CHECK_EQUAL(getEDNSZ(dq), 0);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), false);
- BOOST_CHECK_EQUAL(z, 0);
- BOOST_CHECK_EQUAL(udpPayloadSize, 0);
- }
-
- {
- /* valid EDNS, no options, DO not set */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
- pw.addOpt(512, 0, 0);
- pw.commit();
-
- auto dq = turnIntoResponse(ids, query);
- BOOST_CHECK_EQUAL(getEDNSZ(dq), 0);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true);
- BOOST_CHECK_EQUAL(z, 0);
- BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
- }
-
- {
- /* valid EDNS, no options, DO set */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
- pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
- pw.commit();
-
- auto dq = turnIntoResponse(ids, query);
- BOOST_CHECK_EQUAL(getEDNSZ(dq), EDNS_HEADER_FLAG_DO);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true);
- BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO);
- BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
- }
-
- {
- /* valid EDNS, options, DO not set */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- auto dq = turnIntoResponse(ids, query);
- BOOST_CHECK_EQUAL(getEDNSZ(dq), 0);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true);
- BOOST_CHECK_EQUAL(z, 0);
- BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
- }
-
- {
- /* valid EDNS, options, DO set */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
- pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts);
- pw.commit();
-
- auto dq = turnIntoResponse(ids, query);
- BOOST_CHECK_EQUAL(getEDNSZ(dq), EDNS_HEADER_FLAG_DO);
- BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true);
- BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO);
- BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_getEDNSOptionsStart) {
- const DNSName qname("www.powerdns.com.");
- const uint16_t qtype = QType::A;
- const uint16_t qclass = QClass::IN;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
- const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
- const ComboAddress lc("127.0.0.1");
- const ComboAddress rem("127.0.0.1");
- uint16_t optRDPosition;
- size_t remaining;
-
- const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE;
-
- {
- /* no EDNS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.getHeader()->qr = 1;
- pw.getHeader()->rcode = RCode::NXDomain;
- pw.commit();
-
- int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-
- BOOST_CHECK_EQUAL(res, ENOENT);
-
- /* truncated packet (should not matter) */
- query.resize(query.size() - 1);
- res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-
- BOOST_CHECK_EQUAL(res, ENOENT);
- }
-
- {
- /* valid EDNS, no options */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.addOpt(512, 0, 0);
- pw.commit();
-
- int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
- BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
-
- /* truncated packet */
- query.resize(query.size() - 1);
-
- res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
- BOOST_CHECK_EQUAL(res, ENOENT);
- }
-
- {
- /* valid EDNS, options */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
- BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
-
- /* truncated options (should not matter for this test) */
- query.resize(query.size() - 1);
- res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
- BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
- BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
- }
-
-}
-
-BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
-
- auto locateEDNSOption = [](const PacketBuffer& query, uint16_t code, size_t* optContentStart, uint16_t* optContentLen) {
- uint16_t optStart;
- size_t optLen;
- bool last = false;
- int res = locateEDNSOptRR(query, &optStart, &optLen, &last);
- if (res != 0) {
- // no EDNS OPT RR
- return false;
- }
-
- if (optLen < optRecordMinimumSize) {
- return false;
- }
-
- if (optStart < query.size() && query.at(optStart) != 0) {
- // OPT RR Name != '.'
- return false;
- }
-
- return isEDNSOptionInOpt(query, optStart, optLen, code, optContentStart, optContentLen);
- };
-
- const DNSName qname("www.powerdns.com.");
- const uint16_t qtype = QType::A;
- const uint16_t qclass = QClass::IN;
- EDNSSubnetOpts ecsOpts;
- ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
- const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
- const size_t sizeOfECSContent = ecsOptionStr.size();
- const size_t sizeOfECSOption = /* option code */ 2 + /* option length */ 2 + sizeOfECSContent;
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr = cookiesOpt.makeOptString();
- const size_t sizeOfCookieOption = /* option code */ 2 + /* option length */ 2 + cookiesOpt.size();
- /*
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- */
- const ComboAddress lc("127.0.0.1");
- const ComboAddress rem("127.0.0.1");
- size_t optContentStart{std::numeric_limits<size_t>::max()};
- uint16_t optContentLen{0};
-
- const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE;
-
- {
- /* no EDNS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.getHeader()->qr = 1;
- pw.getHeader()->rcode = RCode::NXDomain;
- pw.commit();
-
- bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
- BOOST_CHECK_EQUAL(found, false);
-
- /* truncated packet (should not matter here) */
- query.resize(query.size() - 1);
- found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
- BOOST_CHECK_EQUAL(found, false);
- }
-
- {
- /* valid EDNS, no options */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- pw.addOpt(512, 0, 0);
- pw.commit();
-
- bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
- BOOST_CHECK_EQUAL(found, false);
-
- /* truncated packet */
- query.resize(query.size() - 1);
- BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::out_of_range);
- }
-
- {
- /* valid EDNS, two cookie options but no ECS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
- BOOST_CHECK_EQUAL(found, false);
-
- /* truncated packet */
- query.resize(query.size() - 1);
- BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
- }
-
- {
- /* valid EDNS, two ECS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
- opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
- BOOST_CHECK_EQUAL(found, true);
- if (found == true) {
- BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + /* option code */ 2 + /* option length */ 2);
- BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
- }
-
- /* truncated packet */
- query.resize(query.size() - 1);
- BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
- }
-
- {
- /* valid EDNS, one ECS between two cookies */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
- opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
- BOOST_CHECK_EQUAL(found, true);
- if (found == true) {
- BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfCookieOption + /* option code */ 2 + /* option length */ 2);
- BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
- }
-
- /* truncated packet */
- query.resize(query.size() - 1);
- BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
- }
-
- {
- /* valid EDNS, one 65002 after an ECS */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
- opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
- opts.emplace_back(65535, cookiesOptionStr);
- pw.addOpt(512, 0, 0, opts);
- pw.commit();
-
- bool found = locateEDNSOption(query, 65535, &optContentStart, &optContentLen);
- BOOST_CHECK_EQUAL(found, true);
- if (found == true) {
- BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfECSOption + /* option code */ 2 + /* option length */ 2);
- BOOST_CHECK_EQUAL(optContentLen, cookiesOptionStr.size());
- }
-
- /* truncated packet */
- query.resize(query.size() - 1);
- BOOST_CHECK_THROW(locateEDNSOption(query, 65002, &optContentStart, &optContentLen), std::range_error);
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
- InternalQueryState ids;
- ids.origRemote = ComboAddress("192.0.2.1");
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- ComboAddress remote;
- DNSName name("www.powerdns.com.");
-
- PacketBuffer query;
- PacketBuffer queryWithEDNS;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- GenericDNSPacketWriter<PacketBuffer> pwEDNS(queryWithEDNS, name, QType::A, QClass::IN, 0);
- pwEDNS.getHeader()->rd = 1;
- pwEDNS.addOpt(1232, 0, 0);
- pwEDNS.commit();
-
- /* test NXD */
- {
- /* no incoming EDNS */
- auto packet = query;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, false));
- BOOST_CHECK(packet.size() > query.size());
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
- BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
- BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
- }
- {
- /* now with incoming EDNS */
- auto packet = queryWithEDNS;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, false));
- BOOST_CHECK(packet.size() > queryWithEDNS.size());
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
- BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U);
- BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
- }
-
- /* test No Data */
- {
- /* no incoming EDNS */
- auto packet = query;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, false));
- BOOST_CHECK(packet.size() > query.size());
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
- BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
- BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
- }
- {
- /* now with incoming EDNS */
- auto packet = queryWithEDNS;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, false));
- BOOST_CHECK(packet.size() > queryWithEDNS.size());
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
- BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U);
- BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
- }
-
- /* SOA in the authority section*/
-
- /* test NXD */
- {
- /* no incoming EDNS */
- auto packet = query;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 ,
- 5, true));
- BOOST_CHECK(packet.size() > query.size());
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
- BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U);
- BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
- }
- {
- /* now with incoming EDNS */
- auto packet = queryWithEDNS;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, true));
- BOOST_CHECK(packet.size() > queryWithEDNS.size());
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
- BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
- BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
- }
-
- /* test No Data */
- {
- /* no incoming EDNS */
- auto packet = query;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, true));
- BOOST_CHECK(packet.size() > query.size());
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
- BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U);
- BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
- }
- {
- /* now with incoming EDNS */
- auto packet = queryWithEDNS;
-
- ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, true));
- BOOST_CHECK(packet.size() > queryWithEDNS.size());
- MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
- BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
- BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
- }
-}
-
-BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS) {
- InternalQueryState ids;
- ids.origRemote = ComboAddress("192.168.1.25");
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- const DNSName name("www.powerdns.com.");
- const ComboAddress v4("192.0.2.1");
-
- {
- /* no EDNS and no other additional record */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- uint16_t qclass;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(!parseEDNSOptions(dq));
- }
-
- {
- /* nothing in additional (so no EDNS) but a record in ANSWER */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER);
- pw.xfrIP(v4.sin4.sin_addr.s_addr);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- uint16_t qclass;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(!parseEDNSOptions(dq));
- }
-
- {
- /* nothing in additional (so no EDNS) but a record in AUTHORITY */
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
- pw.getHeader()->rd = 1;
- pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::AUTHORITY);
- pw.xfrIP(v4.sin4.sin_addr.s_addr);
- pw.commit();
-
- /* large enough packet */
- auto packet = query;
-
- unsigned int consumed = 0;
- uint16_t qtype;
- uint16_t qclass;
- DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
- DNSQuestion dq(ids, packet);
-
- BOOST_CHECK(!parseEDNSOptions(dq));
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_setEDNSOption)
-{
- InternalQueryState ids;
- ids.origRemote = ComboAddress("192.0.2.1:42");
- ids.origDest = ComboAddress("127.0.0.1:53");
- ids.protocol = dnsdist::Protocol::DoUDP;
- ids.qname = DNSName("powerdns.com.");
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.queryRealTime.start();
-
- struct timespec expiredTime;
- /* the internal QPS limiter does not use the real time */
- gettime(&expiredTime);
-
- PacketBuffer packet;
- GenericDNSPacketWriter<PacketBuffer> pw(packet, ids.qname, ids.qtype, ids.qclass, 0);
- pw.addOpt(4096, 0, EDNS_HEADER_FLAG_DO);
- pw.commit();
-
- DNSQuestion dq(ids, packet);
-
- std::string result;
- EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
- string cookiesOptionStr = cookiesOpt.makeOptString();
-
- BOOST_REQUIRE(setEDNSOption(dq, EDNSOptionCode::COOKIE, cookiesOptionStr));
-
- const auto& data = dq.getData();
- MOADNSParser mdp(true, reinterpret_cast<const char*>(data.data()), data.size());
-
- BOOST_CHECK_EQUAL(mdp.d_qname.toString(), ids.qname.toString());
- BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
- BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::OPT));
- BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, g_rootdnsname);
-
- EDNS0Record edns0;
- BOOST_REQUIRE(getEDNS0Record(dq.getData(), edns0));
- BOOST_CHECK_EQUAL(edns0.version, 0U);
- BOOST_CHECK_EQUAL(edns0.extRCode, 0U);
- BOOST_CHECK_EQUAL(edns0.extFlags, EDNS_HEADER_FLAG_DO);
-
- BOOST_REQUIRE(parseEDNSOptions(dq));
- BOOST_REQUIRE(dq.ednsOptions != nullptr);
- BOOST_CHECK_EQUAL(dq.ednsOptions->size(), 1U);
- const auto& ecsOption = dq.ednsOptions->find(EDNSOptionCode::COOKIE);
- BOOST_REQUIRE(ecsOption != dq.ednsOptions->cend());
-
- BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U);
- BOOST_CHECK_EQUAL(cookiesOptionStr, std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
-}
-
-BOOST_AUTO_TEST_SUITE_END();
+++ /dev/null
-#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_NO_MAIN
-
-#include <boost/test/unit_test.hpp>
-
-#include "ednscookies.hh"
-#include "ednsoptions.hh"
-#include "ednssubnet.hh"
-#include "dnsdist.hh"
-#include "iputils.hh"
-#include "dnswriter.hh"
-#include "dnsdist-cache.hh"
-#include "gettime.hh"
-#include "packetcache.hh"
-
-BOOST_AUTO_TEST_SUITE(test_dnsdistpacketcache_cc)
-
-static bool receivedOverUDP = true;
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, 86400, 1);
- BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
- size_t counter = 0;
- size_t skipped = 0;
- bool dnssecOK = false;
- const time_t now = time(nullptr);
- InternalQueryState ids;
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- try {
- for (counter = 0; counter < 100000; ++counter) {
- auto a = DNSName(std::to_string(counter))+DNSName(" hello");
- ids.qname = a;
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.startRecord(a, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- pwR.xfr32BitInt(0x01020304);
- pwR.commit();
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
-
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- if (found == true) {
- BOOST_CHECK_EQUAL(dq.getData().size(), response.size());
- int match = memcmp(dq.getData().data(), response.data(), dq.getData().size());
- BOOST_CHECK_EQUAL(match, 0);
- BOOST_CHECK(!subnet);
- }
- else {
- skipped++;
- }
- }
-
- BOOST_CHECK_EQUAL(skipped, PC.getInsertCollisions());
- BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped);
-
- size_t deleted=0;
- size_t delcounter=0;
- for (delcounter=0; delcounter < counter/1000; ++delcounter) {
- ids.qname = DNSName(std::to_string(delcounter))+DNSName(" hello");
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- if (found == true) {
- auto removed = PC.expungeByName(ids.qname);
- BOOST_CHECK_EQUAL(removed, 1U);
- deleted += removed;
- }
- }
- BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped - deleted);
-
- size_t matches=0;
- size_t expected=counter-skipped-deleted;
- for (; delcounter < counter; ++delcounter) {
- ids.qname = DNSName(std::to_string(delcounter))+DNSName(" hello");
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- if (PC.get(dq, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
- matches++;
- }
- }
-
- /* in the unlikely event that the test took so long that the entries did expire.. */
- auto expired = PC.purgeExpired(0, now);
- BOOST_CHECK_EQUAL(matches + expired, expected);
-
- auto remaining = PC.getSize();
- auto removed = PC.expungeByName(DNSName(" hello"), QType::ANY, true);
- BOOST_CHECK_EQUAL(PC.getSize(), 0U);
- BOOST_CHECK_EQUAL(removed, remaining);
-
- /* nothing to remove */
- BOOST_CHECK_EQUAL(PC.purgeExpired(0, now), 0U);
- }
- catch (const PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheSharded) {
- const size_t maxEntries = 150000;
- const size_t numberOfShards = 10;
- DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, numberOfShards);
- BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
- size_t counter = 0;
- size_t skipped = 0;
- ComboAddress remote;
- bool dnssecOK = false;
- const time_t now = time(nullptr);
- InternalQueryState ids;
- ids.qtype = QType::AAAA;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- try {
- for (counter = 0; counter < 100000; ++counter) {
- ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- ComboAddress v6("2001:db8::1");
- pwR.xfrIP6(std::string(reinterpret_cast<const char*>(v6.sin6.sin6_addr.s6_addr), 16));
- pwR.commit();
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, QType::AAAA, QClass::IN, response, receivedOverUDP, 0, boost::none);
-
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- if (found == true) {
- BOOST_CHECK_EQUAL(dq.getData().size(), response.size());
- int match = memcmp(dq.getData().data(), response.data(), dq.getData().size());
- BOOST_CHECK_EQUAL(match, 0);
- BOOST_CHECK(!subnet);
- }
- else {
- skipped++;
- }
- }
-
- BOOST_CHECK_EQUAL(skipped, PC.getInsertCollisions());
- BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped);
-
- size_t matches = 0;
- for (counter = 0; counter < 100000; ++counter) {
- ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- if (PC.get(dq, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
- matches++;
- }
- }
-
- BOOST_CHECK_EQUAL(matches, counter - skipped);
-
- auto remaining = PC.getSize();
-
- /* no entry should have expired */
- auto expired = PC.purgeExpired(0, now);
- BOOST_CHECK_EQUAL(expired, 0U);
-
- /* but after the TTL .. let's ask for at most 1k entries */
- auto removed = PC.purgeExpired(1000, now + 7200 + 3600);
- BOOST_CHECK_EQUAL(removed, remaining - 1000U);
- BOOST_CHECK_EQUAL(PC.getSize(), 1000U);
-
- /* now remove everything */
- removed = PC.purgeExpired(0, now + 7200 + 3600);
- BOOST_CHECK_EQUAL(removed, 1000U);
- BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
- /* nothing to remove */
- BOOST_CHECK_EQUAL(PC.purgeExpired(0, now), 0U);
- }
- catch (const PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheTCP) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, 86400, 1);
- InternalQueryState ids;
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- ComboAddress remote;
- bool dnssecOK = false;
- try {
- DNSName a("tcp");
- ids.qname = a;
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::AAAA, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::AAAA, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.startRecord(a, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- ComboAddress v6("2001:db8::1");
- pwR.xfrIP6(std::string(reinterpret_cast<const char*>(v6.sin6.sin6_addr.s6_addr), 16));
- pwR.commit();
-
- {
- /* UDP */
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, true);
- BOOST_CHECK(!subnet);
- }
-
- {
- /* same but over TCP */
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- ids.protocol = dnsdist::Protocol::DoTCP;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, !receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, true);
- BOOST_CHECK(!subnet);
- }
- }
- catch(PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, 86400, 1);
- InternalQueryState ids;
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- ComboAddress remote;
- bool dnssecOK = false;
- try {
- DNSName a = DNSName("servfail");
- ids.qname = a;
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 0;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->rcode = RCode::ServFail;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.commit();
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- // Insert with failure-TTL of 0 (-> should not enter cache).
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(0));
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- // Insert with failure-TTL non-zero (-> should enter cache).
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(300));
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, true);
- BOOST_CHECK(!subnet);
- }
- catch(PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
-
- ComboAddress remote;
- bool dnssecOK = false;
- InternalQueryState ids;
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- try {
- DNSName name("nodata");
- ids.qname = name;
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 0;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->rcode = RCode::NoError;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.commit();
- pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
- pwR.commit();
- pwR.addOpt(4096, 0, 0);
- pwR.commit();
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, true);
- BOOST_CHECK(!subnet);
-
- sleep(2);
- /* it should have expired by now */
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
- }
- catch(const PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
-
- InternalQueryState ids;
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- ComboAddress remote;
- bool dnssecOK = false;
- try {
- DNSName name("nxdomain");
- ids.qname = name;
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 0;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->rcode = RCode::NXDomain;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.commit();
- pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
- pwR.commit();
- pwR.addOpt(4096, 0, 0);
- pwR.commit();
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, true);
- BOOST_CHECK(!subnet);
-
- sleep(2);
- /* it should have expired by now */
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
- }
- catch(const PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheTruncated) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
-
- InternalQueryState ids;
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
- ids.queryRealTime.start(); // does not have to be accurate ("realTime") in tests
- bool dnssecOK = false;
-
- try {
- ids.qname = DNSName("truncated");
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 0;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->tc = 1;
- pwR.getHeader()->rcode = RCode::NoError;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.commit();
- pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- pwR.xfr32BitInt(0x01020304);
- pwR.commit();
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
-
- bool allowTruncated = true;
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
- BOOST_CHECK_EQUAL(found, true);
- BOOST_CHECK(!subnet);
-
- allowTruncated = false;
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
- BOOST_CHECK_EQUAL(found, false);
-}
- catch(const PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-}
-
-static DNSDistPacketCache g_PC(500000);
-
-static void threadMangler(unsigned int offset)
-{
- InternalQueryState ids;
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
-
- try {
- ComboAddress remote;
- bool dnssecOK = false;
- for(unsigned int counter=0; counter < 100000; ++counter) {
- ids.qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.startRecord(ids.qname, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
- pwR.xfr32BitInt(0x01020304);
- pwR.commit();
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- g_PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-
- g_PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
- }
- }
- catch(PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-}
-
-AtomicCounter g_missing;
-
-static void threadReader(unsigned int offset)
-{
- InternalQueryState ids;
- ids.qtype = QType::A;
- ids.qclass = QClass::IN;
- ids.qname = DNSName("www.powerdns.com.");
- ids.protocol = dnsdist::Protocol::DoUDP;
- bool dnssecOK = false;
- try
- {
- ComboAddress remote;
- for(unsigned int counter=0; counter < 100000; ++counter) {
- ids.qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = g_PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- if (!found) {
- g_missing++;
- }
- }
- }
- catch(PDNSException& e) {
- cerr<<"Had error in threadReader: "<<e.reason<<endl;
- throw;
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
- try {
- std::vector<std::thread> threads;
- for (int i = 0; i < 4; ++i) {
- threads.push_back(std::thread(threadMangler, i*1000000UL));
- }
-
- for (auto& t : threads) {
- t.join();
- }
-
- threads.clear();
-
- BOOST_CHECK_EQUAL(g_PC.getSize() + g_PC.getDeferredInserts() + g_PC.getInsertCollisions(), 400000U);
- BOOST_CHECK_SMALL(1.0*g_PC.getInsertCollisions(), 10000.0);
-
- for (int i = 0; i < 4; ++i) {
- threads.push_back(std::thread(threadReader, i*1000000UL));
- }
-
- for (auto& t : threads) {
- t.join();
- }
-
- BOOST_CHECK((g_PC.getDeferredInserts() + g_PC.getDeferredLookups() + g_PC.getInsertCollisions()) >= g_missing);
- }
- catch(PDNSException& e) {
- cerr<<"Had error: "<<e.reason<<endl;
- throw;
- }
-
-}
-
-BOOST_AUTO_TEST_CASE(test_PCCollision) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
- BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
- InternalQueryState ids;
- ids.qtype = QType::AAAA;
- ids.qclass = QClass::IN;
- ids.qname = DNSName("www.powerdns.com.");
- ids.protocol = dnsdist::Protocol::DoUDP;
- uint16_t qid = 0x42;
- uint32_t key;
- uint32_t secondKey;
- boost::optional<Netmask> subnetOut;
- bool dnssecOK = false;
-
- /* lookup for a query with a first ECS value,
- insert a corresponding response */
- {
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
- pwQ.getHeader()->id = qid;
- GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
- EDNSSubnetOpts opt;
- opt.source = Netmask("10.0.59.220/32");
- ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
- pwQ.addOpt(512, 0, 0, ednsOptions);
- pwQ.commit();
-
- ComboAddress remote("192.0.2.1");
- ids.queryRealTime.start();
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_REQUIRE(subnetOut);
- BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->id = qid;
- pwR.startRecord(ids.qname, ids.qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
- ComboAddress v6("::1");
- pwR.xfrCAWithoutPort(6, v6);
- pwR.commit();
- pwR.addOpt(512, 0, 0, ednsOptions);
- pwR.commit();
-
- PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), dnssecOK, ids.qname, ids.qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
- BOOST_CHECK_EQUAL(PC.getSize(), 1U);
-
- found = PC.get(dq, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, true);
- BOOST_REQUIRE(subnetOut);
- BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
- }
-
- /* now lookup for the same query with a different ECS value,
- we should get the same key (collision) but no match */
- {
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
- pwQ.getHeader()->id = qid;
- GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
- EDNSSubnetOpts opt;
- opt.source = Netmask("10.0.167.48/32");
- ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
- pwQ.addOpt(512, 0, 0, ednsOptions);
- pwQ.commit();
-
- ComboAddress remote("192.0.2.1");
- ids.queryRealTime.start();
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &secondKey, subnetOut, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK_EQUAL(secondKey, key);
- BOOST_REQUIRE(subnetOut);
- BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
- BOOST_CHECK_EQUAL(PC.getLookupCollisions(), 1U);
- }
-
-#if 0
- /* to be able to compute a new collision if the packet cache hashing code is updated */
- {
- DNSDistPacketCache pc(10000);
- GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
- EDNSSubnetOpts opt;
- std::map<uint32_t, Netmask> colMap;
- size_t collisions = 0;
- size_t total = 0;
- //qname = DNSName("collision-with-ecs-parsing.cache.tests.powerdns.com.");
-
- for (size_t idxA = 0; idxA < 256; idxA++) {
- for (size_t idxB = 0; idxB < 256; idxB++) {
- for (size_t idxC = 0; idxC < 256; idxC++) {
- PacketBuffer secondQuery;
- GenericDNSPacketWriter<PacketBuffer> pwFQ(secondQuery, ids.qname, QType::AAAA, QClass::IN, 0);
- pwFQ.getHeader()->rd = 1;
- pwFQ.getHeader()->qr = false;
- pwFQ.getHeader()->id = 0x42;
- opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
- ednsOptions.clear();
- ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
- pwFQ.addOpt(512, 0, 0, ednsOptions);
- pwFQ.commit();
- secondKey = pc.getKey(ids.qname.toDNSString(), ids.qname.wirelength(), secondQuery, false);
- auto pair = colMap.emplace(secondKey, opt.source);
- total++;
- if (!pair.second) {
- collisions++;
- cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
- goto done;
- }
- }
- }
- }
- done:
- cerr<<"collisions: "<<collisions<<endl;
- cerr<<"total: "<<total<<endl;
- }
-#endif
-}
-
-BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
- BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
- InternalQueryState ids;
- ids.qtype = QType::AAAA;
- ids.qclass = QClass::IN;
- ids.qname = DNSName("www.powerdns.com.");
- ids.protocol = dnsdist::Protocol::DoUDP;
- uint16_t qid = 0x42;
- uint32_t key;
- boost::optional<Netmask> subnetOut;
-
- /* lookup for a query with DNSSEC OK,
- insert a corresponding response with DO set,
- check that it doesn't match without DO, but does with it */
- {
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
- pwQ.getHeader()->id = qid;
- pwQ.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
- pwQ.commit();
-
- ComboAddress remote("192.0.2.1");
- ids.queryRealTime.start();
- ids.origRemote = remote;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnetOut, true, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->id = qid;
- pwR.startRecord(ids.qname, ids.qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
- ComboAddress v6("::1");
- pwR.xfrCAWithoutPort(6, v6);
- pwR.commit();
- pwR.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
- pwR.commit();
-
- PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), /* DNSSEC OK is set */ true, ids.qname, ids.qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
- BOOST_CHECK_EQUAL(PC.getSize(), 1U);
-
- found = PC.get(dq, 0, &key, subnetOut, false, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
-
- found = PC.get(dq, 0, &key, subnetOut, true, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, true);
- }
-
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheInspection) {
- const size_t maxEntries = 100;
- DNSDistPacketCache PC(maxEntries, 86400, 1);
- BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
- ComboAddress remote;
- bool dnssecOK = false;
-
- uint32_t key = 0;
-
- /* insert powerdns.com A 192.0.2.1, 192.0.2.2 */
- {
- DNSName qname("powerdns.com");
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- {
- ComboAddress addr("192.0.2.1");
- pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- pwR.xfrCAWithoutPort(4, addr);
- pwR.commit();
- }
- {
- ComboAddress addr("192.0.2.2");
- pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- pwR.xfrCAWithoutPort(4, addr);
- pwR.commit();
- }
-
- PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
- BOOST_CHECK_EQUAL(PC.getSize(), key);
- }
-
- /* insert powerdns1.com A 192.0.2.3, 192.0.2.4, AAAA 2001:db8::3, 2001:db8::4 */
- {
- DNSName qname("powerdns1.com");
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- {
- ComboAddress addr("192.0.2.3");
- pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- pwR.xfrCAWithoutPort(4, addr);
- pwR.commit();
- }
- {
- ComboAddress addr("192.0.2.4");
- pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- pwR.xfrCAWithoutPort(4, addr);
- pwR.commit();
- }
- {
- ComboAddress addr("2001:db8::3");
- pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
- pwR.xfrCAWithoutPort(6, addr);
- pwR.commit();
- }
- {
- ComboAddress addr("2001:db8::4");
- pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
- pwR.xfrCAWithoutPort(6, addr);
- pwR.commit();
- }
-
- PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
- BOOST_CHECK_EQUAL(PC.getSize(), key);
- }
-
- /* insert powerdns2.com NODATA */
- {
- DNSName qname("powerdns2.com");
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.commit();
- pwR.startRecord(qname, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
- pwR.commit();
- pwR.addOpt(4096, 0, 0);
- pwR.commit();
-
- PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
- BOOST_CHECK_EQUAL(PC.getSize(), key);
- }
-
- /* insert powerdns3.com AAAA 2001:db8::4, 2001:db8::5 */
- {
- DNSName qname("powerdns3.com");
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- {
- ComboAddress addr("2001:db8::4");
- pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
- pwR.xfrCAWithoutPort(6, addr);
- pwR.commit();
- }
- {
- ComboAddress addr("2001:db8::5");
- pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
- pwR.xfrCAWithoutPort(6, addr);
- pwR.commit();
- }
-
- PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
- BOOST_CHECK_EQUAL(PC.getSize(), key);
- }
-
- /* insert powerdns4.com A 192.0.2.1 */
- {
- DNSName qname("powerdns4.com");
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- {
- ComboAddress addr("192.0.2.1");
- pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
- pwR.xfrCAWithoutPort(4, addr);
- pwR.commit();
- }
-
- PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
- BOOST_CHECK_EQUAL(PC.getSize(), key);
- }
-
- {
- auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.1"));
- BOOST_CHECK_EQUAL(domains.size(), 2U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns4.com")), 1U);
- }
- {
- auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.2"));
- BOOST_CHECK_EQUAL(domains.size(), 1U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
- }
- {
- auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.3"));
- BOOST_CHECK_EQUAL(domains.size(), 1U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
- }
- {
- auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.4"));
- BOOST_CHECK_EQUAL(domains.size(), 1U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
- }
- {
- auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.5"));
- BOOST_CHECK_EQUAL(domains.size(), 0U);
- }
- {
- auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::3"));
- BOOST_CHECK_EQUAL(domains.size(), 1U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
- }
- {
- auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::4"));
- BOOST_CHECK_EQUAL(domains.size(), 2U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
- }
- {
- auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::5"));
- BOOST_CHECK_EQUAL(domains.size(), 1U);
- BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
- }
-
- {
- auto records = PC.getRecordsForDomain(DNSName("powerdns.com"));
- BOOST_CHECK_EQUAL(records.size(), 2U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.2")), 1U);
- }
-
- {
- auto records = PC.getRecordsForDomain(DNSName("powerdns1.com"));
- BOOST_CHECK_EQUAL(records.size(), 4U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.3")), 1U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.4")), 1U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::3")), 1U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
- }
-
- {
- auto records = PC.getRecordsForDomain(DNSName("powerdns2.com"));
- BOOST_CHECK_EQUAL(records.size(), 0U);
- }
-
- {
- auto records = PC.getRecordsForDomain(DNSName("powerdns3.com"));
- BOOST_CHECK_EQUAL(records.size(), 2U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
- }
-
- {
- auto records = PC.getRecordsForDomain(DNSName("powerdns4.com"));
- BOOST_CHECK_EQUAL(records.size(), 1U);
- BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
- }
-
- {
- auto records = PC.getRecordsForDomain(DNSName("powerdns5.com"));
- BOOST_CHECK_EQUAL(records.size(), 0U);
- }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheXFR) {
- const size_t maxEntries = 150000;
- DNSDistPacketCache PC(maxEntries, 86400, 1);
- BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
- const std::set<QType> xfrTypes = { QType::AXFR, QType::IXFR };
- for (const auto& type : xfrTypes) {
- bool dnssecOK = false;
- InternalQueryState ids;
- ids.qtype = type;
- ids.qclass = QClass::IN;
- ids.protocol = dnsdist::Protocol::DoUDP;
- ids.qname = DNSName("powerdns.com.");
-
- PacketBuffer query;
- GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, ids.qclass, 0);
- pwQ.getHeader()->rd = 1;
-
- PacketBuffer response;
- GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, ids.qclass, 0);
- pwR.getHeader()->rd = 1;
- pwR.getHeader()->ra = 1;
- pwR.getHeader()->qr = 1;
- pwR.getHeader()->id = pwQ.getHeader()->id;
- pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
- pwR.xfr32BitInt(0x01020304);
- pwR.commit();
-
- uint32_t key = 0;
- boost::optional<Netmask> subnet;
- DNSQuestion dq(ids, query);
- bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
- BOOST_CHECK_EQUAL(found, false);
- BOOST_CHECK(!subnet);
-
- PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, ids.qtype, ids.qclass, response, receivedOverUDP, 0, boost::none);
- found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
- BOOST_CHECK_EQUAL(found, false);
- }
-}
-
-BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#include <boost/test/unit_test.hpp>
#include <cmath>
BOOST_AUTO_TEST_CASE(test_basic) {
DNSName aroot("a.root-servers.net"), broot("b.root-servers.net");
BOOST_CHECK(aroot < broot);
- BOOST_CHECK(!(broot < aroot));
+ BOOST_CHECK(!(broot < aroot));
BOOST_CHECK(aroot.canonCompare(broot));
- BOOST_CHECK(!broot.canonCompare(aroot));
-
+ BOOST_CHECK(!broot.canonCompare(aroot));
+
string before("www.ds9a.nl.");
DNSName b(before);
DNSName name;
BOOST_CHECK(name.empty());
}
-
+
{ // empty() root
DNSName name(".");
BOOST_CHECK(!name.empty());
-
+
DNSName rootnodot("");
BOOST_CHECK_EQUAL(name, rootnodot);
-
+
string empty;
DNSName rootnodot2(empty);
BOOST_CHECK_EQUAL(rootnodot2, name);
left.appendRawLabel("com");
BOOST_CHECK( left == DNSName("WwW.Ds9A.Nl.com."));
-
+
DNSName unset;
unset.appendRawLabel("www");
BOOST_CHECK(!empty.isWildcard());
BOOST_CHECK_EQUAL(empty, empty);
BOOST_CHECK(!(empty < empty));
-
+
DNSName root(".");
BOOST_CHECK(empty < root);
BOOST_AUTO_TEST_CASE(test_specials) {
DNSName root(".");
-
+
BOOST_CHECK(root.isRoot());
BOOST_CHECK(root != DNSName());
BOOST_AUTO_TEST_CASE(test_Append) {
DNSName dn("www."), powerdns("powerdns.com.");
DNSName tot=dn+powerdns;
-
+
BOOST_CHECK_EQUAL(tot.toString(), "www.powerdns.com.");
BOOST_CHECK(tot == DNSName("www.powerdns.com."));
aaaa.toPacket(dpw);
dpw.commit();
string str((const char*)&packet[0], (const char*)&packet[0] + packet.size());
- size_t pos = 0;
+ size_t pos = 0;
int count=0;
while((pos = str.find("ds9a", pos)) != string::npos) {
++pos;
++count;
}
BOOST_CHECK_EQUAL(count, 1);
- pos = 0;
+ pos = 0;
count=0;
while((pos = str.find("powerdns", pos)) != string::npos) {
++pos;
dpw.commit();
DNSName roundtrip((char*)&packet[0], packet.size(), 12, false);
BOOST_CHECK_EQUAL(loopback,roundtrip);
-
+
packet.clear();
DNSName longer("1.2.3.4.5.6.7.8.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa");
DNSPacketWriter dpw2(packet, longer, QType::PTR);
BOOST_AUTO_TEST_CASE(test_hash) {
DNSName a("wwW.Ds9A.Nl"), b("www.ds9a.nl");
BOOST_CHECK_EQUAL(a.hash(), b.hash());
-
+
vector<uint32_t> counts(1500);
-
+
for(unsigned int n=0; n < 100000; ++n) {
DNSName dn(std::to_string(n)+"."+std::to_string(n*2)+"ds9a.nl");
DNSName dn2(std::to_string(n)+"."+std::to_string(n*2)+"Ds9a.nL");
BOOST_CHECK_EQUAL(dn.hash(), dn2.hash());
counts[dn.hash() % counts.size()]++;
}
-
+
double sum = std::accumulate(std::begin(counts), std::end(counts), 0.0);
double m = sum / counts.size();
-
+
double accum = 0.0;
std::for_each (std::begin(counts), std::end(counts), [&](const double d) {
accum += (d - m) * (d - m);
});
-
+
double stdev = sqrt(accum / (counts.size()-1));
- BOOST_CHECK(stdev < 10);
+ BOOST_CHECK(stdev < 10);
}
BOOST_AUTO_TEST_CASE(test_hashContainer) {
vector<unsigned char> packet;
reportBasicTypes();
DNSPacketWriter dpw(packet, DNSName("www.ds9a.nl."), QType::AAAA);
-
+
uint16_t qtype, qclass;
DNSName dn((char*)&packet[0], packet.size(), 12, false, &qtype, &qclass);
BOOST_CHECK_EQUAL(dn.toString(), "www.ds9a.nl.");
content name */
DNSName dn2((char*)&packet[0], packet.size(), 12+13+4, true, &qtype, &qclass);
- BOOST_CHECK_EQUAL(dn2.toString(), "ds9a.nl.");
+ BOOST_CHECK_EQUAL(dn2.toString(), "ds9a.nl.");
BOOST_CHECK(qtype == QType::NS);
BOOST_CHECK_EQUAL(qclass, 1);
DNSName dn3((char*)&packet[0], packet.size(), 12+13+4+2 + 4 + 4 + 2, true);
- BOOST_CHECK_EQUAL(dn3.toString(), "ns1.powerdns.com.");
+ BOOST_CHECK_EQUAL(dn3.toString(), "ns1.powerdns.com.");
try {
DNSName dn4((char*)&packet[0], packet.size(), 12+13+4, false); // compressed, should fail
- BOOST_CHECK(0);
+ BOOST_CHECK(0);
}
catch(...){}
}
vector<DNSName> vec;
for(const char* b : {"bert.com.", "alpha.nl.", "articles.xxx.",
- "Aleph1.powerdns.com.", "ZOMG.powerdns.com.", "aaa.XXX.", "yyy.XXX.",
+ "Aleph1.powerdns.com.", "ZOMG.powerdns.com.", "aaa.XXX.", "yyy.XXX.",
"test.powerdns.com.", "\\128.com"}) {
vec.push_back(DNSName(b));
}
"yyy.XXX."})
right.push_back(DNSName(b));
-
+
BOOST_CHECK(vec==right);
}
BOOST_CHECK_EQUAL(name3.getCommonLabels(name4), name3);
BOOST_CHECK_EQUAL(name4.getCommonLabels(name3), name4);
- const DNSName(name5);
+ const DNSName name5;
BOOST_CHECK_EQUAL(name1.getCommonLabels(name5), DNSName());
BOOST_CHECK_EQUAL(name5.getCommonLabels(name1), DNSName());
}
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
// MX is unsafe, but we asked to remove it
BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 1, QType::A), 1);
BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 1, QType::AAAA), 0);
- BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 3, QType::A), 1);
+ BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 3, QType::A), 1);
BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 3, QType::MX), 0);
}
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
BOOST_CHECK(aaaa == aaaa1);
-
- auto rec1=DNSRecordContent::mastermake(QType::A, 1, "192.168.0.1");
- auto rec2=DNSRecordContent::mastermake(QType::A, 1, "192.168.222.222");
- auto rec3=DNSRecordContent::mastermake(QType::AAAA, 1, "::1");
- auto recMX=DNSRecordContent::mastermake(QType::MX, 1, "25 smtp.powerdns.com");
- auto recMX2=DNSRecordContent::mastermake(QType::MX, 1, "26 smtp.powerdns.com");
- auto recMX3=DNSRecordContent::mastermake(QType::MX, 1, "26 SMTP.powerdns.com");
+ auto rec1 = DNSRecordContent::make(QType::A, 1, "192.168.0.1");
+ auto rec2 = DNSRecordContent::make(QType::A, 1, "192.168.222.222");
+ auto rec3 = DNSRecordContent::make(QType::AAAA, 1, "::1");
+ auto recMX = DNSRecordContent::make(QType::MX, 1, "25 smtp.powerdns.com");
+ auto recMX2 = DNSRecordContent::make(QType::MX, 1, "26 smtp.powerdns.com");
+ auto recMX3 = DNSRecordContent::make(QType::MX, 1, "26 SMTP.powerdns.com");
BOOST_CHECK(!(*rec1==*rec2));
BOOST_CHECK(*rec1==*rec1);
BOOST_CHECK(*rec3==*rec3);
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
(CASE_S(QType::URI, "10000 1 \"ftp://ftp1.example.com/public\"", "\x27\x10\x00\x01\x66\x74\x70\x3a\x2f\x2f\x66\x74\x70\x31\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x70\x75\x62\x6c\x69\x63"))
(CASE_S(QType::URI, "10 1 \"ftp://ftp1.example.com/public/with/a/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/long/url\"", "\x00\x0a\x00\x01\x66\x74\x70\x3a\x2f\x2f\x66\x74\x70\x31\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x70\x75\x62\x6c\x69\x63\x2f\x77\x69\x74\x68\x2f\x61\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x6c\x6f\x6e\x67\x2f\x75\x72\x6c"))
(CASE_S(QType::CAA, "0 issue \"example.net\"", "\x00\x05\x69\x73\x73\x75\x65\x65\x78\x61\x6d\x70\x6c\x65\x2e\x6e\x65\x74"))
+ (CASE_S(QType::CAA, "0 issue \"\"", "\x00\x05\x69\x73\x73\x75\x65"))
+ (CASE_S(QType::CAA, "0 issue \";\"", "\x00\x05\x69\x73\x73\x75\x65\x3b"))
+ (CASE_S(QType::CAA, "0 issue \"a\"", "\x00\x05\x69\x73\x73\x75\x65\x61"))
+ (CASE_S(QType::CAA, "0 issue \"aa\"", "\x00\x05\x69\x73\x73\x75\x65\x61\x61"))
+ (CASE_S(QType::CAA, "0 issue \"aaaaaaa\"", "\x00\x05\x69\x73\x73\x75\x65\x61\x61\x61\x61\x61\x61\x61"))
+ (CASE_S(QType::CAA, "0 issue \"aaaaaaa.aaa\"", "\x00\x05\x69\x73\x73\x75\x65\x61\x61\x61\x61\x61\x61\x61\x2e\x61\x61\x61"))
(CASE_S(QType::DLV, "20642 8 2 04443abe7e94c3985196beae5d548c727b044dda5151e60d7cd76a9fd931d00e", "\x50\xa2\x08\x02\x04\x44\x3a\xbe\x7e\x94\xc3\x98\x51\x96\xbe\xae\x5d\x54\x8c\x72\x7b\x04\x4d\xda\x51\x51\xe6\x0d\x7c\xd7\x6a\x9f\xd9\x31\xd0\x0e"))
(CASE_S((QType::typeenum)65226,"\\# 3 414243","\x41\x42\x43"))
BOOST_TEST_MESSAGE("Checking record type " << q.toString() << " test #" << n);
try {
std::string recData;
- auto rec = DNSRecordContent::mastermake(q.getCode(), 1, inval);
- BOOST_CHECK_MESSAGE(rec != NULL, "mastermake( " << q.getCode() << ", 1, " << inval << ") should not return NULL");
+ auto rec = DNSRecordContent::make(q.getCode(), 1, inval);
+ BOOST_CHECK_MESSAGE(rec != NULL, "make( " << q.getCode() << ", 1, " << inval << ") should not return NULL");
if (rec == NULL) continue;
// now verify the record (note that this will be same as *zone* value (except for certain QTypes)
bool success=true;
BOOST_WARN_EXCEPTION(
{
- auto drc = DNSRecordContent::mastermake(q.getCode(), 1, input);
+ auto drc = DNSRecordContent::make(q.getCode(), 1, input);
pw.startRecord(DNSName("unit.test"), q.getCode());
drc->toPacket(pw);
success=false;
},
- std::exception, test_dnsrecords_cc_predicate
- );
+ std::exception, test_dnsrecords_cc_predicate);
if (success) REC_FAIL_XSUCCESS(q.toString() << " test #" << n << " has unexpectedly passed"); // a bad record was detected when it was supposed not to be detected
} else {
BOOST_CHECK_EXCEPTION(
{
- auto drc = DNSRecordContent::mastermake(q.getCode(), 1, input);
+ auto drc = DNSRecordContent::make(q.getCode(), 1, input);
pw.startRecord(DNSName("unit.test"), q.getCode());
drc->toPacket(pw);
},
- std::exception, test_dnsrecords_cc_predicate
- );
+ std::exception, test_dnsrecords_cc_predicate);
}
};
}
// special record test, because Unknown record types are the worst
BOOST_AUTO_TEST_CASE(test_unknown_records_in) {
- auto validUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 42");
+ auto validUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 42");
// we need at least two parts
- BOOST_CHECK_THROW(auto notEnoughPartsUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\#"), MOADNSException);
+ BOOST_CHECK_THROW(auto notEnoughPartsUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\#"), MOADNSException);
// two parts are OK when the RDATA size is 0, not OK otherwise
- auto validEmptyUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 0");
- BOOST_CHECK_THROW(auto twoPartsNotZeroUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1"), MOADNSException);
+ auto validEmptyUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 0");
+ BOOST_CHECK_THROW(auto twoPartsNotZeroUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1"), MOADNSException);
// the first part has to be "\#"
- BOOST_CHECK_THROW(auto invalidFirstPartUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\$ 0"), MOADNSException);
+ BOOST_CHECK_THROW(auto invalidFirstPartUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\$ 0"), MOADNSException);
// RDATA length is not even
- BOOST_CHECK_THROW(auto unevenUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 A"), MOADNSException);
+ BOOST_CHECK_THROW(auto unevenUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 A"), MOADNSException);
// RDATA length is not equal to the expected size
- BOOST_CHECK_THROW(auto wrongRDATASizeUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 2 AA"), MOADNSException);
+ BOOST_CHECK_THROW(auto wrongRDATASizeUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 2 AA"), MOADNSException);
// RDATA is invalid (invalid hex value)
try {
- auto invalidRDATAUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 JJ");
+ auto invalidRDATAUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 JJ");
// we should not reach that code
BOOST_CHECK(false);
// but if we do let's see what we got (likely what was left over on the stack)
// test that we reject invalid SVCB escaping
BOOST_AUTO_TEST_CASE(test_svcb_records_in) {
- BOOST_CHECK_THROW(auto invalidSVCB1=DNSRecordContent::mastermake(QType::SVCB, QClass::IN, R"FOO(1 . alpn=foo\\)FOO"), std::runtime_error);
-
+ BOOST_CHECK_THROW(auto invalidSVCB1 = DNSRecordContent::make(QType::SVCB, QClass::IN, R"FOO(1 . alpn=foo\\)FOO"), std::runtime_error);
}
// special record test, because EUI are odd
BOOST_AUTO_TEST_CASE(test_eui_records_in) {
- auto validEUI48=DNSRecordContent::mastermake(QType::EUI48, QClass::IN, "00-00-5e-00-53-2a");
+ auto validEUI48 = DNSRecordContent::make(QType::EUI48, QClass::IN, "00-00-5e-00-53-2a");
- BOOST_CHECK_THROW(auto invalidEUI48=DNSRecordContent::mastermake(QType::EUI48, QClass::IN, "00-00-5e-00-53-"), MOADNSException);
+ BOOST_CHECK_THROW(auto invalidEUI48 = DNSRecordContent::make(QType::EUI48, QClass::IN, "00-00-5e-00-53-"), MOADNSException);
- auto validEUI64=DNSRecordContent::mastermake(QType::EUI64, QClass::IN, "00-00-5e-ef-10-00-00-2a");
+ auto validEUI64 = DNSRecordContent::make(QType::EUI64, QClass::IN, "00-00-5e-ef-10-00-00-2a");
- BOOST_CHECK_THROW(auto invalidEUI64=DNSRecordContent::mastermake(QType::EUI64, QClass::IN, "00-00-5e-ef-10-00-00-"), MOADNSException);
+ BOOST_CHECK_THROW(auto invalidEUI64 = DNSRecordContent::make(QType::EUI64, QClass::IN, "00-00-5e-ef-10-00-00-"), MOADNSException);
}
// special record test, because LOC is weird
BOOST_AUTO_TEST_CASE(test_loc_records_in) {
- auto validLOC=DNSRecordContent::mastermake(QType::LOC, QClass::IN, "52 22 23.000 N 4 53 32.000 E -2.00m 0.00m 10000m 10m");
+ auto validLOC = DNSRecordContent::make(QType::LOC, QClass::IN, "52 22 23.000 N 4 53 32.000 E -2.00m 0.00m 10000m 10m");
- BOOST_CHECK_THROW(auto invalidLOC=DNSRecordContent::mastermake(QType::LOC, QClass::IN, "52 22 23.000 N"), MOADNSException);
+ BOOST_CHECK_THROW(auto invalidLOC = DNSRecordContent::make(QType::LOC, QClass::IN, "52 22 23.000 N"), MOADNSException);
vector<uint8_t> packet;
DNSPacketWriter writer(packet, DNSName("powerdns.com."), QType::LOC, QClass::IN, 0);
BOOST_AUTO_TEST_CASE(test_nsec_records_in) {
{
- auto validNSEC=DNSRecordContent::mastermake(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC TYPE1234");
+ auto validNSEC = DNSRecordContent::make(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC TYPE1234");
vector<uint8_t> packet;
DNSPacketWriter writer(packet, DNSName("powerdns.com."), QType::NSEC, QClass::IN, 0);
}
{
- auto validNSEC3=DNSRecordContent::mastermake(QType::NSEC3, QClass::IN, "1 1 12 aabbccdd 2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG");
+ auto validNSEC3 = DNSRecordContent::make(QType::NSEC3, QClass::IN, "1 1 12 aabbccdd 2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG");
vector<uint8_t> packet;
DNSPacketWriter writer(packet, DNSName("powerdns.com."), QType::NSEC3, QClass::IN, 0);
}
{
- auto validNSEC3PARAM=DNSRecordContent::mastermake(QType::NSEC3PARAM, QClass::IN, "1 0 12 aabbccdd");
+ auto validNSEC3PARAM = DNSRecordContent::make(QType::NSEC3PARAM, QClass::IN, "1 0 12 aabbccdd");
vector<uint8_t> packet;
DNSPacketWriter writer(packet, DNSName("powerdns.com."), QType::NSEC3PARAM, QClass::IN, 0);
BOOST_AUTO_TEST_CASE(test_nsec_records_types) {
{
- auto validNSEC = DNSRecordContent::mastermake(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC TYPE1234");
+ auto validNSEC = DNSRecordContent::make(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC TYPE1234");
auto nsecContent = std::dynamic_pointer_cast<NSECRecordContent>(validNSEC);
BOOST_REQUIRE(nsecContent);
}
BOOST_AUTO_TEST_CASE(test_nsec_invalid_bitmap_len) {
- auto validNSEC = DNSRecordContent::mastermake(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC AAAA NSEC3 TYPE1234 TYPE65535");
+ auto validNSEC = DNSRecordContent::make(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC AAAA NSEC3 TYPE1234 TYPE65535");
const DNSName powerdnsName("powerdns.com.");
vector<uint8_t> packet;
{
const std::string str = "1 1 12 aabbccdd 2vptu5timamqttgl4luu9kg21e0aor3s a mx rrsig nsec3 type1234 type65535";
- auto validNSEC3 = DNSRecordContent::mastermake(QType::NSEC3, QClass::IN, str);
+ auto validNSEC3 = DNSRecordContent::make(QType::NSEC3, QClass::IN, str);
auto nsec3Content = std::dynamic_pointer_cast<NSEC3RecordContent>(validNSEC3);
BOOST_REQUIRE(nsec3Content);
const std::string salt = "aabbccdd";
const std::string hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
const std::string str = "1 1 12 " + salt + " " + hash;
- auto validNSEC3=DNSRecordContent::mastermake(QType::NSEC3, QClass::IN, str);
+ auto validNSEC3 = DNSRecordContent::make(QType::NSEC3, QClass::IN, str);
vector<uint8_t> packet;
DNSPacketWriter writer(packet, qname, QType::NSEC3, QClass::IN, 0);
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
BOOST_CHECK(all < empty);
BOOST_CHECK(empty > full);
BOOST_CHECK(full < empty);
+
+ /* invalid (too large) mask */
+ {
+ Netmask invalidMaskV4("192.0.2.1/33");
+ BOOST_CHECK_EQUAL(invalidMaskV4.getBits(), 32U);
+ BOOST_CHECK(invalidMaskV4.getNetwork() == ComboAddress("192.0.2.1"));
+ Netmask invalidMaskV6("fe80::92fb:a6ff:fe4a:51da/129");
+ BOOST_CHECK_EQUAL(invalidMaskV6.getBits(), 128U);
+ BOOST_CHECK(invalidMaskV6.getNetwork() == ComboAddress("fe80::92fb:a6ff:fe4a:51da"));
+ }
+ {
+ Netmask invalidMaskV4(ComboAddress("192.0.2.1"), 33);
+ BOOST_CHECK_EQUAL(invalidMaskV4.getBits(), 32U);
+ BOOST_CHECK(invalidMaskV4.getNetwork() == ComboAddress("192.0.2.1"));
+ Netmask invalidMaskV6(ComboAddress("fe80::92fb:a6ff:fe4a:51da"), 129);
+ BOOST_CHECK_EQUAL(invalidMaskV6.getBits(), 128U);
+ BOOST_CHECK(invalidMaskV6.getNetwork() == ComboAddress("fe80::92fb:a6ff:fe4a:51da"));
+ }
}
static std::string NMGOutputToSorted(const std::string& str)
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
BOOST_AUTO_TEST_SUITE(test_ixfr_cc)
BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_axfr) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::NS, "NS.JAIN.AD.JP.");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
- auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+ auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
BOOST_CHECK_EQUAL(ret.size(), 1U);
BOOST_CHECK_EQUAL(ret.at(0).first.size(), 0U);
BOOST_REQUIRE_EQUAL(ret.at(0).second.size(), records.size());
}
BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_incremental) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
- auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+ auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
// two sequences
BOOST_CHECK_EQUAL(ret.size(), 2U);
// the first one has one removal, two additions (plus the corresponding SOA removal/addition)
}
BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_condensed_incremental) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
- auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+ auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
// one sequence
BOOST_CHECK_EQUAL(ret.size(), 1U);
// it has one removal, two additions (plus the corresponding SOA removal/addition)
}
BOOST_AUTO_TEST_CASE(test_ixfr_no_additions_in_first_sequence) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
- auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+ auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
// two sequences
BOOST_CHECK_EQUAL(ret.size(), 2U);
// the first one has one removal, no additions (plus the corresponding SOA removal/addition)
}
BOOST_AUTO_TEST_CASE(test_ixfr_no_removals_in_first_sequence) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
- auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+ auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
// two sequences
BOOST_CHECK_EQUAL(ret.size(), 2U);
// the first one has no removal, two additions (plus the corresponding SOA removal/addition)
}
BOOST_AUTO_TEST_CASE(test_ixfr_same_serial) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
- auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+ auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
// this is actually an empty AXFR
BOOST_CHECK_EQUAL(ret.size(), 1U);
}
BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_records) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
- auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+ auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
BOOST_CHECK_EQUAL(ret.size(), 0U);
}
-BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_master_soa) {
- const ComboAddress master("[2001:DB8::1]:53");
+BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_primary_soa)
+{
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
;
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
- auto ret = processIXFRRecords(master, zone, records, nullptr);
+ auto ret = processIXFRRecords(primary, zone, records, nullptr);
BOOST_CHECK_EQUAL(ret.size(), 0U);
}
BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_trailing_soa) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2");
- BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA)), std::runtime_error);
+ BOOST_CHECK_THROW(processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA)), std::runtime_error);
}
BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_soa_after_removals) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5");
- BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA)), std::runtime_error);
+ BOOST_CHECK_THROW(processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA)), std::runtime_error);
}
BOOST_AUTO_TEST_CASE(test_ixfr_mismatching_serial_before_and_after_additions) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
- BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA)), std::runtime_error);
+ BOOST_CHECK_THROW(processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA)), std::runtime_error);
}
BOOST_AUTO_TEST_CASE(test_ixfr_trailing_record_after_end) {
- const ComboAddress master("[2001:DB8::1]:53");
+ const ComboAddress primary("[2001:DB8::1]:53");
const DNSName zone("JAIN.AD.JP.");
- auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+ auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
vector<DNSRecord> records;
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
- BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA)), std::runtime_error);
+ BOOST_CHECK_THROW(processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA)), std::runtime_error);
}
BOOST_AUTO_TEST_SUITE_END();
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
#include "config.h"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
BOOST_CHECK_EQUAL(out, "12 34 56 78 90 ab cd ef ");
}
+BOOST_AUTO_TEST_CASE(test_CleanSlashes) {
+ auto cleanSlashesWrapper = [](const char* str) {
+ std::string fullStr(str);
+ cleanSlashes(fullStr);
+ return fullStr;
+ };
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper(""), "");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("/"), "/");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("//"), "/");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("/test"), "/test");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("//test"), "/test");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("///test"), "/test");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("/test/"), "/test/");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("//test/"), "/test/");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("//test//"), "/test/");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("///test//"), "/test/");
+ BOOST_CHECK_EQUAL(cleanSlashesWrapper("test///"), "test/");
+}
+
BOOST_AUTO_TEST_SUITE_END()
-
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <thread>
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
ComboAddress Remote("192.168.255.255", 53);
g_localaddresses.push_back(ComboAddress("0.0.0.0", 53));
-
+
BOOST_CHECK_EQUAL(AddressIsUs(local1), true);
// BOOST_CHECK_EQUAL(AddressIsUs(local2), false);
BOOST_CHECK_EQUAL(AddressIsUs(Remote), false);
-
+
g_localaddresses.clear();
g_localaddresses.push_back(ComboAddress("192.168.255.255", 53));
BOOST_CHECK_EQUAL(AddressIsUs(Remote), true);
ComboAddress local2("127.0.0.2", 53);
ComboAddress local3("::1", 53);
ComboAddress Remote("192.168.255.255", 53);
-
+
g_localaddresses.clear();
g_localaddresses.push_back(ComboAddress("::", 53));
-
+
BOOST_CHECK_EQUAL(AddressIsUs(local1), true);
// BOOST_CHECK_EQUAL(AddressIsUs(local2), false);
if(!getenv("PDNS_TEST_NO_IPV6")) BOOST_CHECK_EQUAL(AddressIsUs(local3), true);
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#include <boost/test/unit_test.hpp>
#include "iputils.hh"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
loopback6.append(15, 0);
loopback6.append(1,1);
BOOST_CHECK_EQUAL(makeHexDump(rawIPv6), makeHexDump(loopback6));
-
+
RecordTextReader rtr2("2a01:4f8:d12:1880::5");
rtr2.xfrIP6(rawIPv6);
string ip6("\x2a\x01\x04\xf8\x0d\x12\x18\x80\x00\x00\x00\x00\x00\x00\x00\x05", 16);
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
BOOST_AUTO_TEST_SUITE(test_sha_hh)
// input, output
-typedef boost::tuple<const std::string, const std::string> case_t;
-typedef std::vector<case_t> cases_t;
-
-BOOST_AUTO_TEST_CASE(test_sha1) {
- cases_t cases = list_of
- (case_t("abc", "a9 99 3e 36 47 06 81 6a ba 3e 25 71 78 50 c2 6c 9c d0 d8 9d "))
- (case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84 98 3e 44 1c 3b d2 6e ba ae 4a a1 f9 51 29 e5 e5 46 70 f1 "));
-
- for(case_t& val : cases) {
- BOOST_CHECK_EQUAL(makeHexDump(pdns_sha1sum(val.get<0>())), val.get<1>());
- }
+using case_t = boost::tuple<const std::string, const std::string>;
+using cases_t = std::vector<case_t>;
+
+BOOST_AUTO_TEST_CASE(test_sha1)
+{
+ cases_t cases = list_of(case_t("abc", "a9 99 3e 36 47 06 81 6a ba 3e 25 71 78 50 c2 6c 9c d0 d8 9d "))(case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84 98 3e 44 1c 3b d2 6e ba ae 4a a1 f9 51 29 e5 e5 46 70 f1 "));
+
+ for (case_t& val : cases) {
+ BOOST_CHECK_EQUAL(makeHexDump(pdns::sha1sum(val.get<0>())), val.get<1>());
+ }
}
-BOOST_AUTO_TEST_CASE(test_sha256) {
- cases_t cases = list_of
- (case_t("abc", "ba 78 16 bf 8f 01 cf ea 41 41 40 de 5d ae 22 23 b0 03 61 a3 96 17 7a 9c b4 10 ff 61 f2 00 15 ad "))
- (case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "24 8d 6a 61 d2 06 38 b8 e5 c0 26 93 0c 3e 60 39 a3 3c e4 59 64 ff 21 67 f6 ec ed d4 19 db 06 c1 "));
+BOOST_AUTO_TEST_CASE(test_sha256)
+{
+ cases_t cases = list_of(case_t("abc", "ba 78 16 bf 8f 01 cf ea 41 41 40 de 5d ae 22 23 b0 03 61 a3 96 17 7a 9c b4 10 ff 61 f2 00 15 ad "))(case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "24 8d 6a 61 d2 06 38 b8 e5 c0 26 93 0c 3e 60 39 a3 3c e4 59 64 ff 21 67 f6 ec ed d4 19 db 06 c1 "));
- for(case_t& val : cases) {
- BOOST_CHECK_EQUAL(makeHexDump(pdns_sha256sum(val.get<0>())), val.get<1>());
- }
+ for (case_t& val : cases) {
+ BOOST_CHECK_EQUAL(makeHexDump(pdns::sha256sum(val.get<0>())), val.get<1>());
+ }
}
-BOOST_AUTO_TEST_CASE(test_sha384) {
- cases_t cases = list_of
- (case_t("abc", "cb 00 75 3f 45 a3 5e 8b b5 a0 3d 69 9a c6 50 07 27 2c 32 ab 0e de d1 63 1a 8b 60 5a 43 ff 5b ed 80 86 07 2b a1 e7 cc 23 58 ba ec a1 34 c8 25 a7 "))
- (case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "33 91 fd dd fc 8d c7 39 37 07 a6 5b 1b 47 09 39 7c f8 b1 d1 62 af 05 ab fe 8f 45 0d e5 f3 6b c6 b0 45 5a 85 20 bc 4e 6f 5f e9 5b 1f e3 c8 45 2b "));
+BOOST_AUTO_TEST_CASE(test_sha384)
+{
+ cases_t cases = list_of(case_t("abc", "cb 00 75 3f 45 a3 5e 8b b5 a0 3d 69 9a c6 50 07 27 2c 32 ab 0e de d1 63 1a 8b 60 5a 43 ff 5b ed 80 86 07 2b a1 e7 cc 23 58 ba ec a1 34 c8 25 a7 "))(case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "33 91 fd dd fc 8d c7 39 37 07 a6 5b 1b 47 09 39 7c f8 b1 d1 62 af 05 ab fe 8f 45 0d e5 f3 6b c6 b0 45 5a 85 20 bc 4e 6f 5f e9 5b 1f e3 c8 45 2b "));
- for(case_t& val : cases) {
- BOOST_CHECK_EQUAL(makeHexDump(pdns_sha384sum(val.get<0>())), val.get<1>());
- }
+ for (case_t& val : cases) {
+ BOOST_CHECK_EQUAL(makeHexDump(pdns::sha384sum(val.get<0>())), val.get<1>());
+ }
}
-BOOST_AUTO_TEST_CASE(test_sha512) {
- cases_t cases = list_of
- (case_t("abc", "dd af 35 a1 93 61 7a ba cc 41 73 49 ae 20 41 31 12 e6 fa 4e 89 a9 7e a2 0a 9e ee e6 4b 55 d3 9a 21 92 99 2a 27 4f c1 a8 36 ba 3c 23 a3 fe eb bd 45 4d 44 23 64 3c e8 0e 2a 9a c9 4f a5 4c a4 9f "))
- (case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "20 4a 8f c6 dd a8 2f 0a 0c ed 7b eb 8e 08 a4 16 57 c1 6e f4 68 b2 28 a8 27 9b e3 31 a7 03 c3 35 96 fd 15 c1 3b 1b 07 f9 aa 1d 3b ea 57 78 9c a0 31 ad 85 c7 a7 1d d7 03 54 ec 63 12 38 ca 34 45 "));
+BOOST_AUTO_TEST_CASE(test_sha512)
+{
+ cases_t cases = list_of(case_t("abc", "dd af 35 a1 93 61 7a ba cc 41 73 49 ae 20 41 31 12 e6 fa 4e 89 a9 7e a2 0a 9e ee e6 4b 55 d3 9a 21 92 99 2a 27 4f c1 a8 36 ba 3c 23 a3 fe eb bd 45 4d 44 23 64 3c e8 0e 2a 9a c9 4f a5 4c a4 9f "))(case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "20 4a 8f c6 dd a8 2f 0a 0c ed 7b eb 8e 08 a4 16 57 c1 6e f4 68 b2 28 a8 27 9b e3 31 a7 03 c3 35 96 fd 15 c1 3b 1b 07 f9 aa 1d 3b ea 57 78 9c a0 31 ad 85 c7 a7 1d d7 03 54 ec 63 12 38 ca 34 45 "));
- for(case_t& val : cases) {
- BOOST_CHECK_EQUAL(makeHexDump(pdns_sha512sum(val.get<0>())), val.get<1>());
- }
+ for (case_t& val : cases) {
+ BOOST_CHECK_EQUAL(makeHexDump(pdns::sha512sum(val.get<0>())), val.get<1>());
+ }
}
BOOST_AUTO_TEST_SUITE_END()
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
rrc.d_signer = DNSName("example.net.");
inception = 946684800;
expire = 1893456000;
- rrs.insert(DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1"));
+ rrs.insert(DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.1"));
}
else {
rrc.d_signer = qname;
- rrs.insert(DNSRecordContent::mastermake(QType::MX, QClass::IN, "10 mail.example.com."));
+ rrs.insert(DNSRecordContent::make(QType::MX, QClass::IN, "10 mail.example.com."));
}
rrc.d_originalttl = 3600;
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
s.declare("c", "description");
s.inc("a");
BOOST_CHECK_EQUAL(s.read("a"), 1UL);
-
+
unsigned long n;
for(n=0; n < 1000000; ++n)
s.inc("b");
manglers.clear();
BOOST_CHECK_EQUAL(s.read("c"), 4000000U);
-
+
s.set("c", 0);
for (int i=0; i < 4; ++i) {
s.inc("c");
BOOST_CHECK_EQUAL(s.read("c"), (1ULL<<31) +1 );
-#ifdef UINTPTR_MAX
+#ifdef UINTPTR_MAX
#if UINTPTR_MAX > 0xffffffffULL
BOOST_CHECK_EQUAL(sizeof(AtomicCounterInner), 8U);
s.set("c", 1ULL<<33);
BOOST_AUTO_TEST_SUITE_END()
-
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#include <boost/test/unit_test.hpp>
#include "trusted-notification-proxy.hh"
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
#ifdef HAVE_CONFIG_H
d_end = range.second;
}
else {
- auto range = idx.equal_range(std::make_tuple(qdomain, qtype.getCode()));
+ auto range = idx.equal_range(std::tuple(qdomain, qtype.getCode()));
d_iter = range.first;
d_end = range.second;
}
bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override
{
const auto& idx = boost::multi_index::get<OrderedNameKindTag>(s_metadata.at(d_backendId));
- auto it = idx.find(std::make_tuple(name, kind));
+ auto it = idx.find(std::tuple(name, kind));
if (it == idx.end()) {
/* funnily enough, we are expected to return true even though we might not know that zone */
return true;
bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override
{
auto& idx = boost::multi_index::get<OrderedNameKindTag>(s_metadata.at(d_backendId));
- auto it = idx.find(std::make_tuple(name, kind));
+ auto it = idx.find(std::tuple(name, kind));
if (it == idx.end()) {
s_metadata.at(d_backendId).insert(SimpleMetaData(name, kind, meta));
return true;
}
auto& idx = records->get<OrderedNameTypeTag>();
- auto range = idx.equal_range(std::make_tuple(best, QType::SOA));
+ auto range = idx.equal_range(std::tuple(best, QType::SOA));
if (range.first == range.second) {
return false;
}
auto testFunction = [](UeberBackend& ub) -> void {
{
- auto sbba = dynamic_cast<SimpleBackendBestAuth*>(ub.backends.at(0));
+ auto* sbba = dynamic_cast<SimpleBackendBestAuth*>(ub.backends.at(0).get());
BOOST_REQUIRE(sbba != nullptr);
+
+ // NOLINTNEXTLINE (clang-analyzer-core.NullDereference): Not sure.
sbba->d_authLookupCount = 0;
// test getAuth()
{
// check that it has not been updated in the second backend
- const auto& it = SimpleBackend::s_metadata[2].find(std::make_tuple(DNSName("powerdns.org."), "test-data-b"));
+ const auto& it = SimpleBackend::s_metadata[2].find(std::tuple(DNSName("powerdns.org."), "test-data-b"));
BOOST_REQUIRE(it != SimpleBackend::s_metadata[2].end());
BOOST_REQUIRE_EQUAL(it->d_values.size(), 2U);
BOOST_CHECK_EQUAL(it->d_values.at(0), "value1");
--- /dev/null
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+#include "webserver.hh"
+
+BOOST_AUTO_TEST_SUITE(test_webserver_cc)
+
+BOOST_AUTO_TEST_CASE(test_validURL)
+{
+ // We cannot test\x00 as embedded NULs are not handled by YaHTTP other than stopping the parsing
+ const std::vector<std::pair<string, bool>> urls = {
+ {"http://www.powerdns.com/?foo=123", true},
+ {"http://ww.powerdns.com/?foo=%ff", true},
+ {"http://\x01ww.powerdns.com/?foo=123", false},
+ {"http://\xffwww.powerdns.com/?foo=123", false},
+ {"http://www.powerdns.com/?foo=123\x01", false},
+ {"http://www.powerdns.com/\x7f?foo=123", false},
+ {"http://www.powerdns.com/\x80?foo=123", false},
+ {"http://www.powerdns.com/?\xff", false},
+ {"/?foo=123&bar", true},
+ {"/?foo=%ff&bar", true},
+ {"/?\x01foo=123", false},
+ {"/?foo=123\x01", false},
+ {"/\x7f?foo=123", false},
+ {"/\x80?foo=123", false},
+ {"/?\xff", false},
+ };
+
+ for (const auto& testcase : urls) {
+ BOOST_CHECK_EQUAL(WebServer::validURL(testcase.first), testcase.second);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#include <boost/test/unit_test.hpp>
#include "zonemd.hh"
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
+
#define BOOST_TEST_NO_MAIN
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
+#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
#include <pthread.h>
-#if HAVE_PTHREAD_NP_H
+#ifdef HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#endif
tkey_out->d_error = 0;
tkey_out->d_mode = tkey_in.d_mode;
tkey_out->d_algo = tkey_in.d_algo;
+ // coverity[store_truncates_time_t]
tkey_out->d_inception = inception;
tkey_out->d_expiration = tkey_out->d_inception+15;
#ifdef ENABLE_GSS_TSIG
g_log<<Logger::Error<<"GSS-TSIG request but feature not enabled by enable-gss-tsigs setting"<<endl;
#else
- g_log<<Logger::Error<<"GSS-TSIG request but not feature not compiled in"<<endl;
+ g_log<<Logger::Error<<"GSS-TSIG request but feature not compiled in"<<endl;
#endif
}
} else if (tkey_in.d_mode == 5) { // destroy context
// Fill out the key
for (size_t i = 0; i < klen; i += sizeof(uint32_t)) {
- uint32_t t = dns_random(std::numeric_limits<uint32_t>::max());
+ uint32_t t = dns_random_uint32();
memcpy(&tmpkey.at(i), &t, sizeof(uint32_t));
}
}
// Reset and store some values for the next chunks.
- d_prevMac = theirMac;
+ d_prevMac = std::move(theirMac);
d_nonSignedMessages = 0;
d_signData.clear();
d_tsigPos = 0;
}
- else
+ else {
d_nonSignedMessages++;
+ }
return true;
}
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <memory>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "auth-zonecache.hh"
#include "utility.hh"
-
-#include <dlfcn.h>
-#include <string>
-#include <map>
-#include <sys/types.h>
-#include <sstream>
#include <cerrno>
+#include <dlfcn.h>
+#include <functional>
#include <iostream>
+#include <map>
#include <sstream>
-#include <functional>
+#include <string>
+#include <sys/types.h>
#include "dns.hh"
#include "arguments.hh"
extern StatBag S;
-LockGuarded<vector<UeberBackend *>> UeberBackend::d_instances;
+LockGuarded<vector<UeberBackend*>> UeberBackend::d_instances;
// initially we are blocked
-bool UeberBackend::d_go=false;
-bool UeberBackend::s_doANYLookupsOnly=false;
+bool UeberBackend::d_go = false;
+bool UeberBackend::s_doANYLookupsOnly = false;
std::mutex UeberBackend::d_mut;
std::condition_variable UeberBackend::d_cond;
AtomicCounter* UeberBackend::s_backendQueries = nullptr;
//! Loads a module and reports it to all UeberBackend threads
-bool UeberBackend::loadmodule(const string &name)
+bool UeberBackend::loadmodule(const string& name)
{
- g_log<<Logger::Warning <<"Loading '"<<name<<"'" << endl;
+ g_log << Logger::Warning << "Loading '" << name << "'" << endl;
- void *dlib=dlopen(name.c_str(), RTLD_NOW);
+ void* dlib = dlopen(name.c_str(), RTLD_NOW);
- if(dlib == nullptr) {
- g_log<<Logger::Error <<"Unable to load module '"<<name<<"': "<<dlerror() << endl;
+ if (dlib == nullptr) {
+ // NOLINTNEXTLINE(concurrency-mt-unsafe): There's no thread-safe alternative to dlerror().
+ g_log << Logger::Error << "Unable to load module '" << name << "': " << dlerror() << endl;
return false;
}
bool UeberBackend::loadModules(const vector<string>& modules, const string& path)
{
- for (const auto& module: modules) {
- bool res;
- if (module.find('.')==string::npos) {
- res = UeberBackend::loadmodule(path+"/lib"+module+"backend.so");
- } else if (module[0]=='/' || (module[0]=='.' && module[1]=='/') || (module[0]=='.' && module[1]=='.')) {
+ for (const auto& module : modules) {
+ bool res = false;
+
+ if (module.find('.') == string::npos) {
+ auto fullPath = path;
+ fullPath += "/lib";
+ fullPath += module;
+ fullPath += "backend.so";
+ res = UeberBackend::loadmodule(fullPath);
+ }
+ else if (module[0] == '/' || (module[0] == '.' && module[1] == '/') || (module[0] == '.' && module[1] == '.')) {
// absolute or current path
res = UeberBackend::loadmodule(module);
- } else {
- res = UeberBackend::loadmodule(path+"/"+module);
+ }
+ else {
+ auto fullPath = path;
+ fullPath += "/";
+ fullPath += module;
+ res = UeberBackend::loadmodule(fullPath);
}
- if (res == false) {
+ if (!res) {
return false;
}
}
d_cond.notify_all();
}
-bool UeberBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial)
+bool UeberBackend::getDomainInfo(const DNSName& domain, DomainInfo& domainInfo, bool getSerial)
{
- for(auto backend : backends)
- if(backend->getDomainInfo(domain, di, getSerial))
+ for (auto& backend : backends) {
+ if (backend->getDomainInfo(domain, domainInfo, getSerial)) {
return true;
+ }
+ }
return false;
}
-bool UeberBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account)
+bool UeberBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
{
- for(DNSBackend* mydb : backends) {
- if (mydb->createDomain(domain, kind, masters, account)) {
+ for (auto& backend : backends) {
+ if (backend->createDomain(domain, kind, primaries, account)) {
return true;
}
}
bool UeberBackend::doesDNSSEC()
{
- for(auto* db : backends) {
- if(db->doesDNSSEC())
+ for (auto& backend : backends) {
+ if (backend->doesDNSSEC()) {
return true;
+ }
}
return false;
}
-bool UeberBackend::addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& id)
+bool UeberBackend::addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& keyID)
{
- id = -1;
- for(DNSBackend* db : backends) {
- if(db->addDomainKey(name, key, id))
+ keyID = -1;
+ for (auto& backend : backends) {
+ if (backend->addDomainKey(name, key, keyID)) {
return true;
+ }
}
return false;
}
bool UeberBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys)
{
- for(DNSBackend* db : backends) {
- if(db->getDomainKeys(name, keys))
+ for (auto& backend : backends) {
+ if (backend->getDomainKeys(name, keys)) {
return true;
+ }
}
return false;
}
-bool UeberBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta)
+bool UeberBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
{
- for(DNSBackend* db : backends) {
- if(db->getAllDomainMetadata(name, meta))
+ for (auto& backend : backends) {
+ if (backend->getAllDomainMetadata(name, meta)) {
return true;
+ }
}
return false;
}
bool UeberBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
{
- for(DNSBackend* db : backends) {
- if(db->getDomainMetadata(name, kind, meta))
+ for (auto& backend : backends) {
+ if (backend->getDomainMetadata(name, kind, meta)) {
return true;
+ }
}
return false;
}
bool UeberBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::string& meta)
{
- bool ret;
meta.clear();
std::vector<string> tmp;
- if ((ret = getDomainMetadata(name, kind, tmp)) && !tmp.empty()) {
+ const bool ret = getDomainMetadata(name, kind, tmp);
+ if (ret && !tmp.empty()) {
meta = *tmp.begin();
}
return ret;
bool UeberBackend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
{
- for(DNSBackend* db : backends) {
- if(db->setDomainMetadata(name, kind, meta))
+ for (auto& backend : backends) {
+ if (backend->setDomainMetadata(name, kind, meta)) {
return true;
+ }
}
return false;
}
return setDomainMetadata(name, kind, tmp);
}
-bool UeberBackend::activateDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::activateDomainKey(const DNSName& name, unsigned int keyID)
{
- for(DNSBackend* db : backends) {
- if(db->activateDomainKey(name, id))
+ for (auto& backend : backends) {
+ if (backend->activateDomainKey(name, keyID)) {
return true;
+ }
}
return false;
}
-bool UeberBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::deactivateDomainKey(const DNSName& name, unsigned int keyID)
{
- for(DNSBackend* db : backends) {
- if(db->deactivateDomainKey(name, id))
+ for (auto& backend : backends) {
+ if (backend->deactivateDomainKey(name, keyID)) {
return true;
+ }
}
return false;
}
-bool UeberBackend::publishDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::publishDomainKey(const DNSName& name, unsigned int keyID)
{
- for(DNSBackend* db : backends) {
- if(db->publishDomainKey(name, id))
+ for (auto& backend : backends) {
+ if (backend->publishDomainKey(name, keyID)) {
return true;
+ }
}
return false;
}
-bool UeberBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::unpublishDomainKey(const DNSName& name, unsigned int keyID)
{
- for(DNSBackend* db : backends) {
- if(db->unpublishDomainKey(name, id))
+ for (auto& backend : backends) {
+ if (backend->unpublishDomainKey(name, keyID)) {
return true;
+ }
}
return false;
}
-
-bool UeberBackend::removeDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::removeDomainKey(const DNSName& name, unsigned int keyID)
{
- for(DNSBackend* db : backends) {
- if(db->removeDomainKey(name, id))
+ for (auto& backend : backends) {
+ if (backend->removeDomainKey(name, keyID)) {
return true;
+ }
}
return false;
}
-
-
void UeberBackend::reload()
{
- for (auto & backend : backends)
- {
+ for (auto& backend : backends) {
backend->reload();
}
}
-void UeberBackend::updateZoneCache() {
+void UeberBackend::updateZoneCache()
+{
if (!g_zoneCache.isEnabled()) {
return;
}
vector<std::tuple<DNSName, int>> zone_indices;
g_zoneCache.setReplacePending();
- for (vector<DNSBackend*>::iterator i = backends.begin(); i != backends.end(); ++i )
- {
+ for (auto& backend : backends) {
vector<DomainInfo> zones;
- (*i)->getAllDomains(&zones, false, true);
- for(auto& di: zones) {
- zone_indices.emplace_back(std::move(di.zone), (int)di.id); // this cast should not be necessary
+ backend->getAllDomains(&zones, false, true);
+ for (auto& domainInfo : zones) {
+ zone_indices.emplace_back(std::move(domainInfo.zone), (int)domainInfo.id); // this cast should not be necessary
}
}
g_zoneCache.replace(zone_indices);
}
-void UeberBackend::rediscover(string *status)
+void UeberBackend::rediscover(string* status)
{
- for ( vector< DNSBackend * >::iterator i = backends.begin(); i != backends.end(); ++i )
- {
+ for (auto backend = backends.begin(); backend != backends.end(); ++backend) {
string tmpstr;
- ( *i )->rediscover(&tmpstr);
- if(status)
- *status+=tmpstr + (i!=backends.begin() ? "\n" : "");
+ (*backend)->rediscover(&tmpstr);
+ if (status != nullptr) {
+ *status += tmpstr + (backend != backends.begin() ? "\n" : "");
+ }
}
updateZoneCache();
}
-
-void UeberBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+void UeberBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
{
- for (auto & backend : backends)
- {
- backend->getUnfreshSlaveInfos( domains );
- }
+ for (auto& backend : backends) {
+ backend->getUnfreshSecondaryInfos(domains);
+ }
}
-void UeberBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+void UeberBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
{
- for (auto & backend : backends)
- {
- backend->getUpdatedMasters(domains, catalogs, catalogHashes);
+ for (auto& backend : backends) {
+ backend->getUpdatedPrimaries(domains, catalogs, catalogHashes);
}
}
bool UeberBackend::inTransaction()
{
- for (auto* b : backends )
- {
- if(b->inTransaction())
+ for (auto& backend : backends) {
+ if (backend->inTransaction()) {
return true;
+ }
}
return false;
}
-bool UeberBackend::getAuth(const DNSName &target, const QType& qtype, SOAData* sd, bool cachedOk)
+bool UeberBackend::fillSOAFromZoneRecord(DNSName& shorter, const int zoneId, SOAData* const soaData)
+{
+ // Zone exists in zone cache, directly look up SOA.
+ lookup(QType(QType::SOA), shorter, zoneId, nullptr);
+
+ DNSZoneRecord zoneRecord;
+ if (!get(zoneRecord)) {
+ DLOG(g_log << Logger::Info << "Backend returned no SOA for zone '" << shorter.toLogString() << "', which it reported as existing " << endl);
+ return false;
+ }
+
+ if (zoneRecord.dr.d_name != shorter) {
+ throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '" + zoneRecord.dr.d_name.toLogString() + "' is not equal to looked up zone '" + shorter.toLogString() + "'");
+ }
+
+ // Fill soaData.
+ soaData->qname = zoneRecord.dr.d_name;
+
+ try {
+ fillSOAData(zoneRecord, *soaData);
+ }
+ catch (...) {
+ g_log << Logger::Warning << "Backend returned a broken SOA for zone '" << shorter.toLogString() << "'" << endl;
+
+ while (get(zoneRecord)) {
+ ;
+ }
+
+ return false;
+ }
+
+ soaData->db = backends.size() == 1 ? backends.begin()->get() : nullptr;
+
+ // Leave database handle in a consistent state.
+ while (get(zoneRecord)) {
+ ;
+ }
+
+ return true;
+}
+
+UeberBackend::CacheResult UeberBackend::fillSOAFromCache(SOAData* soaData, DNSName& shorter)
+{
+ auto cacheResult = cacheHas(d_question, d_answers);
+
+ if (cacheResult == CacheResult::Hit && !d_answers.empty() && d_cache_ttl != 0U) {
+ DLOG(g_log << Logger::Error << "has pos cache entry: " << shorter << endl);
+ fillSOAData(d_answers[0], *soaData);
+
+ soaData->db = backends.size() == 1 ? backends.begin()->get() : nullptr;
+ soaData->qname = shorter;
+ }
+ else if (cacheResult == CacheResult::NegativeMatch && d_negcache_ttl != 0U) {
+ DLOG(g_log << Logger::Error << "has neg cache entry: " << shorter << endl);
+ }
+
+ return cacheResult;
+}
+
+static std::vector<std::unique_ptr<DNSBackend>>::iterator findBestMatchingBackend(std::vector<std::unique_ptr<DNSBackend>>& backends, std::vector<std::pair<std::size_t, SOAData>>& bestMatches, const DNSName& shorter, SOAData* soaData)
+{
+ auto backend = backends.begin();
+ for (auto bestMatch = bestMatches.begin(); backend != backends.end() && bestMatch != bestMatches.end(); ++backend, ++bestMatch) {
+
+ DLOG(g_log << Logger::Error << "backend: " << backend - backends.begin() << ", qname: " << shorter << endl);
+
+ if (bestMatch->first < shorter.wirelength()) {
+ DLOG(g_log << Logger::Error << "skipped, we already found a shorter best match in this backend: " << bestMatch->second.qname << endl);
+ continue;
+ }
+
+ if (bestMatch->first == shorter.wirelength()) {
+ DLOG(g_log << Logger::Error << "use shorter best match: " << bestMatch->second.qname << endl);
+ *soaData = bestMatch->second;
+ break;
+ }
+
+ DLOG(g_log << Logger::Error << "lookup: " << shorter << endl);
+
+ if ((*backend)->getAuth(shorter, soaData)) {
+ DLOG(g_log << Logger::Error << "got: " << soaData->qname << endl);
+
+ if (!soaData->qname.empty() && !shorter.isPartOf(soaData->qname)) {
+ throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '" + soaData->qname.toLogString() + "' is not part of '" + shorter.toLogString() + "'");
+ }
+
+ bestMatch->first = soaData->qname.wirelength();
+ bestMatch->second = *soaData;
+
+ if (soaData->qname == shorter) {
+ break;
+ }
+ }
+ else {
+ DLOG(g_log << Logger::Error << "no match for: " << shorter << endl);
+ }
+ }
+
+ return backend;
+}
+
+static bool foundTarget(const DNSName& target, const DNSName& shorter, const QType& qtype, [[maybe_unused]] SOAData* soaData, const bool found)
+{
+ if (found == (qtype == QType::DS) || target != shorter) {
+ DLOG(g_log << Logger::Error << "found: " << soaData->qname << endl);
+ return true;
+ }
+
+ DLOG(g_log << Logger::Error << "chasing next: " << soaData->qname << endl);
+ return false;
+}
+
+bool UeberBackend::getAuth(const DNSName& target, const QType& qtype, SOAData* soaData, bool cachedOk)
{
// A backend can respond to our authority request with the 'best' match it
// has. For example, when asked for a.b.c.example.com. it might respond with
// of them has a more specific zone but don't bother asking this specific
// backend again for b.c.example.com., c.example.com. and example.com.
// If a backend has no match it may respond with an empty qname.
+
bool found = false;
- int cstat;
DNSName shorter(target);
- vector<pair<size_t, SOAData> > bestmatch (backends.size(), pair(target.wirelength()+1, SOAData()));
- do {
+ vector<pair<size_t, SOAData>> bestMatches(backends.size(), pair(target.wirelength() + 1, SOAData()));
+
+ bool first = true;
+ while (first || shorter.chopOff()) {
+ first = false;
+
int zoneId{-1};
- if(cachedOk && g_zoneCache.isEnabled()) {
+
+ if (cachedOk && g_zoneCache.isEnabled()) {
if (g_zoneCache.getEntry(shorter, zoneId)) {
- // Zone exists in zone cache, directly look up SOA.
- DNSZoneRecord zr;
- lookup(QType(QType::SOA), shorter, zoneId, nullptr);
- if (!get(zr)) {
- DLOG(g_log << Logger::Info << "Backend returned no SOA for zone '" << shorter.toLogString() << "', which it reported as existing " << endl);
- continue;
- }
- if (zr.dr.d_name != shorter) {
- throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '"+zr.dr.d_name.toLogString()+"' is not equal to looked up zone '"+shorter.toLogString()+"'");
- }
- // fill sd
- sd->qname = zr.dr.d_name;
- try {
- fillSOAData(zr, *sd);
- }
- catch (...) {
- g_log << Logger::Warning << "Backend returned a broken SOA for zone '" << shorter.toLogString() << "'" << endl;
- while (get(zr))
- ;
- continue;
- }
- if (backends.size() == 1) {
- sd->db = *backends.begin();
- }
- else {
- sd->db = nullptr;
+ if (fillSOAFromZoneRecord(shorter, zoneId, soaData)) {
+ if (foundTarget(target, shorter, qtype, soaData, found)) {
+ return true;
+ }
+
+ found = true;
}
- // leave database handle in a consistent state
- while (get(zr))
- ;
- goto found;
+
+ continue;
}
- // zone does not exist, try again with shorter name
+
+ // Zone does not exist, try again with a shorter name.
continue;
}
d_question.qname = shorter;
d_question.zoneId = zoneId;
- // Check cache
- if(cachedOk && (d_cache_ttl || d_negcache_ttl)) {
- cstat = cacheHas(d_question,d_answers);
+ // Check cache.
+ if (cachedOk && (d_cache_ttl != 0 || d_negcache_ttl != 0)) {
+ auto cacheResult = fillSOAFromCache(soaData, shorter);
+ if (cacheResult == CacheResult::Hit) {
+ if (foundTarget(target, shorter, qtype, soaData, found)) {
+ return true;
+ }
- if(cstat == 1 && !d_answers.empty() && d_cache_ttl) {
- DLOG(g_log<<Logger::Error<<"has pos cache entry: "<<shorter<<endl);
- fillSOAData(d_answers[0], *sd);
+ found = true;
+ continue;
+ }
- if (backends.size() == 1) {
- sd->db = *backends.begin();
- } else {
- sd->db = nullptr;
- }
- sd->qname = shorter;
- goto found;
- } else if(cstat == 0 && d_negcache_ttl) {
- DLOG(g_log<<Logger::Error<<"has neg cache entry: "<<shorter<<endl);
+ if (cacheResult == CacheResult::NegativeMatch) {
continue;
}
}
- // Check backends
+ // Check backends.
{
- vector<DNSBackend *>::const_iterator i = backends.begin();
- vector<pair<size_t, SOAData> >::iterator j = bestmatch.begin();
- for(; i != backends.end() && j != bestmatch.end(); ++i, ++j) {
-
- DLOG(g_log<<Logger::Error<<"backend: "<<i-backends.begin()<<", qname: "<<shorter<<endl);
-
- if(j->first < shorter.wirelength()) {
- DLOG(g_log<<Logger::Error<<"skipped, we already found a shorter best match in this backend: "<<j->second.qname<<endl);
- continue;
- } else if(j->first == shorter.wirelength()) {
- DLOG(g_log<<Logger::Error<<"use shorter best match: "<<j->second.qname<<endl);
- *sd = j->second;
- break;
- } else {
- DLOG(g_log<<Logger::Error<<"lookup: "<<shorter<<endl);
- if((*i)->getAuth(shorter, sd)) {
- DLOG(g_log<<Logger::Error<<"got: "<<sd->qname<<endl);
- if(!sd->qname.empty() && !shorter.isPartOf(sd->qname)) {
- throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '"+sd->qname.toLogString()+"' is not part of '"+shorter.toLogString()+"'");
- }
- j->first = sd->qname.wirelength();
- j->second = *sd;
- if(sd->qname == shorter) {
- break;
- }
- } else {
- DLOG(g_log<<Logger::Error<<"no match for: "<<shorter<<endl);
- }
- }
- }
+ auto backend = findBestMatchingBackend(backends, bestMatches, shorter, soaData);
// Add to cache
- if(i == backends.end()) {
- if(d_negcache_ttl) {
- DLOG(g_log<<Logger::Error<<"add neg cache entry:"<<shorter<<endl);
- d_question.qname=shorter;
+ if (backend == backends.end()) {
+ if (d_negcache_ttl != 0U) {
+ DLOG(g_log << Logger::Error << "add neg cache entry:" << shorter << endl);
+ d_question.qname = shorter;
addNegCache(d_question);
}
+
continue;
- } else if(d_cache_ttl) {
- DLOG(g_log<<Logger::Error<<"add pos cache entry: "<<sd->qname<<endl);
+ }
+
+ if (d_cache_ttl != 0) {
+ DLOG(g_log << Logger::Error << "add pos cache entry: " << soaData->qname << endl);
+
d_question.qtype = QType::SOA;
- d_question.qname = sd->qname;
+ d_question.qname = soaData->qname;
d_question.zoneId = zoneId;
- DNSZoneRecord rr;
- rr.dr.d_name = sd->qname;
- rr.dr.d_type = QType::SOA;
- rr.dr.setContent(makeSOAContent(*sd));
- rr.dr.d_ttl = sd->ttl;
- rr.domain_id = sd->domain_id;
+ DNSZoneRecord resourceRecord;
+ resourceRecord.dr.d_name = soaData->qname;
+ resourceRecord.dr.d_type = QType::SOA;
+ resourceRecord.dr.setContent(makeSOAContent(*soaData));
+ resourceRecord.dr.d_ttl = soaData->ttl;
+ resourceRecord.domain_id = soaData->domain_id;
- addCache(d_question, {rr});
+ addCache(d_question, {std::move(resourceRecord)});
}
}
-found:
- if(found == (qtype == QType::DS) || target != shorter) {
- DLOG(g_log<<Logger::Error<<"found: "<<sd->qname<<endl);
+ if (foundTarget(target, shorter, qtype, soaData, found)) {
return true;
- } else {
- DLOG(g_log<<Logger::Error<<"chasing next: "<<sd->qname<<endl);
- found = true;
}
- } while(shorter.chopOff());
+ found = true;
+ }
+
return found;
}
-bool UeberBackend::getSOAUncached(const DNSName &domain, SOAData &sd)
+bool UeberBackend::getSOAUncached(const DNSName& domain, SOAData& soaData)
{
- d_question.qtype=QType::SOA;
- d_question.qname=domain;
- d_question.zoneId=-1;
+ d_question.qtype = QType::SOA;
+ d_question.qname = domain;
+ d_question.zoneId = -1;
- for(auto backend : backends)
- if(backend->getSOA(domain, sd)) {
- if(domain != sd.qname) {
- throw PDNSException("getSOA() returned an SOA for the wrong zone. Question: '"+domain.toLogString()+"', answer: '"+sd.qname.toLogString()+"'");
+ for (auto& backend : backends) {
+ if (backend->getSOA(domain, soaData)) {
+ if (domain != soaData.qname) {
+ throw PDNSException("getSOA() returned an SOA for the wrong zone. Question: '" + domain.toLogString() + "', answer: '" + soaData.qname.toLogString() + "'");
}
- if(d_cache_ttl) {
- DNSZoneRecord rr;
- rr.dr.d_name = sd.qname;
- rr.dr.d_type = QType::SOA;
- rr.dr.setContent(makeSOAContent(sd));
- rr.dr.d_ttl = sd.ttl;
- rr.domain_id = sd.domain_id;
-
- addCache(d_question, {rr});
-
+ if (d_cache_ttl != 0U) {
+ DNSZoneRecord zoneRecord;
+ zoneRecord.dr.d_name = soaData.qname;
+ zoneRecord.dr.d_type = QType::SOA;
+ zoneRecord.dr.setContent(makeSOAContent(soaData));
+ zoneRecord.dr.d_ttl = soaData.ttl;
+ zoneRecord.domain_id = soaData.domain_id;
+
+ addCache(d_question, {std::move(zoneRecord)});
}
return true;
}
+ }
- if(d_negcache_ttl)
+ if (d_negcache_ttl != 0U) {
addNegCache(d_question);
+ }
return false;
}
-bool UeberBackend::superMasterAdd(const AutoPrimary &primary)
+bool UeberBackend::autoPrimaryAdd(const AutoPrimary& primary)
{
- for(auto backend : backends)
- if(backend->superMasterAdd(primary))
+ for (auto& backend : backends) {
+ if (backend->autoPrimaryAdd(primary)) {
return true;
+ }
+ }
return false;
}
-bool UeberBackend::autoPrimaryRemove(const AutoPrimary &primary)
+bool UeberBackend::autoPrimaryRemove(const AutoPrimary& primary)
{
- for(auto backend : backends)
- if(backend->autoPrimaryRemove(primary))
+ for (auto& backend : backends) {
+ if (backend->autoPrimaryRemove(primary)) {
return true;
+ }
+ }
return false;
}
bool UeberBackend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
{
- for(auto backend : backends)
- if(backend->autoPrimariesList(primaries))
- return true;
- return false;
+ for (auto& backend : backends) {
+ if (backend->autoPrimariesList(primaries)) {
+ return true;
+ }
+ }
+ return false;
}
-bool UeberBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db)
+bool UeberBackend::autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** dnsBackend)
{
- for(auto backend : backends)
- if(backend->superMasterBackend(ip, domain, nsset, nameserver, account, db))
+ for (auto& backend : backends) {
+ if (backend->autoPrimaryBackend(ip, domain, nsset, nameserver, account, dnsBackend)) {
return true;
+ }
+ }
return false;
}
-UeberBackend::UeberBackend(const string &pname)
+UeberBackend::UeberBackend(const string& pname)
{
{
d_instances.lock()->push_back(this); // report to the static list of ourself
}
- d_negcached=false;
- d_cached=false;
d_cache_ttl = ::arg().asNum("query-cache-ttl");
d_negcache_ttl = ::arg().asNum("negquery-cache-ttl");
- d_qtype = 0;
- d_stale = false;
-
- backends=BackendMakers().all(pname=="key-only");
-}
-static void del(DNSBackend* d)
-{
- delete d;
-}
-
-void UeberBackend::cleanup()
-{
- {
- auto instances = d_instances.lock();
- remove(instances->begin(), instances->end(), this);
- instances->resize(instances->size()-1);
- }
-
- for_each(backends.begin(),backends.end(),del);
+ backends = BackendMakers().all(pname == "key-only");
}
// returns -1 for miss, 0 for negative match, 1 for hit
-int UeberBackend::cacheHas(const Question &q, vector<DNSZoneRecord> &rrs)
+enum UeberBackend::CacheResult UeberBackend::cacheHas(const Question& question, vector<DNSZoneRecord>& resourceRecords) const
{
extern AuthQueryCache QC;
- if(!d_cache_ttl && ! d_negcache_ttl) {
- return -1;
+ if (d_cache_ttl == 0 && d_negcache_ttl == 0) {
+ return CacheResult::Miss;
}
- rrs.clear();
+ resourceRecords.clear();
// g_log<<Logger::Warning<<"looking up: '"<<q.qname+"'|N|"+q.qtype.getName()+"|"+itoa(q.zoneId)<<endl;
- bool ret=QC.getEntry(q.qname, q.qtype, rrs, q.zoneId); // think about lowercasing here
- if(!ret) {
- return -1;
+ bool ret = QC.getEntry(question.qname, question.qtype, resourceRecords, question.zoneId); // think about lowercasing here
+ if (!ret) {
+ return CacheResult::Miss;
}
- if(rrs.empty()) // negatively cached
- return 0;
-
- return 1;
+ if (resourceRecords.empty()) { // negatively cached
+ return CacheResult::NegativeMatch;
+ }
+
+ return CacheResult::Hit;
}
-void UeberBackend::addNegCache(const Question &q)
+void UeberBackend::addNegCache(const Question& question) const
{
extern AuthQueryCache QC;
- if(!d_negcache_ttl)
+ if (d_negcache_ttl == 0) {
return;
+ }
// we should also not be storing negative answers if a pipebackend does scopeMask, but we can't pass a negative scopeMask in an empty set!
- QC.insert(q.qname, q.qtype, vector<DNSZoneRecord>(), d_negcache_ttl, q.zoneId);
+ QC.insert(question.qname, question.qtype, vector<DNSZoneRecord>(), d_negcache_ttl, question.zoneId);
}
-void UeberBackend::addCache(const Question &q, vector<DNSZoneRecord> &&rrs)
+void UeberBackend::addCache(const Question& question, vector<DNSZoneRecord>&& rrs) const
{
extern AuthQueryCache QC;
- if(!d_cache_ttl)
+ if (d_cache_ttl == 0) {
return;
+ }
- for(const auto& rr : rrs) {
- if (rr.scopeMask)
- return;
+ for (const auto& resourceRecord : rrs) {
+ if (resourceRecord.scopeMask != 0) {
+ return;
+ }
}
- QC.insert(q.qname, q.qtype, std::move(rrs), d_cache_ttl, q.zoneId);
+ QC.insert(question.qname, question.qtype, std::move(rrs), d_cache_ttl, question.zoneId);
}
-void UeberBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
+void UeberBackend::alsoNotifies(const DNSName& domain, set<string>* ips)
{
- for (auto & backend : backends)
- backend->alsoNotifies(domain,ips);
+ for (auto& backend : backends) {
+ backend->alsoNotifies(domain, ips);
+ }
}
UeberBackend::~UeberBackend()
{
- DLOG(g_log<<Logger::Error<<"UeberBackend destructor called, removing ourselves from instances, and deleting our backends"<<endl);
- cleanup();
+ DLOG(g_log << Logger::Error << "UeberBackend destructor called, removing ourselves from instances, and deleting our backends" << endl);
+
+ {
+ auto instances = d_instances.lock();
+ [[maybe_unused]] auto end = remove(instances->begin(), instances->end(), this);
+ instances->resize(instances->size() - 1);
+ }
+
+ backends.clear();
}
// this handle is more magic than most
-void UeberBackend::lookup(const QType &qtype,const DNSName &qname, int zoneId, DNSPacket *pkt_p)
+void UeberBackend::lookup(const QType& qtype, const DNSName& qname, int zoneId, DNSPacket* pkt_p)
{
- if(d_stale) {
- g_log<<Logger::Error<<"Stale ueberbackend received question, signalling that we want to be recycled"<<endl;
+ if (d_stale) {
+ g_log << Logger::Error << "Stale ueberbackend received question, signalling that we want to be recycled" << endl;
throw PDNSException("We are stale, please recycle");
}
- DLOG(g_log<<"UeberBackend received question for "<<qtype<<" of "<<qname<<endl);
+ DLOG(g_log << "UeberBackend received question for " << qtype << " of " << qname << endl);
if (!d_go) {
- g_log<<Logger::Error<<"UeberBackend is blocked, waiting for 'go'"<<endl;
+ g_log << Logger::Error << "UeberBackend is blocked, waiting for 'go'" << endl;
std::unique_lock<std::mutex> l(d_mut);
- d_cond.wait(l, []{ return d_go == true; });
- g_log<<Logger::Error<<"Broadcast received, unblocked"<<endl;
+ d_cond.wait(l, [] { return d_go; });
+ g_log << Logger::Error << "Broadcast received, unblocked" << endl;
}
- d_qtype=qtype.getCode();
+ d_qtype = qtype.getCode();
- d_handle.i=0;
- d_handle.qtype=s_doANYLookupsOnly ? QType::ANY : qtype;
- d_handle.qname=qname;
- d_handle.zoneId=zoneId;
- d_handle.pkt_p=pkt_p;
+ d_handle.i = 0;
+ d_handle.qtype = s_doANYLookupsOnly ? QType::ANY : qtype;
+ d_handle.qname = qname;
+ d_handle.zoneId = zoneId;
+ d_handle.pkt_p = pkt_p;
- if(!backends.size()) {
- g_log<<Logger::Error<<"No database backends available - unable to answer questions."<<endl;
- d_stale=true; // please recycle us!
+ if (backends.empty()) {
+ g_log << Logger::Error << "No database backends available - unable to answer questions." << endl;
+ d_stale = true; // please recycle us!
throw PDNSException("We are stale, please recycle");
}
+
+ d_question.qtype = d_handle.qtype;
+ d_question.qname = qname;
+ d_question.zoneId = d_handle.zoneId;
+
+ auto cacheResult = cacheHas(d_question, d_answers);
+ if (cacheResult == CacheResult::Miss) { // nothing
+ // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): uncached"<<endl;
+ d_negcached = d_cached = false;
+ d_answers.clear();
+ (d_handle.d_hinterBackend = backends[d_handle.i++].get())->lookup(d_handle.qtype, d_handle.qname, d_handle.zoneId, d_handle.pkt_p);
+ ++(*s_backendQueries);
+ }
+ else if (cacheResult == CacheResult::NegativeMatch) {
+ // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): NEGcached"<<endl;
+ d_negcached = true;
+ d_cached = false;
+ d_answers.clear();
+ }
else {
- d_question.qtype=d_handle.qtype;
- d_question.qname=qname;
- d_question.zoneId=d_handle.zoneId;
-
- int cstat=cacheHas(d_question, d_answers);
- if(cstat<0) { // nothing
- // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): uncached"<<endl;
- d_negcached=d_cached=false;
- d_answers.clear();
- (d_handle.d_hinterBackend=backends[d_handle.i++])->lookup(d_handle.qtype, d_handle.qname, d_handle.zoneId, d_handle.pkt_p);
- ++(*s_backendQueries);
- }
- else if(cstat==0) {
- // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): NEGcached"<<endl;
- d_negcached=true;
- d_cached=false;
- d_answers.clear();
- }
- else {
- // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): CACHED"<<endl;
- d_negcached=false;
- d_cached=true;
- d_cachehandleiter = d_answers.begin();
- }
+ // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): CACHED"<<endl;
+ d_negcached = false;
+ d_cached = true;
+ d_cachehandleiter = d_answers.begin();
}
- d_handle.parent=this;
+ d_handle.parent = this;
}
void UeberBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled)
{
- for (auto & backend : backends)
- {
+ for (auto& backend : backends) {
backend->getAllDomains(domains, getSerial, include_disabled);
}
}
-bool UeberBackend::get(DNSZoneRecord &rr)
+bool UeberBackend::get(DNSZoneRecord& resourceRecord)
{
// cout<<"UeberBackend::get(DNSZoneRecord) called"<<endl;
- if(d_negcached) {
- return false;
+ if (d_negcached) {
+ return false;
}
- if(d_cached) {
- while(d_cachehandleiter != d_answers.end()) {
- rr=*d_cachehandleiter++;;
- if((d_qtype == QType::ANY || rr.dr.d_type == d_qtype)) {
+ if (d_cached) {
+ while (d_cachehandleiter != d_answers.end()) {
+ resourceRecord = *d_cachehandleiter++;
+ if ((d_qtype == QType::ANY || resourceRecord.dr.d_type == d_qtype)) {
return true;
}
}
return false;
}
- while(d_handle.get(rr)) {
- rr.dr.d_place=DNSResourceRecord::ANSWER;
- d_answers.push_back(rr);
- if((d_qtype == QType::ANY || rr.dr.d_type == d_qtype)) {
+ while (d_handle.get(resourceRecord)) {
+ resourceRecord.dr.d_place = DNSResourceRecord::ANSWER;
+ d_answers.push_back(resourceRecord);
+ if ((d_qtype == QType::ANY || resourceRecord.dr.d_type == d_qtype)) {
return true;
}
}
// cout<<"end of ueberbackend get, seeing if we should cache"<<endl;
- if(d_answers.empty()) {
+ if (d_answers.empty()) {
// cout<<"adding negcache"<<endl;
addNegCache(d_question);
}
//
bool UeberBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
{
- for (auto* b : backends) {
- if (b->setTSIGKey(name, algorithm, content)) {
+ for (auto& backend : backends) {
+ if (backend->setTSIGKey(name, algorithm, content)) {
return true;
}
}
algorithm.clear();
content.clear();
- for (auto* b : backends) {
- if (b->getTSIGKey(name, algorithm, content)) {
+ for (auto& backend : backends) {
+ if (backend->getTSIGKey(name, algorithm, content)) {
break;
}
}
{
keys.clear();
- for (auto* b : backends) {
- if (b->getTSIGKeys(keys)) {
+ for (auto& backend : backends) {
+ if (backend->getTSIGKeys(keys)) {
return true;
}
}
bool UeberBackend::deleteTSIGKey(const DNSName& name)
{
- for (auto* b : backends) {
- if (b->deleteTSIGKey(name)) {
+ for (auto& backend : backends) {
+ if (backend->deleteTSIGKey(name)) {
return true;
}
}
// API Search
//
-bool UeberBackend::searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result)
+bool UeberBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
{
- bool rc = false;
- for ( vector< DNSBackend * >::iterator i = backends.begin(); result.size() < static_cast<vector<DNSResourceRecord>::size_type>(maxResults) && i != backends.end(); ++i )
- if ((*i)->searchRecords(pattern, maxResults - result.size(), result)) rc = true;
- return rc;
+ bool ret = false;
+ for (auto backend = backends.begin(); result.size() < maxResults && backend != backends.end(); ++backend) {
+ if ((*backend)->searchRecords(pattern, maxResults - result.size(), result)) {
+ ret = true;
+ }
+ }
+ return ret;
}
-bool UeberBackend::searchComments(const string& pattern, int maxResults, vector<Comment>& result)
+bool UeberBackend::searchComments(const string& pattern, size_t maxResults, vector<Comment>& result)
{
- bool rc = false;
- for ( vector< DNSBackend * >::iterator i = backends.begin(); result.size() < static_cast<vector<Comment>::size_type>(maxResults) && i != backends.end(); ++i )
- if ((*i)->searchComments(pattern, maxResults - result.size(), result)) rc = true;
- return rc;
+ bool ret = false;
+ for (auto backend = backends.begin(); result.size() < maxResults && backend != backends.end(); ++backend) {
+ if ((*backend)->searchComments(pattern, maxResults - result.size(), result)) {
+ ret = true;
+ }
+ }
+ return ret;
}
AtomicCounter UeberBackend::handle::instances(0);
{
// g_log<<Logger::Warning<<"Handle instances: "<<instances<<endl;
++instances;
- parent=nullptr;
- d_hinterBackend=nullptr;
- pkt_p=nullptr;
- i=0;
- zoneId = -1;
}
UeberBackend::handle::~handle()
--instances;
}
-bool UeberBackend::handle::get(DNSZoneRecord &r)
+bool UeberBackend::handle::get(DNSZoneRecord& record)
{
- DLOG(g_log << "Ueber get() was called for a "<<qtype<<" record" << endl);
- bool isMore=false;
- while(d_hinterBackend && !(isMore=d_hinterBackend->get(r))) { // this backend out of answers
- if(i<parent->backends.size()) {
- DLOG(g_log<<"Backend #"<<i<<" of "<<parent->backends.size()
- <<" out of answers, taking next"<<endl);
-
- d_hinterBackend=parent->backends[i++];
- d_hinterBackend->lookup(qtype,qname,zoneId,pkt_p);
+ DLOG(g_log << "Ueber get() was called for a " << qtype << " record" << endl);
+ bool isMore = false;
+ while (d_hinterBackend != nullptr && !(isMore = d_hinterBackend->get(record))) { // this backend out of answers
+ if (i < parent->backends.size()) {
+ DLOG(g_log << "Backend #" << i << " of " << parent->backends.size()
+ << " out of answers, taking next" << endl);
+
+ d_hinterBackend = parent->backends[i++].get();
+ d_hinterBackend->lookup(qtype, qname, zoneId, pkt_p);
++(*s_backendQueries);
}
- else
+ else {
break;
+ }
- DLOG(g_log<<"Now asking backend #"<<i<<endl);
+ DLOG(g_log << "Now asking backend #" << i << endl);
}
- if(!isMore && i==parent->backends.size()) {
- DLOG(g_log<<"UeberBackend reached end of backends"<<endl);
+ if (!isMore && i == parent->backends.size()) {
+ DLOG(g_log << "UeberBackend reached end of backends" << endl);
return false;
}
- DLOG(g_log<<"Found an answering backend - will not try another one"<<endl);
- i=parent->backends.size(); // don't go on to the next backend
+ DLOG(g_log << "Found an answering backend - will not try another one" << endl);
+ i = parent->backends.size(); // don't go on to the next backend
return true;
}
/** This is a very magic backend that allows us to load modules dynamically,
and query them in order. This is persistent over all UeberBackend instantiations
- across multiple threads.
+ across multiple threads.
The UeberBackend is transparent for exceptions, which should fall straight through.
*/
class UeberBackend : public boost::noncopyable
{
public:
- UeberBackend(const string &pname="default");
+ UeberBackend(const string& pname = "default");
~UeberBackend();
- bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db);
+ bool autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** dnsBackend);
- bool superMasterAdd(const AutoPrimary &primary);
+ bool autoPrimaryAdd(const AutoPrimary& primary);
bool autoPrimaryRemove(const struct AutoPrimary& primary);
bool autoPrimariesList(std::vector<AutoPrimary>& primaries);
/** Tracks all created UeberBackend instances for us. We use this vector to notify
- existing threads of new modules
+ existing threads of new modules
*/
- static LockGuarded<vector<UeberBackend *>> d_instances;
+ static LockGuarded<vector<UeberBackend*>> d_instances;
- static bool loadmodule(const string &name);
+ static bool loadmodule(const string& name);
static bool loadModules(const vector<string>& modules, const string& path);
- static void go(void);
+ static void go();
/** This contains all registered backends. The DynListener modifies this list for us when
new modules are loaded */
- vector<DNSBackend*> backends;
-
- void cleanup();
+ vector<std::unique_ptr<DNSBackend>> backends;
//! the very magic handle for UeberBackend questions
class handle
{
public:
- bool get(DNSZoneRecord &dr);
+ bool get(DNSZoneRecord& record);
handle();
~handle();
//! The UeberBackend class where this handle belongs to
- UeberBackend *parent;
+ UeberBackend* parent{nullptr};
//! The current real backend, which is answering questions
- DNSBackend *d_hinterBackend;
+ DNSBackend* d_hinterBackend{nullptr};
//! DNSPacket who asked this question
- DNSPacket* pkt_p;
+ DNSPacket* pkt_p{nullptr};
DNSName qname;
//! Index of the current backend within the backends vector
- unsigned int i;
+ unsigned int i{0};
QType qtype;
- int zoneId;
+ int zoneId{-1};
private:
-
static AtomicCounter instances;
};
- void lookup(const QType &, const DNSName &qdomain, int zoneId, DNSPacket *pkt_p=nullptr);
+ void lookup(const QType& qtype, const DNSName& qname, int zoneId, DNSPacket* pkt_p = nullptr);
/** Determines if we are authoritative for a zone, and at what level */
- bool getAuth(const DNSName &target, const QType &qtype, SOAData* sd, bool cachedOk=true);
+ bool getAuth(const DNSName& target, const QType& qtype, SOAData* soaData, bool cachedOk = true);
/** Load SOA info from backends, ignoring the cache.*/
- bool getSOAUncached(const DNSName &domain, SOAData &sd);
- bool get(DNSZoneRecord &r);
+ bool getSOAUncached(const DNSName& domain, SOAData& soaData);
+ bool get(DNSZoneRecord& resourceRecord);
void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled);
- void getUnfreshSlaveInfos(vector<DomainInfo>* domains);
- void getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes);
- bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true);
- bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account);
-
+ void getUnfreshSecondaryInfos(vector<DomainInfo>* domains);
+ void getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes);
+ bool getDomainInfo(const DNSName& domain, DomainInfo& domainInfo, bool getSerial = true);
+ bool createDomain(const DNSName& domain, DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account);
+
bool doesDNSSEC();
- bool addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& id);
+ bool addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& keyID);
bool getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys);
- bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta);
+ bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta);
bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta);
bool getDomainMetadata(const DNSName& name, const std::string& kind, std::string& meta);
bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta);
bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::string& meta);
- bool removeDomainKey(const DNSName& name, unsigned int id);
- bool activateDomainKey(const DNSName& name, unsigned int id);
- bool deactivateDomainKey(const DNSName& name, unsigned int id);
- bool publishDomainKey(const DNSName& name, unsigned int id);
- bool unpublishDomainKey(const DNSName& name, unsigned int id);
+ bool removeDomainKey(const DNSName& name, unsigned int keyID);
+ bool activateDomainKey(const DNSName& name, unsigned int keyID);
+ bool deactivateDomainKey(const DNSName& name, unsigned int keyID);
+ bool publishDomainKey(const DNSName& name, unsigned int keyID);
+ bool unpublishDomainKey(const DNSName& name, unsigned int keyID);
- void alsoNotifies(const DNSName &domain, set<string> *ips);
- void rediscover(string* status=0);
+ void alsoNotifies(const DNSName& domain, set<string>* ips);
+ void rediscover(string* status = nullptr);
void reload();
bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content);
bool getTSIGKeys(std::vector<struct TSIGKey>& keys);
bool deleteTSIGKey(const DNSName& name);
- bool searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result);
- bool searchComments(const string &pattern, int maxResults, vector<Comment>& result);
+ bool searchRecords(const string& pattern, vector<DNSResourceRecord>::size_type maxResults, vector<DNSResourceRecord>& result);
+ bool searchComments(const string& pattern, size_t maxResults, vector<Comment>& result);
void updateZoneCache();
DNSName qname;
int zoneId;
QType qtype;
- }d_question;
+ } d_question;
unsigned int d_cache_ttl, d_negcache_ttl;
- uint16_t d_qtype;
+ uint16_t d_qtype{0};
- bool d_negcached;
- bool d_cached;
+ bool d_negcached{false};
+ bool d_cached{false};
static AtomicCounter* s_backendQueries;
static bool d_go;
- bool d_stale;
+ bool d_stale{false};
static bool s_doANYLookupsOnly;
- int cacheHas(const Question &q, vector<DNSZoneRecord> &rrs);
- void addNegCache(const Question &q);
- void addCache(const Question &q, vector<DNSZoneRecord>&& rrs);
+ enum CacheResult
+ {
+ Miss = -1,
+ NegativeMatch = 0,
+ Hit = 1,
+ };
+
+ CacheResult cacheHas(const Question& question, vector<DNSZoneRecord>& resourceRecords) const;
+ void addNegCache(const Question& question) const;
+ void addCache(const Question& question, vector<DNSZoneRecord>&& rrs) const;
+
+ bool fillSOAFromZoneRecord(DNSName& shorter, int zoneId, SOAData* soaData);
+ CacheResult fillSOAFromCache(SOAData* soaData, DNSName& shorter);
};
if (initgroups(pw->pw_name, gid)<0) {
int err = errno;
SLOG(g_log<<Logger::Critical<<"Unable to set supplementary groups: "<<stringerror(err)<<endl,
- g_slog->withName("runtime")->error(Logr::Critical, err, "Unable to drop supplementary groups"));
+ g_slog->withName("runtime")->error(Logr::Critical, err, "Unable to set supplementary groups"));
exit(1);
}
}
if(setuid(uid)<0) {
int err = errno;
SLOG(g_log<<Logger::Critical<<"Unable to set effective user id to "<<uid<<": "<<stringerror(err)<<endl,
- g_slog->withName("runtime")->error(Logr::Error, err, "Unable to set effective user id", "uid", Logging::Loggable(uid)));
+ g_slog->withName("runtime")->error(Logr::Critical, err, "Unable to set effective user id", "uid", Logging::Loggable(uid)));
exit(1);
}
else {
SLOG(g_log<<Logger::Info<<"Set effective user id to "<<uid<<endl,
- g_slog->withName("runtime")->info("Set effective user", "uid", Logging::Loggable(uid)));
+ g_slog->withName("runtime")->info(Logr::Info, "Set effective user", "uid", Logging::Loggable(uid)));
}
}
}
return ::gettimeofday(tv, nullptr);
}
-// Sets the random seed.
-void Utility::srandom()
-{
- struct timeval tv;
- gettimeofday(&tv, nullptr);
- ::srandom(tv.tv_sec ^ tv.tv_usec ^ getpid());
-}
-
// Writes a vector.
int Utility::writev(int socket, const iovec *vector, size_t count )
{
/* day is now the number of days since 'Jan 1 1970' */
i = 7;
+ // coverity[store_truncates_time_t]
t->tm_wday = (day + 4) % i; /* Sunday=0, Monday=1, ..., Saturday=6 */
i = 24;
Semaphore( unsigned int value = 0 );
//! Destructor.
- ~Semaphore( void );
+ ~Semaphore();
//! Posts to a semaphore.
- int post( void );
+ int post();
//! Waits for a semaphore.
- int wait( void );
+ int wait();
//! Tries to wait for a semaphore.
- int tryWait( void );
+ int tryWait();
//! Retrieves the semaphore value.
int getValue( Semaphore::sem_value_t *sval );
int timeout_usec);
//! Returns the process id of the current process.
- static pid_t getpid( void );
+ static pid_t getpid();
//! Gets the current time.
static int gettimeofday( struct timeval *tv, void *tz = NULL );
//! Writes a vector.
static int writev( Utility::sock_t socket, const iovec *vector, size_t count );
- //! Sets the random seed.
- static void srandom(void);
-
//! Drops the program's group privileges.
static void dropGroupPrivs( uid_t uid, gid_t gid );
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
#include "validate.hh"
#include "misc.hh"
#include "dnssecinfra.hh"
time_t g_signatureInceptionSkew{0};
uint16_t g_maxNSEC3Iterations{0};
+uint16_t g_maxRRSIGsPerRecordToConsider{0};
+uint16_t g_maxNSEC3sPerRecordToConsider{0};
+uint16_t g_maxDNSKEYsToConsider{0};
+uint16_t g_maxDSsToConsider{0};
static bool isAZoneKey(const DNSKEYRecordContent& key)
{
return ret;
}
-bool isCoveredByNSEC3Hash(const std::string& h, const std::string& beginHash, const std::string& nextHash)
+bool isCoveredByNSEC3Hash(const std::string& hash, const std::string& beginHash, const std::string& nextHash)
{
- return ((beginHash < h && h < nextHash) || // no wrap BEGINNING --- HASH -- END
- (nextHash > h && beginHash > nextHash) || // wrap HASH --- END --- BEGINNING
- (nextHash < beginHash && beginHash < h) || // wrap other case END --- BEGINNING --- HASH
- (beginHash == nextHash && h != beginHash)); // "we have only 1 NSEC3 record, LOL!"
+ return ((beginHash < hash && hash < nextHash) || // no wrap BEGINNING --- HASH -- END
+ (nextHash > hash && beginHash > nextHash) || // wrap HASH --- END --- BEGINNING
+ (nextHash < beginHash && beginHash < hash) || // wrap other case END --- BEGINNING --- HASH
+ (beginHash == nextHash && hash != beginHash)); // "we have only 1 NSEC3 record, LOL!"
}
-bool isCoveredByNSEC3Hash(const DNSName& h, const DNSName& beginHash, const DNSName& nextHash)
+bool isCoveredByNSEC3Hash(const DNSName& name, const DNSName& beginHash, const DNSName& nextHash)
{
- return ((beginHash.canonCompare(h) && h.canonCompare(nextHash)) || // no wrap BEGINNING --- HASH -- END
- (h.canonCompare(nextHash) && nextHash.canonCompare(beginHash)) || // wrap HASH --- END --- BEGINNING
- (nextHash.canonCompare(beginHash) && beginHash.canonCompare(h)) || // wrap other case END --- BEGINNING --- HASH
- (beginHash == nextHash && h != beginHash)); // "we have only 1 NSEC3 record, LOL!"
+ return ((beginHash.canonCompare(name) && name.canonCompare(nextHash)) || // no wrap BEGINNING --- HASH -- END
+ (name.canonCompare(nextHash) && nextHash.canonCompare(beginHash)) || // wrap HASH --- END --- BEGINNING
+ (nextHash.canonCompare(beginHash) && beginHash.canonCompare(name)) || // wrap other case END --- BEGINNING --- HASH
+ (beginHash == nextHash && name != beginHash)); // "we have only 1 NSEC3 record, LOL!"
}
bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next)
return begin.canonCompare(name) && next != name && next.isPartOf(name);
}
-using nsec3HashesCache = std::map<std::tuple<DNSName, std::string, uint16_t>, std::string>;
-
-static std::string getHashFromNSEC3(const DNSName& qname, const NSEC3RecordContent& nsec3, nsec3HashesCache& cache)
+[[nodiscard]] std::string getHashFromNSEC3(const DNSName& qname, uint16_t iterations, const std::string& salt, pdns::validation::ValidationContext& context)
{
std::string result;
- if (g_maxNSEC3Iterations && nsec3.d_iterations > g_maxNSEC3Iterations) {
+ if (g_maxNSEC3Iterations != 0 && iterations > g_maxNSEC3Iterations) {
return result;
}
- auto key = std::make_tuple(qname, nsec3.d_salt, nsec3.d_iterations);
- auto it = cache.find(key);
- if (it != cache.end())
- {
- return it->second;
+ auto key = std::tuple(qname, salt, iterations);
+ auto iter = context.d_nsec3Cache.find(key);
+ if (iter != context.d_nsec3Cache.end()) {
+ return iter->second;
}
- result = hashQNameWithSalt(nsec3.d_salt, nsec3.d_iterations, qname);
- cache[key] = result;
+ if (context.d_nsec3IterationsRemainingQuota < iterations) {
+ // we throw here because we cannot take the risk that the result
+ // be cached, since a different query can try to validate the
+ // same result with a bigger NSEC3 iterations quota
+ throw pdns::validation::TooManySEC3IterationsException();
+ }
+
+ result = hashQNameWithSalt(salt, iterations, qname);
+ context.d_nsec3IterationsRemainingQuota -= iterations;
+ context.d_nsec3Cache[key] = result;
return result;
}
+[[nodiscard]] static std::string getHashFromNSEC3(const DNSName& qname, const NSEC3RecordContent& nsec3, pdns::validation::ValidationContext& context)
+{
+ return getHashFromNSEC3(qname, nsec3.d_iterations, nsec3.d_salt, context);
+}
+
/* There is no delegation at this exact point if:
- the name exists but the NS type is not set
- the name does not exist
One exception, if the name is covered by an opt-out NSEC3
it doesn't prove that an insecure delegation doesn't exist.
*/
-bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords)
+bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords, pdns::validation::ValidationContext& context)
{
- nsec3HashesCache cache;
+ uint16_t nsec3sConsidered = 0;
for (const auto& record : dsrecords) {
if (record.d_type == QType::NSEC) {
continue;
}
- const string h = getHashFromNSEC3(zone, *nsec3, cache);
- if (h.empty()) {
+ if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+ context.d_limitHit = true;
+ return false;
+ }
+ nsec3sConsidered++;
+
+ const string hash = getHashFromNSEC3(zone, *nsec3, context);
+ if (hash.empty()) {
return false;
}
const string beginHash = fromBase32Hex(record.d_name.getRawLabels()[0]);
- if (beginHash == h) {
+ if (beginHash == hash) {
return !nsec3->isSet(QType::NS);
}
- if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
+ if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
return !(nsec3->isOptOut());
}
}
*/
bool isWildcardExpanded(unsigned int labelCount, const RRSIGRecordContent& sign)
{
- if (sign.d_labels < labelCount) {
- return true;
- }
-
- return false;
+ return sign.d_labels < labelCount;
}
static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const RRSIGRecordContent& sign)
{
- if (owner.isWildcard() && (labelCount - 1) == sign.d_labels) {
- /* this is a wildcard alright, but it has not been expanded */
- return true;
- }
- return false;
+ /* this is a wildcard alright, but it has not been expanded */
+ return owner.isWildcard() && (labelCount - 1) == sign.d_labels;
}
static bool isWildcardExpandedOntoItself(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
{
const DNSName wildcard = g_wildcarddnsname + closestEncloser;
VLOG(log, qname << ": Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
- for (const auto& v : validrrsets) {
- VLOG(log, qname << ": Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
- if (v.first.second == QType::NSEC) {
- for (const auto& r : v.second.records) {
- VLOG(log, ":\t"<<r->getZoneRepresentation()<<endl);
- auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(r);
+ for (const auto& validset : validrrsets) {
+ VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
+ if (validset.first.second == QType::NSEC) {
+ for (const auto& record : validset.second.records) {
+ VLOG(log, ":\t"<<record->getZoneRepresentation()<<endl);
+ auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(record);
if (!nsec) {
continue;
}
- DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures);
+ DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
if (owner != wildcard) {
continue;
}
{
VLOG(log, qname << ": Trying to prove that there is no wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
const DNSName wildcard = g_wildcarddnsname + closestEncloser;
- for (const auto& v : validrrsets) {
- VLOG(log, qname << ": Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
- if (v.first.second == QType::NSEC) {
- for (const auto& r : v.second.records) {
- VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
- auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(r);
+ for (const auto& validset : validrrsets) {
+ VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
+ if (validset.first.second == QType::NSEC) {
+ for (const auto& records : validset.second.records) {
+ VLOG(log, qname << ":\t"<<records->getZoneRepresentation()<<endl);
+ auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(records);
if (!nsec) {
continue;
}
- const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures);
+ const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
VLOG(log, qname << ": Comparing owner: "<<owner<<" with target: "<<wildcard<<endl);
if (qname != owner && qname.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
If `wildcardExists` is not NULL, if will be set to true if a wildcard exists
for this qname but doesn't have this qtype.
*/
-static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const qtype, const cspmap_t& validrrsets, bool* wildcardExists, nsec3HashesCache& cache, const OptLog& log)
+static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const qtype, const cspmap_t& validrrsets, bool* wildcardExists, const OptLog& log, pdns::validation::ValidationContext& context)
{
auto wildcard = g_wildcarddnsname + closestEncloser;
VLOG(log, closestEncloser << ": Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype)<<endl);
- for (const auto& v : validrrsets) {
- VLOG(log, closestEncloser << ": Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
- if (v.first.second == QType::NSEC3) {
- for (const auto& r : v.second.records) {
- VLOG(log, closestEncloser << ":\t"<<r->getZoneRepresentation()<<endl);
- auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(r);
+ for (const auto& validset : validrrsets) {
+ VLOG(log, closestEncloser << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
+ if (validset.first.second == QType::NSEC3) {
+ for (const auto& records : validset.second.records) {
+ VLOG(log, closestEncloser << ":\t"<<records->getZoneRepresentation()<<endl);
+ auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(records);
if (!nsec3) {
continue;
}
- const DNSName signer = getSigner(v.second.signatures);
- if (!v.first.first.isPartOf(signer)) {
+ const DNSName signer = getSigner(validset.second.signatures);
+ if (!validset.first.first.isPartOf(signer)) {
continue;
}
- string h = getHashFromNSEC3(wildcard, *nsec3, cache);
- if (h.empty()) {
+ string hash = getHashFromNSEC3(wildcard, *nsec3, context);
+ if (hash.empty()) {
+ VLOG(log, closestEncloser << ": Unsupported hash, ignoring"<<endl);
return false;
}
- VLOG(log, closestEncloser << ":\tWildcard hash: "<<toBase32Hex(h)<<endl);
- string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+ VLOG(log, closestEncloser << ":\tWildcard hash: "<<toBase32Hex(hash)<<endl);
+ string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
VLOG(log, closestEncloser << ":\tNSEC3 hash: "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
- if (beginHash == h) {
+ if (beginHash == hash) {
VLOG(log, closestEncloser << ":\tWildcard hash matches");
- if (wildcardExists) {
+ if (wildcardExists != nullptr) {
*wildcardExists = true;
}
that (original) owner name other than DS RRs, and all RRs below that
owner name regardless of type.
*/
- if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, v.first.first, *nsec3)) {
+ if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
/* this is an "ancestor delegation" NSEC3 RR */
VLOG_NO_PREFIX(log, " BUT an ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
return false;
return false;
}
- if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
+ if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
VLOG(log, closestEncloser << ":\tWildcard hash is covered"<<endl);
return true;
}
*/
if (name.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, nsec)) {
/* this is an "ancestor delegation" NSEC RR */
- if (!(qtype == QType::DS && name == owner)) {
+ if (qtype != QType::DS || name != owner) {
VLOG_NO_PREFIX(log, "An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
return dState::NODENIAL;
}
return dState::INCONCLUSIVE;
}
+[[nodiscard]] uint64_t getNSEC3DenialProofWorstCaseIterationsCount(uint8_t maxLabels, uint16_t iterations, size_t saltLength)
+{
+ return static_cast<uint64_t>((iterations + 1U + (saltLength > 0 ? 1U : 0U))) * maxLabels;
+}
+
/*
This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3
in validrrsets.
useful when we have a positive answer synthesized from a wildcard and we only need to prove that the exact
name does not exist.
*/
-
-dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, const OptLog& log, bool needWildcardProof, unsigned int wildcardLabelsCount)
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, pdns::validation::ValidationContext& context, const OptLog& log, bool needWildcardProof, unsigned int wildcardLabelsCount) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
{
- nsec3HashesCache cache;
bool nsec3Seen = false;
if (!needWildcardProof && wildcardLabelsCount == 0) {
throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthesized from a wildcard");
}
- for (const auto& v : validrrsets) {
- VLOG(log, qname << ": Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
+ uint8_t numberOfLabelsOfParentZone{std::numeric_limits<uint8_t>::max()};
+ uint16_t nsec3sConsidered = 0;
+ for (const auto& validset : validrrsets) {
+ VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
- if (v.first.second==QType::NSEC) {
- for (const auto& r : v.second.records) {
- VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
+ if (validset.first.second==QType::NSEC) {
+ for (const auto& record : validset.second.records) {
+ VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
- if (v.second.signatures.empty()) {
+ if (validset.second.signatures.empty()) {
continue;
}
- auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(r);
+ auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(record);
if (!nsec) {
continue;
}
- const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures);
- const DNSName signer = getSigner(v.second.signatures);
- if (!v.first.first.isPartOf(signer) || !owner.isPartOf(signer) ) {
+ const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
+ const DNSName signer = getSigner(validset.second.signatures);
+ if (!validset.first.first.isPartOf(signer) || !owner.isPartOf(signer) ) {
continue;
}
*/
if (qname.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, *nsec)) {
/* this is an "ancestor delegation" NSEC RR */
- if (!(qtype == QType::DS && qname == owner)) {
+ if (qtype != QType::DS || qname != owner) {
VLOG(log, qname << ": An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
return dState::NODENIAL;
}
/* we know that the name exists (but this qtype doesn't) so except
if the answer was generated by a wildcard expansion, no wildcard
could have matched (rfc4035 section 5.4 bullet 1) */
- if (needWildcardProof && (!isWildcardExpanded(owner, v.second.signatures) || isWildcardExpandedOntoItself(owner, v.second.signatures))) {
+ if (needWildcardProof && (!isWildcardExpanded(owner, validset.second.signatures) || isWildcardExpandedOntoItself(owner, validset.second.signatures))) {
needWildcardProof = false;
}
VLOG_NO_PREFIX(log, "Denies existence of type "<<qname<<"/"<<QType(qtype)<<" by proving that "<<qname<<" is an ENT"<<endl);
return dState::NXQTYPE;
}
- else {
- /* but for a NXDOMAIN proof, this doesn't make sense! */
- VLOG_NO_PREFIX(log, "but it tries to deny the existence of "<<qname<<" by proving that "<<qname<<" is an ENT, this does not make sense!"<<endl);
- return dState::NODENIAL;
- }
+ /* but for a NXDOMAIN proof, this doesn't make sense! */
+ VLOG_NO_PREFIX(log, "but it tries to deny the existence of "<<qname<<" by proving that "<<qname<<" is an ENT, this does not make sense!"<<endl);
+ return dState::NODENIAL;
}
if (!needWildcardProof) {
return dState::NODENIAL;
}
- VLOG(log, qname << ": Did not deny existence of "<<QType(qtype)<<", "<<v.first.first<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
+ VLOG(log, qname << ": Did not deny existence of "<<QType(qtype)<<", "<<validset.first.first<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
}
- } else if(v.first.second==QType::NSEC3) {
- for (const auto& r : v.second.records) {
- VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
- auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(r);
+ } else if(validset.first.second==QType::NSEC3) {
+ for (const auto& record : validset.second.records) {
+ VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
+ auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
if (!nsec3) {
continue;
}
- if (v.second.signatures.empty()) {
+ if (validset.second.signatures.empty()) {
continue;
}
- const DNSName& hashedOwner = v.first.first;
- const DNSName signer = getSigner(v.second.signatures);
+ const DNSName& hashedOwner = validset.first.first;
+ const DNSName signer = getSigner(validset.second.signatures);
if (!hashedOwner.isPartOf(signer)) {
VLOG(log, qname << ": Owner "<<hashedOwner<<" is not part of the signer "<<signer<<", ignoring"<<endl);
continue;
}
+ numberOfLabelsOfParentZone = std::min(numberOfLabelsOfParentZone, static_cast<uint8_t>(signer.countLabels()));
if (qtype == QType::DS && !qname.isRoot() && signer == qname) {
VLOG(log, qname << ": A NSEC3 RR from the child zone cannot deny the existence of a DS"<<endl);
continue;
}
- string h = getHashFromNSEC3(qname, *nsec3, cache);
- if (h.empty()) {
+ if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+ VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+ context.d_limitHit = true;
+ return dState::NODENIAL;
+ }
+ nsec3sConsidered++;
+
+ string hash = getHashFromNSEC3(qname, *nsec3, context);
+ if (hash.empty()) {
VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
return dState::INSECURE;
}
nsec3Seen = true;
- VLOG(log, qname << ":\tquery hash: "<<toBase32Hex(h)<<endl);
+ VLOG(log, qname << ":\tquery hash: "<<toBase32Hex(hash)<<endl);
string beginHash = fromBase32Hex(hashedOwner.getRawLabels()[0]);
// If the name exists, check if the qtype is denied
- if (beginHash == h) {
+ if (beginHash == hash) {
/* The NSEC3 is either a delegation one, from the parent zone, and
* must have the NS bit set but not the SOA one, or a regular NSEC3
DNSName closestEncloser(qname);
bool found = false;
-
if (needWildcardProof) {
+ nsec3sConsidered = 0;
/* We now need to look for a NSEC3 covering the closest (provable) encloser
RFC 5155 section-7.2.1
RFC 7129 section-5.5
*/
VLOG(log, qname << ": Now looking for the closest encloser for "<<qname<<endl);
- while (found == false && closestEncloser.chopOff()) {
+ while (!found && closestEncloser.chopOff() && closestEncloser.countLabels() >= numberOfLabelsOfParentZone) {
- for(const auto& v : validrrsets) {
- if(v.first.second==QType::NSEC3) {
- for(const auto& r : v.second.records) {
- VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
- auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(r);
+ for(const auto& validset : validrrsets) {
+ if(validset.first.second==QType::NSEC3) {
+ for(const auto& record : validset.second.records) {
+ VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
+ auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
if (!nsec3) {
continue;
}
- const DNSName signer = getSigner(v.second.signatures);
- if (!v.first.first.isPartOf(signer)) {
- VLOG(log, qname << ": Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
+ const DNSName signer = getSigner(validset.second.signatures);
+ if (!validset.first.first.isPartOf(signer)) {
+ VLOG(log, qname << ": Owner "<<validset.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
continue;
}
- string h = getHashFromNSEC3(closestEncloser, *nsec3, cache);
- if (h.empty()) {
+ if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+ VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+ context.d_limitHit = true;
+ return dState::NODENIAL;
+ }
+ nsec3sConsidered++;
+
+ string hash = getHashFromNSEC3(closestEncloser, *nsec3, context);
+ if (hash.empty()) {
+ VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
return dState::INSECURE;
}
- string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+ string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
- VLOG(log, qname << ": Comparing "<<toBase32Hex(h)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl);
- if (beginHash == h) {
+ VLOG(log, qname << ": Comparing "<<toBase32Hex(hash)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl);
+ if (beginHash == hash) {
/* If the closest encloser is a delegation NS we know nothing about the names in the child zone. */
- if (isNSEC3AncestorDelegation(signer, v.first.first, *nsec3)) {
+ if (isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
continue;
}
}
}
}
- if (found == true) {
+ if (found) {
break;
}
}
bool nextCloserFound = false;
bool isOptOut = false;
- if (found == true) {
+ if (found) {
/* now that we have found the closest (provable) encloser,
we can construct the next closer (RFC7129 section-5.5) name
and look for a NSEC3 RR covering it */
if (labelIdx >= 1) {
DNSName nextCloser(closestEncloser);
nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1));
+ nsec3sConsidered = 0;
VLOG(log, qname << ":Looking for a NSEC3 covering the next closer name "<<nextCloser<<endl);
- for(const auto& v : validrrsets) {
- if(v.first.second==QType::NSEC3) {
- for(const auto& r : v.second.records) {
- VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
- auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(r);
- if(!nsec3)
+ for (const auto& validset : validrrsets) {
+ if (validset.first.second == QType::NSEC3) {
+ for (const auto& record : validset.second.records) {
+ VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
+ auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
+ if (!nsec3) {
continue;
+ }
- string h = getHashFromNSEC3(nextCloser, *nsec3, cache);
- if (h.empty()) {
+ if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+ VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+ context.d_limitHit = true;
+ return dState::NODENIAL;
+ }
+ nsec3sConsidered++;
+
+ string hash = getHashFromNSEC3(nextCloser, *nsec3, context);
+ if (hash.empty()) {
+ VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
return dState::INSECURE;
}
- const DNSName signer = getSigner(v.second.signatures);
- if (!v.first.first.isPartOf(signer)) {
- VLOG(log, qname << ": Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
+ const DNSName signer = getSigner(validset.second.signatures);
+ if (!validset.first.first.isPartOf(signer)) {
+ VLOG(log, qname << ": Owner "<<validset.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
continue;
}
- string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+ string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
- VLOG(log, qname << ": Comparing "<<toBase32Hex(h)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
- if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
+ VLOG(log, qname << ": Comparing "<<toBase32Hex(hash)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
+ if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
VLOG(log, qname << ": Denies existence of name "<<qname<<"/"<<QType(qtype));
nextCloserFound = true;
VLOG_NO_PREFIX(log, endl);
break;
}
- VLOG(log, qname << ": Did not cover us ("<<qname<<"), start="<<v.first.first<<", us="<<toBase32Hex(h)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl);
+ VLOG(log, qname << ": Did not cover us ("<<qname<<"), start="<<validset.first.first<<", us="<<toBase32Hex(hash)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl);
}
}
if (nextCloserFound) {
if (nextCloserFound) {
bool wildcardExists = false;
/* RFC 7129 section-5.6 */
- if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists, cache, log)) {
+ if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists, log, context)) {
if (!isOptOut) {
VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype)<<endl);
return dState::NODENIAL;
if (isOptOut) {
return dState::OPTOUT;
}
- else {
- if (wildcardExists) {
- return dState::NXQTYPE;
- }
- return dState::NXDOMAIN;
+ if (wildcardExists) {
+ return dState::NXQTYPE;
}
+ return dState::NXDOMAIN;
}
// There were no valid NSEC(3) records
return sig.d_siginception - g_signatureInceptionSkew <= now;
}
-static bool checkSignatureWithKey(const DNSName& qname, time_t now, const RRSIGRecordContent& sig, const DNSKEYRecordContent& key, const std::string& msg, vState& ede, const OptLog& log)
+namespace {
+[[nodiscard]] bool checkSignatureInceptionAndExpiry(const DNSName& qname, time_t now, const RRSIGRecordContent& sig, vState& ede, const OptLog& log)
+{
+ /* rfc4035:
+ - The validator's notion of the current time MUST be less than or equal to the time listed in the RRSIG RR's Expiration field.
+ - The validator's notion of the current time MUST be greater than or equal to the time listed in the RRSIG RR's Inception field.
+ */
+ if (isRRSIGIncepted(now, sig) && isRRSIGNotExpired(now, sig)) {
+ return true;
+ }
+ ede = ((sig.d_siginception - g_signatureInceptionSkew) > now) ? vState::BogusSignatureNotYetValid : vState::BogusSignatureExpired;
+ VLOG(log, qname << ": Signature is "<<(ede == vState::BogusSignatureNotYetValid ? "not yet valid" : "expired")<<" (inception: "<<sig.d_siginception<<", inception skew: "<<g_signatureInceptionSkew<<", expiration: "<<sig.d_sigexpire<<", now: "<<now<<")"<<endl);
+ return false;
+}
+
+[[nodiscard]] bool checkSignatureWithKey(const DNSName& qname, const RRSIGRecordContent& sig, const DNSKEYRecordContent& key, const std::string& msg, vState& ede, const OptLog& log)
{
bool result = false;
try {
- /* rfc4035:
- - The validator's notion of the current time MUST be less than or equal to the time listed in the RRSIG RR's Expiration field.
- - The validator's notion of the current time MUST be greater than or equal to the time listed in the RRSIG RR's Inception field.
- */
- if (isRRSIGIncepted(now, sig) && isRRSIGNotExpired(now, sig)) {
- auto dke = DNSCryptoKeyEngine::makeFromPublicKeyString(key.d_algorithm, key.d_key);
- result = dke->verify(msg, sig.d_signature);
- VLOG(log, qname << ": Signature by key with tag "<<sig.d_tag<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig.d_algorithm)<<" was " << (result ? "" : "NOT ")<<"valid"<<endl);
- if (!result) {
- ede = vState::BogusNoValidRRSIG;
- }
+ auto dke = DNSCryptoKeyEngine::makeFromPublicKeyString(key.d_algorithm, key.d_key);
+ result = dke->verify(msg, sig.d_signature);
+ VLOG(log, qname << ": Signature by key with tag "<<sig.d_tag<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig.d_algorithm)<<" was " << (result ? "" : "NOT ")<<"valid"<<endl);
+ if (!result) {
+ ede = vState::BogusNoValidRRSIG;
}
- else {
- ede = ((sig.d_siginception - g_signatureInceptionSkew) > now) ? vState::BogusSignatureNotYetValid : vState::BogusSignatureExpired;
- VLOG(log, qname << ": Signature is "<<(ede == vState::BogusSignatureNotYetValid ? "not yet valid" : "expired")<<" (inception: "<<sig.d_siginception<<", inception skew: "<<g_signatureInceptionSkew<<", expiration: "<<sig.d_sigexpire<<", now: "<<now<<")"<<endl);
- }
}
catch (const std::exception& e) {
VLOG(log, qname << ": Could not make a validator for signature: "<<e.what()<<endl);
return result;
}
-vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, bool validateAllSigs)
+}
+
+vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, pdns::validation::ValidationContext& context, bool validateAllSigs)
{
- bool foundKey = false;
+ bool missingKey = false;
bool isValid = false;
bool allExpired = true;
bool noneIncepted = true;
+ uint16_t signaturesConsidered = 0;
- for(const auto& signature : signatures) {
+ for (const auto& signature : signatures) {
unsigned int labelCount = name.countLabels();
if (signature->d_labels > labelCount) {
VLOG(log, name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl);
continue;
}
+ vState ede = vState::Indeterminate;
+ if (!checkSignatureInceptionAndExpiry(name, now, *signature, ede, log)) {
+ if (isRRSIGIncepted(now, *signature)) {
+ noneIncepted = false;
+ }
+ if (isRRSIGNotExpired(now, *signature)) {
+ allExpired = false;
+ }
+ continue;
+ }
+
+ if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
+ VLOG(log, name<<": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
+ // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+ context.d_limitHit = true;
+ break;
+ }
+ signaturesConsidered++;
+ context.d_validationsCounter++;
+
auto keysMatchingTag = getByTag(keys, signature->d_tag, signature->d_algorithm, log);
if (keysMatchingTag.empty()) {
- VLOG(log, name<<"No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;);
+ VLOG(log, name << ": No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;);
+ missingKey = true;
continue;
}
string msg = getMessageForRRSET(name, *signature, toSign, true);
+ uint16_t dnskeysConsidered = 0;
for (const auto& key : keysMatchingTag) {
- vState ede;
- bool signIsValid = checkSignatureWithKey(name, now, *signature, *key, msg, ede, log);
- foundKey = true;
+ if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
+ VLOG(log, name << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(signature->d_tag)<<" and algorithm "<<std::to_string(signature->d_algorithm)<<", not considering the remaining ones for this signature"<<endl;);
+ if (!isValid) {
+ context.d_limitHit = true;
+ }
+ return isValid ? vState::Secure : vState::BogusNoValidRRSIG;
+ }
+ dnskeysConsidered++;
+
+ bool signIsValid = checkSignatureWithKey(name, *signature, *key, msg, ede, log);
if (signIsValid) {
isValid = true;
if (isValid) {
return vState::Secure;
}
- if (!foundKey) {
+ if (missingKey) {
return vState::BogusNoValidRRSIG;
}
if (noneIncepted) {
return vState::BogusNoValidRRSIG;
}
-// returns vState
-// should return vState, zone cut and validated keyset
-// i.e. www.7bits.nl -> insecure/7bits.nl/[]
-// www.powerdnssec.org -> secure/powerdnssec.org/[keys]
-// www.dnssec-failed.org -> bogus/dnssec-failed.org/[]
-
-cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs)
-{
- cspmap_t cspmap;
- for(const auto& rec : recs) {
- // cerr<<"res "<<rec.d_name<<"/"<<rec.d_type<<endl;
- if(rec.d_type == QType::OPT) continue;
-
- if(rec.d_type == QType::RRSIG) {
- auto rrc = getRR<RRSIGRecordContent>(rec);
- if (rrc) {
- cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(rrc);
- }
- }
- else {
- cspmap[{rec.d_name, rec.d_type}].records.insert(rec.getContent());
- }
- }
- return cspmap;
-}
-
bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res)
{
- const auto& it = anchors.find(zone);
+ const auto& iter = anchors.find(zone);
- if (it == anchors.cend()) {
+ if (iter == anchors.cend()) {
return false;
}
- res = it->second;
+ res = iter->second;
return true;
}
bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason)
{
- const auto& it = negAnchors.find(zone);
+ const auto& iter = negAnchors.find(zone);
- if (it == negAnchors.cend()) {
+ if (iter == negAnchors.cend()) {
return false;
}
- reason = it->second;
+ reason = iter->second;
return true;
}
-vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog& log)
+vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog& log, pdns::validation::ValidationContext& context)
{
/*
* Check all DNSKEY records against all DS records and place all DNSKEY records
* that have DS records (that we support the algo for) in the tentative key storage
*/
- for (const auto& dsrc : dsmap)
- {
- auto r = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm, log);
+ uint16_t dssConsidered = 0;
+ for (const auto& dsrc : dsmap) {
+ if (g_maxDSsToConsider > 0 && dssConsidered > g_maxDSsToConsider) {
+ VLOG(log, zone << ": We have already considered "<<std::to_string(dssConsidered)<<" DS"<<addS(dssConsidered)<<", not considering the remaining ones"<<endl;);
+ return vState::BogusNoValidDNSKEY;
+ }
+ ++dssConsidered;
+
+ uint16_t dnskeysConsidered = 0;
+ auto record = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm, log);
// cerr<<"looking at DS with tag "<<dsrc.d_tag<<", algo "<<DNSSECKeeper::algorithm2name(dsrc.d_algorithm)<<", digest "<<std::to_string(dsrc.d_digesttype)<<" for "<<zone<<", got "<<r.size()<<" DNSKEYs for tag"<<endl;
- for (const auto& drc : r)
- {
+ for (const auto& drc : record) {
bool isValid = false;
bool dsCreated = false;
DSRecordContent dsrc2;
+
+ if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
+ VLOG(log, zone << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(dsrc.d_tag)<<" and algorithm "<<std::to_string(dsrc.d_algorithm)<<", not considering the remaining ones for this DS"<<endl;);
+ // we need to break because we can have a partially validated set
+ // where the KSK signs the ZSK(s), and even if we don't
+ // we are going to try to get the correct EDE status (revoked, expired, ...)
+ context.d_limitHit = true;
+ break;
+ }
+ dnskeysConsidered++;
+
try {
dsrc2 = makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype);
dsCreated = true;
// cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
// these counts could be off if we somehow ended up with
// duplicate keys. Should switch to a type that prevents that.
- if (validkeys.size() < tkeys.size())
- {
+ if (!tkeys.empty() && validkeys.size() < tkeys.size()) {
// this should mean that we have one or more DS-validated DNSKEYs
// but not a fully validated DNSKEY set, yet
// one of these valid DNSKEYs should be able to validate the
// whole set
- for (const auto& sig : sigs)
- {
+ uint16_t signaturesConsidered = 0;
+ for (const auto& sig : sigs) {
+ if (!checkSignatureInceptionAndExpiry(zone, now, *sig, ede, log)) {
+ continue;
+ }
+
// cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<getByTag(tkeys, i->d_tag).size()<<" keys of which "<<getByTag(validkeys, i->d_tag).size()<<" valid"<<endl;
auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm, log);
continue;
}
+ if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
+ VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
+ // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+ context.d_limitHit = true;
+ return vState::BogusNoValidDNSKEY;
+ }
+
string msg = getMessageForRRSET(zone, *sig, toSign);
+ uint16_t dnskeysConsidered = 0;
for (const auto& key : bytag) {
+ if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
+ VLOG(log, zone << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(sig->d_tag)<<" and algorithm "<<std::to_string(sig->d_algorithm)<<", not considering the remaining ones for this signature"<<endl;);
+ context.d_limitHit = true;
+ return vState::BogusNoValidDNSKEY;
+ }
+ dnskeysConsidered++;
+
+ if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
+ VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
+ // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+ context.d_limitHit = true;
+ return vState::BogusNoValidDNSKEY;
+ }
// cerr<<"validating : ";
- bool signIsValid = checkSignatureWithKey(zone, now, *sig, *key, msg, ede, log);
+ bool signIsValid = checkSignatureWithKey(zone, *sig, *key, msg, ede, log);
+ signaturesConsidered++;
+ context.d_validationsCounter++;
if (signIsValid) {
VLOG(log, zone << ": Validation succeeded - whole DNSKEY set is valid"<<endl);
validkeys = tkeys;
break;
}
- else {
- VLOG(log, zone << ": Validation did not succeed!"<<endl);
- }
+ VLOG(log, zone << ": Validation did not succeed!"<<endl);
+ }
+
+ if (validkeys.size() == tkeys.size()) {
+ // we validated the whole DNSKEY set already */
+ break;
}
// if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
}
return vState::Secure;
}
-bool isSupportedDS(const DSRecordContent& ds, const OptLog& log)
+bool isSupportedDS(const DSRecordContent& dsrec, const OptLog& log)
{
- if (!DNSCryptoKeyEngine::isAlgorithmSupported(ds.d_algorithm)) {
- VLOG(log, "Discarding DS "<<ds.d_tag<<" because we don't support algorithm number "<<std::to_string(ds.d_algorithm)<<endl);
+ if (!DNSCryptoKeyEngine::isAlgorithmSupported(dsrec.d_algorithm)) {
+ VLOG(log, "Discarding DS "<<dsrec.d_tag<<" because we don't support algorithm number "<<std::to_string(dsrec.d_algorithm)<<endl);
return false;
}
- if (!DNSCryptoKeyEngine::isDigestSupported(ds.d_digesttype)) {
- VLOG(log, "Discarding DS "<<ds.d_tag<<" because we don't support digest number "<<std::to_string(ds.d_digesttype)<<endl);
+ if (!DNSCryptoKeyEngine::isDigestSupported(dsrec.d_digesttype)) {
+ VLOG(log, "Discarding DS "<<dsrec.d_tag<<" because we don't support digest number "<<std::to_string(dsrec.d_digesttype)<<endl);
return false;
}
}
}
- return DNSName();
+ return {};
}
const std::string& vStateToString(vState state)
return vStates.at(static_cast<size_t>(state));
}
-std::ostream& operator<<(std::ostream &os, const vState d)
+std::ostream& operator<<(std::ostream &ostr, const vState dstate)
{
- os<<vStateToString(d);
- return os;
+ ostr<<vStateToString(dstate);
+ return ostr;
}
-std::ostream& operator<<(std::ostream &os, const dState d)
+std::ostream& operator<<(std::ostream &ostr, const dState dstate)
{
static const std::vector<std::string> dStates = {"no denial", "inconclusive", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
- os<<dStates.at(static_cast<size_t>(d));
- return os;
+ ostr<<dStates.at(static_cast<size_t>(dstate));
+ return ostr;
}
void updateDNSSECValidationState(vState& state, const vState stateUpdate)
else if (stateUpdate == vState::NTA) {
state = vState::Insecure;
}
- else if (vStateIsBogus(stateUpdate)) {
- state = stateUpdate;
- }
- else if (state == vState::Indeterminate) {
+ else if (vStateIsBogus(stateUpdate) || state == vState::Indeterminate) {
state = stateUpdate;
}
else if (stateUpdate == vState::Insecure) {
extern time_t g_signatureInceptionSkew;
extern uint16_t g_maxNSEC3Iterations;
+extern uint16_t g_maxRRSIGsPerRecordToConsider;
+extern uint16_t g_maxNSEC3sPerRecordToConsider;
+extern uint16_t g_maxDNSKEYsToConsider;
+extern uint16_t g_maxDSsToConsider;
// 4033 5
enum class vState : uint8_t { Indeterminate, Insecure, Secure, NTA, TA, BogusNoValidDNSKEY, BogusInvalidDenial, BogusUnableToGetDSs, BogusUnableToGetDNSKEYs, BogusSelfSignedDS, BogusNoRRSIG, BogusNoValidRRSIG, BogusMissingNegativeIndication, BogusSignatureNotYetValid, BogusSignatureExpired, BogusUnsupportedDNSKEYAlgo, BogusUnsupportedDSDigestType, BogusNoZoneKeyBitSet, BogusRevokedDNSKEY, BogusInvalidDNSKEYProtocol };
// NSEC(3) results
enum class dState : uint8_t { NODENIAL, INCONCLUSIVE, NXDOMAIN, NXQTYPE, ENT, INSECURE, OPTOUT};
-std::ostream& operator<<(std::ostream &os, const vState d);
-std::ostream& operator<<(std::ostream &os, const dState d);
+std::ostream& operator<<(std::ostream &, vState);
+std::ostream& operator<<(std::ostream &, dState);
class DNSRecordOracle
{
public:
- virtual std::vector<DNSRecord> get(const DNSName& qname, uint16_t qtype)=0;
+ virtual ~DNSRecordOracle() = default;
+ DNSRecordOracle(const DNSRecordOracle&) = default;
+ DNSRecordOracle(DNSRecordOracle&&) = default;
+ DNSRecordOracle& operator=(const DNSRecordOracle&) = default;
+ DNSRecordOracle& operator=(DNSRecordOracle&&) = default;
+ virtual std::vector<DNSRecord> get(const DNSName& qname, uint16_t qtype) = 0;
};
vector<shared_ptr<const RRSIGRecordContent>> signatures;
// ponder adding a validate method that accepts a key
};
-typedef map<pair<DNSName,uint16_t>, ContentSigPair> cspmap_t;
-typedef std::set<DSRecordContent> dsmap_t;
+using cspmap_t = map<pair<DNSName, uint16_t>, ContentSigPair>;
+using dsmap_t = std::set<DSRecordContent>;
struct sharedDNSKeyRecordContentCompare
{
- bool operator() (const shared_ptr<const DNSKEYRecordContent>& a, const shared_ptr<const DNSKEYRecordContent>& b) const
+ bool operator() (const shared_ptr<const DNSKEYRecordContent>& lhs, const shared_ptr<const DNSKEYRecordContent>& rhs) const
{
- return *a < *b;
+ return *lhs < *rhs;
}
};
-typedef set<shared_ptr<const DNSKEYRecordContent>, sharedDNSKeyRecordContentCompare > skeyset_t;
+using skeyset_t = set<shared_ptr<const DNSKEYRecordContent>, sharedDNSKeyRecordContentCompare>;
+namespace pdns::validation
+{
+using Nsec3HashesCache = std::map<std::tuple<DNSName, std::string, uint16_t>, std::string>;
+
+struct ValidationContext
+{
+ Nsec3HashesCache d_nsec3Cache;
+ unsigned int d_validationsCounter{0};
+ unsigned int d_nsec3IterationsRemainingQuota{0};
+ bool d_limitHit{false};
+};
+
+class TooManySEC3IterationsException : public std::runtime_error
+{
+public:
+ TooManySEC3IterationsException(): std::runtime_error("Too many NSEC3 hash computations per query")
+ {
+ }
+};
+
+}
-vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& records, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, bool validateAllSigs=true);
+vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, pdns::validation::ValidationContext& context, bool validateAllSigs=true);
bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next);
-bool isCoveredByNSEC3Hash(const std::string& h, const std::string& beginHash, const std::string& nextHash);
-bool isCoveredByNSEC3Hash(const DNSName& h, const DNSName& beginHash, const DNSName& nextHash);
-cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs);
+bool isCoveredByNSEC3Hash(const std::string& hash, const std::string& beginHash, const std::string& nextHash);
+bool isCoveredByNSEC3Hash(const DNSName& name, const DNSName& beginHash, const DNSName& nextHash);
bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res);
bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason);
-vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog&);
-dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, const OptLog& log = std::nullopt, bool needsWildcardProof=true, unsigned int wildcardLabelsCount=0);
-bool isSupportedDS(const DSRecordContent& ds, const OptLog&);
+vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog&, pdns::validation::ValidationContext& context);
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, pdns::validation::ValidationContext& context, const OptLog& log = std::nullopt, bool needWildcardProof=true, unsigned int wildcardLabelsCount=0);
+bool isSupportedDS(const DSRecordContent& dsRecordContent, const OptLog&);
DNSName getSigner(const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures);
-bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords);
-bool isRRSIGNotExpired(const time_t now, const RRSIGRecordContent& sig);
-bool isRRSIGIncepted(const time_t now, const RRSIGRecordContent& sig);
+bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords, pdns::validation::ValidationContext& context);
+bool isRRSIGNotExpired(time_t now, const RRSIGRecordContent& sig);
+bool isRRSIGIncepted(time_t now, const RRSIGRecordContent& sig);
bool isWildcardExpanded(unsigned int labelCount, const RRSIGRecordContent& sign);
bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const RRSIGRecordContent& sign);
-void updateDNSSECValidationState(vState& state, const vState stateUpdate);
+void updateDNSSECValidationState(vState& state, vState stateUpdate);
dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner, const NSECRecordContent& nsec, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, const OptLog&);
bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const NSEC3RecordContent& nsec3);
DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures);
DNSName getClosestEncloserFromNSEC(const DNSName& name, const DNSName& owner, const DNSName& next);
+[[nodiscard]] uint64_t getNSEC3DenialProofWorstCaseIterationsCount(uint8_t maxLabels, uint16_t iterations, size_t saltLength);
+[[nodiscard]] std::string getHashFromNSEC3(const DNSName& qname, uint16_t iterations, const std::string& salt, pdns::validation::ValidationContext& context);
template <typename NSEC> bool isTypeDenied(const NSEC& nsec, const QType& type)
{
#endif
#include "logger.hh"
#include "version.hh"
+#include "dnsbackend.hh"
+
+#include <boost/algorithm/string/join.hpp>
static ProductType productType;
void showProductVersion()
{
- g_log<<Logger::Warning<<productName()<<" "<< VERSION << " (C) 2001-2022 "
+ g_log<<Logger::Warning<<productName()<<" "<< VERSION << " (C) "
"PowerDNS.COM BV" << endl;
g_log<<Logger::Warning<<"Using "<<(sizeof(unsigned long)*8)<<"-bits mode. "
"Built using " << compilerVersion()
endl;
#ifdef PDNS_MODULES
// Auth only
- g_log<<Logger::Warning<<"Built-in modules: "<<PDNS_MODULES<<endl;
+ g_log << Logger::Warning << "Built-in modules: " << PDNS_MODULES << endl;
+ const auto& modules = BackendMakers().getModules();
+ g_log << Logger::Warning << "Loaded modules: " << boost::join(modules, " ") << endl;
#endif
#ifdef PDNS_CONFIG_ARGS
#define double_escape(s) #s
*/
#pragma once
-#include <array>
-#include <cstdint>
-#include <string>
+#include <string_view>
-namespace dnsdist
+namespace pdns::views
{
-class Protocol
+
+class UnsignedCharView
{
public:
- enum typeenum : uint8_t
+ UnsignedCharView(const char* data_, size_t size_) :
+ view(data_, size_)
{
- DoUDP = 0,
- DoTCP,
- DNSCryptUDP,
- DNSCryptTCP,
- DoT,
- DoH
- };
-
- Protocol(typeenum protocol = DoUDP) :
- d_protocol(protocol)
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): No unsigned char view in C++17
+ UnsignedCharView(const unsigned char* data_, size_t size_) :
+ view(reinterpret_cast<const char*>(data_), size_)
{
- if (protocol >= s_names.size()) {
- throw std::runtime_error("Unknown protocol: '" + std::to_string(protocol) + "'");
- }
+ }
+ const unsigned char& at(std::string_view::size_type pos) const
+ {
+ return reinterpret_cast<const unsigned char&>(view.at(pos));
}
- explicit Protocol(const std::string& protocol);
-
- bool operator==(typeenum) const;
- bool operator!=(typeenum) const;
-
- const std::string& toString() const;
- const std::string& toPrettyString() const;
- bool isUDP() const;
- uint8_t toNumber() const;
+ size_t size() const
+ {
+ return view.size();
+ }
private:
- typeenum d_protocol;
-
- static constexpr size_t s_numberOfProtocols = 6;
- static const std::array<std::string, s_numberOfProtocols> s_names;
- static const std::array<std::string, s_numberOfProtocols> s_prettyNames;
+ std::string_view view;
};
+
}
#include "json.hh"
#include "uuid-utils.hh"
#include <yahttp/router.hpp>
+#include <algorithm>
+#include <unordered_set>
json11::Json HttpRequest::json()
{
handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
}
-void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler)
+void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method)
{
YaHTTP::THandlerFunction f = [=](YaHTTP::Request* req, YaHTTP::Response* resp){return bareHandlerWrapper(handler, req, resp);};
- YaHTTP::Router::Any(url, f);
-}
-
-static bool optionsHandler(HttpRequest* req, HttpResponse* resp) {
- if (req->method == "OPTIONS") {
- resp->headers["access-control-allow-origin"] = "*";
- resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key";
- resp->headers["access-control-allow-methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS";
- resp->headers["access-control-max-age"] = "3600";
- resp->status = 200;
- resp->headers["content-type"]= "text/plain";
- resp->body = "";
- return true;
- }
- return false;
+ YaHTTP::Router::Map(method, url, std::move(f));
}
void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp, bool allowPassword) {
- if (optionsHandler(req, resp)) return;
-
resp->headers["access-control-allow-origin"] = "*";
if (!d_apikey) {
}
}
-void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword) {
+void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method, bool allowPassword) {
auto f = [=](HttpRequest *req, HttpResponse* resp){apiWrapper(handler, req, resp, allowPassword);};
- registerBareHandler(url, f);
+ registerBareHandler(url, f, method);
}
void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp) {
handler(req, resp);
}
-void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler) {
+void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method) {
auto f = [=](HttpRequest *req, HttpResponse *resp){webWrapper(handler, req, resp);};
- registerBareHandler(url, f);
+ registerBareHandler(url, f, method);
}
-static void *WebServerConnectionThreadStart(const WebServer* webServer, std::shared_ptr<Socket> client) {
+static void* WebServerConnectionThreadStart(const WebServer* webServer, const std::shared_ptr<Socket>& client)
+{
setThreadName("rec/webhndlr");
const std::string msg = "Exception while serving a connection in main webserver thread";
try {
}
YaHTTP::THandlerFunction handler;
- if (!YaHTTP::Router::Route(&req, handler)) {
+ YaHTTP::RoutingResult res = YaHTTP::Router::Route(&req, handler);
+
+ if (res == YaHTTP::RouteNotFound) {
SLOG(g_log<<Logger::Debug<<req.logprefix<<"No route found for \"" << req.url.path << "\"" << endl,
log->info(Logr::Debug, "No route found"));
throw HttpNotFoundException();
}
+ if (res == YaHTTP::RouteNoMethod) {
+ throw HttpMethodNotAllowedException();
+ }
const string msg = "HTTP ISE Exception";
try {
resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
} else {
resp.headers["Content-Type"] = "text/plain; charset=utf-8";
- resp.body = what;
+ resp.body = std::move(what);
}
}
}
#endif
-void WebServer::logRequest(const HttpRequest& req, const ComboAddress& remote) const {
+void WebServer::logRequest(const HttpRequest& req, [[maybe_unused]] const ComboAddress& remote) const {
if (d_loglevel >= WebServer::LogLevel::Detailed) {
#ifdef RECURSOR
if (!g_slogStructured) {
#endif
- auto logprefix = req.logprefix;
+ const auto& logprefix = req.logprefix;
g_log<<Logger::Notice<<logprefix<<"Request details:"<<endl;
bool first = true;
}
}
+
+struct ValidChars {
+ ValidChars()
+ {
+ // letter may be signed, but we only pass positive values
+ for (auto letter : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=") {
+ set.set(letter);
+ }
+ }
+ std::bitset<127> set;
+};
+
+static const ValidChars validChars;
+
+static bool validURLChars(const string& str)
+{
+ for (auto iter = str.begin(); iter != str.end(); ++iter) {
+ if (*iter == '%') {
+ ++iter;
+ if (iter == str.end() || isxdigit(static_cast<unsigned char>(*iter)) == 0) {
+ return false;
+ }
+ ++iter;
+ if (iter == str.end() || isxdigit(static_cast<unsigned char>(*iter)) == 0) {
+ return false;
+ }
+ }
+ else if (static_cast<size_t>(*iter) >= validChars.set.size() || !validChars.set[*iter]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool WebServer::validURL(const YaHTTP::URL& url)
+{
+ bool isOK = true;
+ isOK = isOK && validURLChars(url.protocol);
+ isOK = isOK && validURLChars(url.host);
+ isOK = isOK && validURLChars(url.username);
+ isOK = isOK && validURLChars(url.password);
+ isOK = isOK && validURLChars(url.path);
+ isOK = isOK && validURLChars(url.parameters);
+ isOK = isOK && validURLChars(url.anchor);
+ return isOK;
+}
+
void WebServer::serveConnection(const std::shared_ptr<Socket>& client) const {
const auto unique = getUniqueID();
const string logprefix = d_logprefix + to_string(unique) + " ";
d_slog->error(Logr::Warning, e.what(), "Unable to parse request"));
}
+ if (!validURL(req.url)) {
+ throw PDNSException("Received request with invalid URL");
+ }
// Uses of `remote` below guarded by d_loglevel
if (d_loglevel > WebServer::LogLevel::None) {
client->getRemote(remote);
}
if (d_loglevel >= WebServer::LogLevel::Normal) {
- SLOG(g_log<<Logger::Notice<<logprefix<<remote<<" \""<<req.method<<" "<<YaHTTP::Utility::encodeURL(req.url.path)<<" HTTP/"<<req.versionStr(req.version)<<"\" "<<resp.status<<" "<<reply.size()<<endl,
+ SLOG(g_log<<Logger::Notice<<logprefix<<remote<<" \""<<req.method<<" "<<req.url.path<<" HTTP/"<<req.versionStr(req.version)<<"\" "<<resp.status<<" "<<reply.size()<<endl,
d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
"urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
"status", Logging::Loggable(resp.status), "respsize", Logging::Loggable(reply.size())));
d_server(nullptr),
d_maxbodysize(2*1024*1024)
{
+ YaHTTP::Router::Map("OPTIONS", "/<*url>", [](YaHTTP::Request *req, YaHTTP::Response *resp) {
+ // look for url in routes
+ bool seen = false;
+ std::vector<std::string> methods;
+ for(const auto& route: YaHTTP::Router::GetRoutes()) {
+ const auto& method = std::get<0>(route);
+ const auto& url = std::get<1>(route);
+ if (method == "OPTIONS") {
+ continue;
+ }
+ std::map<std::string, YaHTTP::TDelim> params;
+ if (YaHTTP::Router::Match(url, req->url, params)) {
+ methods.push_back(method);
+ seen = true;
+ }
+ }
+ if (!seen) {
+ resp->status = 404;
+ resp->body = "";
+ return;
+ }
+ methods.emplace_back("OPTIONS");
+ resp->headers["access-control-allow-origin"] = "*";
+ resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key";
+ resp->headers["access-control-allow-methods"] = boost::algorithm::join(methods, ", ");
+ resp->headers["access-control-max-age"] = "3600";
+ resp->status = 200;
+ resp->headers["content-type"]= "text/plain";
+ resp->body = "";
+ }, "OptionsHandlerRoute");
}
void WebServer::bind()
#include <string>
#include <list>
#include <boost/utility.hpp>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Woverloaded-virtual"
#include <yahttp/yahttp.hpp>
+#pragma GCC diagnostic pop
#include "json11.hpp"
d_server_socket.bind(d_local);
d_server_socket.listen();
}
- virtual ~Server() { };
+ virtual ~Server() = default;
ComboAddress d_local;
{
public:
WebServer(string listenaddress, int port);
- virtual ~WebServer() { };
+ virtual ~WebServer() = default;
#ifdef RECURSOR
void setSLog(Logr::log_t log)
d_acl = nmg;
}
+ static bool validURL(const YaHTTP::URL& url);
+
void bind();
void go();
void handleRequest(HttpRequest& request, HttpResponse& resp) const;
typedef std::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
- void registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword=false);
- void registerWebHandler(const string& url, const HandlerFunction& handler);
+ void registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method = "", bool allowPassword=false);
+ void registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method = "");
enum class LogLevel : uint8_t {
None = 0, // No logs from requests at all
#endif
protected:
- void registerBareHandler(const string& url, const HandlerFunction& handler);
+ static void registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method);
void logRequest(const HttpRequest& req, const ComboAddress& remote) const;
void logResponse(const HttpResponse& resp, const ComboAddress& remote, const string& logprefix) const;
#include "responsestats.hh"
#include "statbag.hh"
#endif
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
+#include <cstdio>
+#include <cstring>
+#include <cctype>
#include <sys/types.h>
#include <iomanip>
* If s2 is empty, the function returns s1.
*/
-static char *
-strcasestr(const char *s1, const char *s2)
+static char*
+strcasestr(const char* s1, const char* s2)
{
- int *cm = __trans_lower;
- const uchar_t *us1 = (const uchar_t *)s1;
- const uchar_t *us2 = (const uchar_t *)s2;
- const uchar_t *tptr;
- int c;
-
- if (us2 == NULL || *us2 == '\0')
- return ((char *)us1);
-
- c = cm[*us2];
- while (*us1 != '\0') {
- if (c == cm[*us1++]) {
- tptr = us1;
- while (cm[c = *++us2] == cm[*us1++] && c != '\0')
- continue;
- if (c == '\0')
- return ((char *)tptr - 1);
- us1 = tptr;
- us2 = (const uchar_t *)s2;
- c = cm[*us2];
- }
- }
+ int* cm = __trans_lower;
+ const uchar_t* us1 = (const uchar_t*)s1;
+ const uchar_t* us2 = (const uchar_t*)s2;
+ const uchar_t* tptr;
+ int c;
+
+ if (us2 == NULL || *us2 == '\0')
+ return ((char*)us1);
+
+ c = cm[*us2];
+ while (*us1 != '\0') {
+ if (c == cm[*us1++]) {
+ tptr = us1;
+ while (cm[c = *++us2] == cm[*us1++] && c != '\0')
+ continue;
+ if (c == '\0')
+ return ((char*)tptr - 1);
+ us1 = tptr;
+ us2 = (const uchar_t*)s2;
+ c = cm[*us2];
+ }
+ }
- return (NULL);
+ return (NULL);
}
#endif // HAVE_STRCASESTR
-static Json getServerDetail() {
- return Json::object {
- { "type", "Server" },
- { "id", "localhost" },
- { "url", "/api/v1/servers/localhost" },
- { "daemon_type", productTypeApiType() },
- { "version", getPDNSVersion() },
- { "config_url", "/api/v1/servers/localhost/config{/config_setting}" },
- { "zones_url", "/api/v1/servers/localhost/zones{/zone}" },
+static Json getServerDetail()
+{
+ return Json::object{
+ {"type", "Server"},
+ {"id", "localhost"},
+ {"url", "/api/v1/servers/localhost"},
+ {"daemon_type", productTypeApiType()},
+ {"version", getPDNSVersion()},
+ {"config_url", "/api/v1/servers/localhost/config{/config_setting}"},
+ {"zones_url", "/api/v1/servers/localhost/zones{/zone}"},
#ifndef RECURSOR
- { "autoprimaries_url", "/api/v1/servers/localhost/autoprimaries{/autoprimary}" }
+ {"autoprimaries_url", "/api/v1/servers/localhost/autoprimaries{/autoprimary}"}
#endif
};
}
/* Return information about the supported API versions.
* The format of this MUST NEVER CHANGE at it's not versioned.
*/
-void apiDiscovery(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
-
- Json version1 = Json::object {
- { "version", 1 },
- { "url", "/api/v1" }
- };
- Json doc = Json::array { version1 };
+void apiDiscovery(HttpRequest* /* req */, HttpResponse* resp)
+{
+ Json version1 = Json::object{
+ {"version", 1},
+ {"url", "/api/v1"}};
+ Json doc = Json::array{std::move(version1)};
resp->setJsonBody(doc);
}
-void apiDiscoveryV1(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
-
- Json version1 = Json::object {
- { "server_url", "/api/v1/servers{/server}" },
- { "api_features", Json::array {} }
- };
- Json doc = Json::array { version1 };
+void apiDiscoveryV1(HttpRequest* /* req */, HttpResponse* resp)
+{
+ const Json& version1 = Json::object{
+ {"server_url", "/api/v1/servers{/server}"},
+ {"api_features", Json::array{}}};
+ const Json& doc = Json::array{version1};
resp->setJsonBody(doc);
-
}
-void apiServer(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
-
- Json doc = Json::array {getServerDetail()};
+void apiServer(HttpRequest* /* req */, HttpResponse* resp)
+{
+ const Json& doc = Json::array{getServerDetail()};
resp->setJsonBody(doc);
}
-void apiServerDetail(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
-
+void apiServerDetail(HttpRequest* /* req */, HttpResponse* resp)
+{
resp->setJsonBody(getServerDetail());
}
-void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
-
- vector<string> items = ::arg().list();
+void apiServerConfig(HttpRequest* /* req */, HttpResponse* resp)
+{
+ const vector<string>& items = ::arg().list();
string value;
Json::array doc;
- for(const string& item : items) {
- if(item.find("password") != string::npos || item.find("api-key") != string::npos)
+ for (const string& item : items) {
+ if (item.find("password") != string::npos || item.find("api-key") != string::npos) {
value = "***";
- else
+ }
+ else {
value = ::arg()[item];
+ }
- doc.push_back(Json::object {
- { "type", "ConfigSetting" },
- { "name", item },
- { "value", value },
+ doc.push_back(Json::object{
+ {"type", "ConfigSetting"},
+ {"name", item},
+ {"value", value},
});
}
resp->setJsonBody(doc);
}
-void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
-
+void apiServerStatistics(HttpRequest* req, HttpResponse* resp)
+{
Json::array doc;
string name = req->getvars["statistic"];
if (!name.empty()) {
- auto stat = productServerStatisticsFetch(name);
+ const auto& stat = productServerStatisticsFetch(name);
if (!stat) {
throw ApiException("Unknown statistic name");
}
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", name },
- { "value", std::to_string(*stat) },
+ doc.push_back(Json::object{
+ {"type", "StatisticItem"},
+ {"name", name},
+ {"value", std::to_string(*stat)},
});
resp->setJsonBody(doc);
stat_items_t general_stats;
productServerStatisticsFetch(general_stats);
- for(const auto& item : general_stats) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", item.second },
+ for (const auto& item : general_stats) {
+ doc.push_back(Json::object{
+ {"type", "StatisticItem"},
+ {"name", item.first},
+ {"value", item.second},
});
}
#endif
{
Json::array values;
- for(const auto& item : resp_qtype_stats) {
- if (item.second == 0)
+ for (const auto& item : resp_qtype_stats) {
+ if (item.second == 0) {
continue;
- values.push_back(Json::object {
- { "name", DNSRecordContent::NumberToType(item.first) },
- { "value", std::to_string(item.second) },
+ }
+ values.push_back(Json::object{
+ {"name", DNSRecordContent::NumberToType(item.first)},
+ {"value", std::to_string(item.second)},
});
}
- doc.push_back(Json::object {
- { "type", "MapStatisticItem" },
- { "name", "response-by-qtype" },
- { "value", values },
+ doc.push_back(Json::object{
+ {"type", "MapStatisticItem"},
+ {"name", "response-by-qtype"},
+ {"value", values},
});
}
{
Json::array values;
- for(const auto& item : resp_size_stats) {
- if (item.second == 0)
+ for (const auto& item : resp_size_stats) {
+ if (item.second == 0) {
continue;
+ }
- values.push_back(Json::object {
- { "name", std::to_string(item.first) },
- { "value", std::to_string(item.second) },
+ values.push_back(Json::object{
+ {"name", std::to_string(item.first)},
+ {"value", std::to_string(item.second)},
});
}
- doc.push_back(Json::object {
- { "type", "MapStatisticItem" },
- { "name", "response-sizes" },
- { "value", values },
+ doc.push_back(Json::object{
+ {"type", "MapStatisticItem"},
+ {"name", "response-sizes"},
+ {"value", values},
});
}
{
Json::array values;
- for(const auto& item : resp_rcode_stats) {
- if (item.second == 0)
+ for (const auto& item : resp_rcode_stats) {
+ if (item.second == 0) {
continue;
- values.push_back(Json::object {
- { "name", RCode::to_s(item.first) },
- { "value", std::to_string(item.second) },
+ }
+
+ values.push_back(Json::object{
+ {"name", RCode::to_s(item.first)},
+ {"value", std::to_string(item.second)},
});
}
- doc.push_back(Json::object {
- { "type", "MapStatisticItem" },
- { "name", "response-by-rcode" },
- { "value", values },
+ doc.push_back(Json::object{
+ {"type", "MapStatisticItem"},
+ {"name", "response-by-rcode"},
+ {"value", values},
});
}
#ifndef RECURSOR
- if (!req->getvars.count("includerings") ||
- req->getvars["includerings"] != "false") {
- for(const auto& ringName : S.listRings()) {
+ if ((req->getvars.count("includerings") == 0) || req->getvars["includerings"] != "false") {
+ for (const auto& ringName : S.listRings()) {
Json::array values;
const auto& ring = S.getRing(ringName);
- for(const auto& item : ring) {
- if (item.second == 0)
+ for (const auto& item : ring) {
+ if (item.second == 0) {
continue;
+ }
- values.push_back(Json::object {
- { "name", item.first },
- { "value", std::to_string(item.second) },
+ values.push_back(Json::object{
+ {"name", item.first},
+ {"value", std::to_string(item.second)},
});
}
- doc.push_back(Json::object {
- { "type", "RingStatisticItem" },
- { "name", ringName },
- { "size", std::to_string(S.getRingSize(ringName)) },
- { "value", values },
+ doc.push_back(Json::object{
+ {"type", "RingStatisticItem"},
+ {"name", ringName},
+ {"size", std::to_string(S.getRingSize(ringName))},
+ {"value", values},
});
}
}
resp->setJsonBody(doc);
}
-DNSName apiNameToDNSName(const string& name) {
+DNSName apiNameToDNSName(const string& name)
+{
if (!isCanonical(name)) {
throw ApiException("DNS Name '" + name + "' is not canonical");
}
try {
return DNSName(name);
- } catch (...) {
+ }
+ catch (...) {
throw ApiException("Unable to parse DNS Name '" + name + "'");
}
}
-DNSName apiZoneIdToName(const string& id) {
+DNSName apiZoneIdToName(const string& identifier)
+{
string zonename;
- ostringstream ss;
+ ostringstream outputStringStream;
- if(id.empty())
+ if (identifier.empty()) {
throw HttpBadRequestException();
+ }
- std::size_t lastpos = 0, pos = 0;
- while ((pos = id.find('=', lastpos)) != string::npos) {
- ss << id.substr(lastpos, pos-lastpos);
- char c;
+ std::size_t lastpos = 0;
+ std::size_t pos = 0;
+ while ((pos = identifier.find('=', lastpos)) != string::npos) {
+ outputStringStream << identifier.substr(lastpos, pos - lastpos);
+ char currentChar{};
// decode tens
- if (id[pos+1] >= '0' && id[pos+1] <= '9') {
- c = id[pos+1] - '0';
- } else if (id[pos+1] >= 'A' && id[pos+1] <= 'F') {
- c = id[pos+1] - 'A' + 10;
- } else {
+ if (identifier[pos + 1] >= '0' && identifier[pos + 1] <= '9') {
+ currentChar = static_cast<char>(identifier[pos + 1] - '0');
+ }
+ else if (identifier[pos + 1] >= 'A' && identifier[pos + 1] <= 'F') {
+ currentChar = static_cast<char>(identifier[pos + 1] - 'A' + 10);
+ }
+ else {
throw HttpBadRequestException();
}
- c = c * 16;
+ currentChar = static_cast<char>(currentChar * 16);
// decode unit place
- if (id[pos+2] >= '0' && id[pos+2] <= '9') {
- c += id[pos+2] - '0';
- } else if (id[pos+2] >= 'A' && id[pos+2] <= 'F') {
- c += id[pos+2] - 'A' + 10;
- } else {
+ if (identifier[pos + 2] >= '0' && identifier[pos + 2] <= '9') {
+ currentChar = static_cast<char>(currentChar + identifier[pos + 2] - '0');
+ }
+ else if (identifier[pos + 2] >= 'A' && identifier[pos + 2] <= 'F') {
+ currentChar = static_cast<char>(currentChar + identifier[pos + 2] - 'A' + 10);
+ }
+ else {
throw HttpBadRequestException();
}
- ss << c;
+ outputStringStream << currentChar;
- lastpos = pos+3;
+ lastpos = pos + 3;
}
if (lastpos < pos) {
- ss << id.substr(lastpos, pos-lastpos);
+ outputStringStream << identifier.substr(lastpos, pos - lastpos);
}
- zonename = ss.str();
+ zonename = outputStringStream.str();
try {
return DNSName(zonename);
- } catch (...) {
+ }
+ catch (...) {
throw ApiException("Unable to parse DNS Name '" + zonename + "'");
}
}
-string apiZoneNameToId(const DNSName& dname) {
- string name=dname.toString();
- ostringstream ss;
-
- for(char iter : name) {
- if ((iter >= 'A' && iter <= 'Z') ||
- (iter >= 'a' && iter <= 'z') ||
- (iter >= '0' && iter <= '9') ||
- (iter == '.') || (iter == '-')) {
- ss << iter;
- } else {
- ss << (boost::format("=%02X") % (int)iter);
+string apiZoneNameToId(const DNSName& dname)
+{
+ string name = dname.toString();
+ ostringstream outputStringStream;
+
+ for (char iter : name) {
+ if ((iter >= 'A' && iter <= 'Z') || (iter >= 'a' && iter <= 'z') || (iter >= '0' && iter <= '9') || (iter == '.') || (iter == '-')) {
+ outputStringStream << iter;
+ }
+ else {
+ outputStringStream << (boost::format("=%02X") % (int)iter);
}
}
- string id = ss.str();
+ string identifier = outputStringStream.str();
// add trailing dot
- if (id.size() == 0 || id.substr(id.size()-1) != ".") {
- id += ".";
+ if (identifier.empty() || identifier.substr(identifier.size() - 1) != ".") {
+ identifier += ".";
}
// special handling for the root zone, as a dot on it's own doesn't work
// everywhere.
- if (id == ".") {
- id = (boost::format("=%02X") % (int)('.')).str();
+ if (identifier == ".") {
+ identifier = (boost::format("=%02X") % (int)('.')).str();
}
- return id;
+ return identifier;
}
-void apiCheckNameAllowedCharacters(const string& name) {
- if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos)
- throw ApiException("Name '"+name+"' contains unsupported characters");
+void apiCheckNameAllowedCharacters(const string& name)
+{
+ if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos) {
+ throw ApiException("Name '" + name + "' contains unsupported characters");
+ }
}
-void apiCheckQNameAllowedCharacters(const string& qname) {
- if (qname.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname.substr(2));
- else apiCheckNameAllowedCharacters(qname);
+void apiCheckQNameAllowedCharacters(const string& qname)
+{
+ if (qname.compare(0, 2, "*.") == 0) {
+ apiCheckNameAllowedCharacters(qname.substr(2));
+ }
+ else {
+ apiCheckNameAllowedCharacters(qname);
+ }
}
void apiServerStatistics(HttpRequest* req, HttpResponse* resp);
// helpers
-DNSName apiZoneIdToName(const string& id);
+DNSName apiZoneIdToName(const string& identifier);
string apiZoneNameToId(const DNSName& name);
void apiCheckNameAllowedCharacters(const string& name);
void apiCheckQNameAllowedCharacters(const string& name);
DNSName apiNameToDNSName(const string& name);
// To be provided by product code.
-void productServerStatisticsFetch(std::map<string,string>& out);
+void productServerStatisticsFetch(std::map<string, string>& out);
std::optional<uint64_t> productServerStatisticsFetch(const std::string& name);
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "dnsbackend.hh"
+#include "webserver.hh"
+#include <array>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
using json11::Json;
-extern StatBag S;
+Ewma::Ewma() { dt.set(); }
-static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp);
+void Ewma::submit(int val)
+{
+ int rate = val - d_last;
+ double difft = dt.udiff() / 1000000.0;
+ dt.set();
+
+ d_10 = ((600.0 - difft) * d_10 + (difft * rate)) / 600.0;
+ d_5 = ((300.0 - difft) * d_5 + (difft * rate)) / 300.0;
+ d_1 = ((60.0 - difft) * d_1 + (difft * rate)) / 60.0;
+ d_max = max(d_1, d_max);
+
+ d_last = val;
+}
+
+double Ewma::get10() const
+{
+ return d_10;
+}
+
+double Ewma::get5() const
+{
+ return d_5;
+}
+
+double Ewma::get1() const
+{
+ return d_1;
+}
+
+double Ewma::getMax() const
+{
+ return d_max;
+}
+
+static void patchZone(UeberBackend& backend, const DNSName& zonename, DomainInfo& domainInfo, HttpRequest* req, HttpResponse* resp);
// QTypes that MUST NOT have multiple records of the same type in a given RRset.
-static const std::set<uint16_t> onlyOneEntryTypes = { QType::CNAME, QType::DNAME, QType::SOA };
+static const std::set<uint16_t> onlyOneEntryTypes = {QType::CNAME, QType::DNAME, QType::SOA};
// QTypes that MUST NOT be used with any other QType on the same name.
-static const std::set<uint16_t> exclusiveEntryTypes = { QType::CNAME };
+static const std::set<uint16_t> exclusiveEntryTypes = {QType::CNAME};
// QTypes that MUST be at apex.
static const std::set<uint16_t> atApexTypes = {QType::SOA, QType::DNSKEY};
// QTypes that are NOT allowed at apex.
}
}
-void AuthWebServer::go()
+void AuthWebServer::go(StatBag& stats)
{
S.doRings();
- std::thread webT([this](){webThread();});
+ std::thread webT([this]() { webThread(); });
webT.detach();
- std::thread statT([this](){statThread();});
+ std::thread statT([this, &stats]() { statThread(stats); });
statT.detach();
}
-void AuthWebServer::statThread()
+void AuthWebServer::statThread(StatBag& stats)
{
try {
setThreadName("pdns/statHelper");
- for(;;) {
- d_queries.submit(S.read("udp-queries"));
- d_cachehits.submit(S.read("packetcache-hit"));
- d_cachemisses.submit(S.read("packetcache-miss"));
- d_qcachehits.submit(S.read("query-cache-hit"));
- d_qcachemisses.submit(S.read("query-cache-miss"));
+ for (;;) {
+ d_queries.submit(static_cast<int>(stats.read("udp-queries")));
+ d_cachehits.submit(static_cast<int>(stats.read("packetcache-hit")));
+ d_cachemisses.submit(static_cast<int>(stats.read("packetcache-miss")));
+ d_qcachehits.submit(static_cast<int>(stats.read("query-cache-hit")));
+ d_qcachemisses.submit(static_cast<int>(stats.read("query-cache-miss")));
Utility::sleep(1);
}
}
- catch(...) {
- g_log<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl;
+ catch (...) {
+ g_log << Logger::Error << "Webserver statThread caught an exception, dying" << endl;
_exit(1);
}
}
-static string htmlescape(const string &s) {
+static string htmlescape(const string& inputString)
+{
string result;
- for(char it : s) {
- switch (it) {
+ for (char currentChar : inputString) {
+ switch (currentChar) {
case '&':
result += "&";
break;
result += """;
break;
default:
- result += it;
+ result += currentChar;
}
}
return result;
}
-static void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
+static void printtable(ostringstream& ret, const string& ringname, const string& title, int limit = 10)
{
- int tot=0;
- int entries=0;
- vector<pair <string,unsigned int> >ring=S.getRing(ringname);
+ unsigned int tot = 0;
+ int entries = 0;
+ vector<pair<string, unsigned int>> ring = S.getRing(ringname);
- for(const auto & i : ring) {
- tot+=i.second;
+ for (const auto& entry : ring) {
+ tot += entry.second;
entries++;
}
- ret<<"<div class=\"panel\">";
- ret<<"<span class=resetring><i></i><a href=\"?resetring="<<htmlescape(ringname)<<"\">Reset</a></span>"<<endl;
- ret<<"<h2>"<<title<<"</h2>"<<endl;
- ret<<"<div class=ringmeta>";
- ret<<"<a class=topXofY href=\"?ring="<<htmlescape(ringname)<<"\">Showing: Top "<<limit<<" of "<<entries<<"</a>"<<endl;
- ret<<"<span class=resizering>Resize: ";
- unsigned int sizes[]={10,100,500,1000,10000,500000,0};
- for(int i=0;sizes[i];++i) {
- if(S.getRingSize(ringname)!=sizes[i])
- ret<<"<a href=\"?resizering="<<htmlescape(ringname)<<"&size="<<sizes[i]<<"\">"<<sizes[i]<<"</a> ";
- else
- ret<<"("<<sizes[i]<<") ";
+ ret << "<div class=\"panel\">";
+ ret << "<span class=resetring><i></i><a href=\"?resetring=" << htmlescape(ringname) << "\">Reset</a></span>" << endl;
+ ret << "<h2>" << title << "</h2>" << endl;
+ ret << "<div class=ringmeta>";
+ ret << "<a class=topXofY href=\"?ring=" << htmlescape(ringname) << "\">Showing: Top " << limit << " of " << entries << "</a>" << endl;
+ ret << "<span class=resizering>Resize: ";
+ std::vector<uint64_t> sizes{10, 100, 500, 1000, 10000, 500000, 0};
+ for (int i = 0; sizes[i] != 0; ++i) {
+ if (S.getRingSize(ringname) != sizes[i]) {
+ ret << "<a href=\"?resizering=" << htmlescape(ringname) << "&size=" << sizes[i] << "\">" << sizes[i] << "</a> ";
+ }
+ else {
+ ret << "(" << sizes[i] << ") ";
+ }
}
- ret<<"</span></div>";
+ ret << "</span></div>";
- ret<<"<table class=\"data\">";
- int printed=0;
- int total=max(1,tot);
- for(vector<pair<string,unsigned int> >::const_iterator i=ring.begin();limit && i!=ring.end();++i,--limit) {
- ret<<"<tr><td>"<<htmlescape(i->first)<<"</td><td>"<<i->second<<"</td><td align=right>"<< AuthWebServer::makePercentage(i->second*100.0/total)<<"</td>"<<endl;
- printed+=i->second;
+ ret << "<table class=\"data\">";
+ unsigned int printed = 0;
+ unsigned int total = std::max(1U, tot);
+ for (auto i = ring.begin(); limit != 0 && i != ring.end(); ++i, --limit) {
+ ret << "<tr><td>" << htmlescape(i->first) << "</td><td>" << i->second << "</td><td align=right>" << AuthWebServer::makePercentage(i->second * 100.0 / total) << "</td>" << endl;
+ printed += i->second;
+ }
+ ret << "<tr><td colspan=3></td></tr>" << endl;
+ if (printed != tot) {
+ ret << "<tr><td><b>Rest:</b></td><td><b>" << tot - printed << "</b></td><td align=right><b>" << AuthWebServer::makePercentage((tot - printed) * 100.0 / total) << "</b></td>" << endl;
}
- ret<<"<tr><td colspan=3></td></tr>"<<endl;
- if(printed!=tot)
- ret<<"<tr><td><b>Rest:</b></td><td><b>"<<tot-printed<<"</b></td><td align=right><b>"<< AuthWebServer::makePercentage((tot-printed)*100.0/total)<<"</b></td>"<<endl;
- ret<<"<tr><td><b>Total:</b></td><td><b>"<<tot<<"</b></td><td align=right><b>100%</b></td>";
- ret<<"</table></div>"<<endl;
+ ret << "<tr><td><b>Total:</b></td><td><b>" << tot << "</b></td><td align=right><b>100%</b></td>";
+ ret << "</table></div>" << endl;
}
-void AuthWebServer::printvars(ostringstream &ret)
+static void printvars(ostringstream& ret)
{
- ret<<"<div class=panel><h2>Variables</h2><table class=\"data\">"<<endl;
+ ret << "<div class=panel><h2>Variables</h2><table class=\"data\">" << endl;
- vector<string>entries=S.getEntries();
- for(const auto & entry : entries) {
- ret<<"<tr><td>"<<entry<<"</td><td>"<<S.read(entry)<<"</td><td>"<<S.getDescrip(entry)<<"</td>"<<endl;
+ vector<string> entries = S.getEntries();
+ for (const auto& entry : entries) {
+ ret << "<tr><td>" << entry << "</td><td>" << S.read(entry) << "</td><td>" << S.getDescrip(entry) << "</td>" << endl;
}
- ret<<"</table></div>"<<endl;
+ ret << "</table></div>" << endl;
}
-void AuthWebServer::printargs(ostringstream &ret)
+static void printargs(ostringstream& ret)
{
- ret<<R"(<table border=1><tr><td colspan=3 bgcolor="#0000ff"><font color="#ffffff">Arguments</font></td>)"<<endl;
+ ret << R"(<table border=1><tr><td colspan=3 bgcolor="#0000ff"><font color="#ffffff">Arguments</font></td>)" << endl;
- vector<string>entries=arg().list();
- for(const auto & entry : entries) {
- ret<<"<tr><td>"<<entry<<"</td><td>"<<arg()[entry]<<"</td><td>"<<arg().getHelp(entry)<<"</td>"<<endl;
+ vector<string> entries = arg().list();
+ for (const auto& entry : entries) {
+ ret << "<tr><td>" << entry << "</td><td>" << arg()[entry] << "</td><td>" << arg().getHelp(entry) << "</td>" << endl;
}
}
void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
{
- if(!req->getvars["resetring"].empty()) {
- if (S.ringExists(req->getvars["resetring"]))
+ if (!req->getvars["resetring"].empty()) {
+ if (S.ringExists(req->getvars["resetring"])) {
S.resetRing(req->getvars["resetring"]);
+ }
resp->status = 302;
resp->headers["Location"] = req->url.path;
return;
}
- if(!req->getvars["resizering"].empty()){
- int size=std::stoi(req->getvars["size"]);
- if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000)
+ if (!req->getvars["resizering"].empty()) {
+ int size = std::stoi(req->getvars["size"]);
+ if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000) {
S.resizeRing(req->getvars["resizering"], std::stoi(req->getvars["size"]));
+ }
resp->status = 302;
resp->headers["Location"] = req->url.path;
return;
ostringstream ret;
- ret<<"<!DOCTYPE html>"<<endl;
- ret<<"<html><head>"<<endl;
- ret<<"<title>PowerDNS Authoritative Server Monitor</title>"<<endl;
- ret<<R"(<link rel="stylesheet" href="style.css"/>)"<<endl;
- ret<<"</head><body>"<<endl;
-
- ret<<"<div class=\"row\">"<<endl;
- ret<<"<div class=\"headl columns\">";
- ret<<R"(<a href="/" id="appname">PowerDNS )"<<htmlescape(VERSION);
- if(!arg()["config-name"].empty()) {
- ret<<" ["<<htmlescape(arg()["config-name"])<<"]";
- }
- ret<<"</a></div>"<<endl;
- ret<<"<div class=\"header columns\"></div></div>";
- ret<<R"(<div class="row"><div class="all columns">)";
-
- time_t passed=time(nullptr)-g_starttime;
-
- ret<<"<p>Uptime: "<<
- humanDuration(passed)<<
- "<br>"<<endl;
-
- ret<<"Queries/second, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
- (int)d_queries.get1()<<", "<<
- (int)d_queries.get5()<<", "<<
- (int)d_queries.get10()<<". Max queries/second: "<<(int)d_queries.getMax()<<
- "<br>"<<endl;
-
- if(d_cachemisses.get10()+d_cachehits.get10()>0)
- ret<<"Cache hitrate, 1, 5, 10 minute averages: "<<
- makePercentage((d_cachehits.get1()*100.0)/((d_cachehits.get1())+(d_cachemisses.get1())))<<", "<<
- makePercentage((d_cachehits.get5()*100.0)/((d_cachehits.get5())+(d_cachemisses.get5())))<<", "<<
- makePercentage((d_cachehits.get10()*100.0)/((d_cachehits.get10())+(d_cachemisses.get10())))<<
- "<br>"<<endl;
-
- if(d_qcachemisses.get10()+d_qcachehits.get10()>0)
- ret<<"Backend query cache hitrate, 1, 5, 10 minute averages: "<<std::setprecision(2)<<
- makePercentage((d_qcachehits.get1()*100.0)/((d_qcachehits.get1())+(d_qcachemisses.get1())))<<", "<<
- makePercentage((d_qcachehits.get5()*100.0)/((d_qcachehits.get5())+(d_qcachemisses.get5())))<<", "<<
- makePercentage((d_qcachehits.get10()*100.0)/((d_qcachehits.get10())+(d_qcachemisses.get10())))<<
- "<br>"<<endl;
-
- ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
- (int)d_qcachemisses.get1()<<", "<<
- (int)d_qcachemisses.get5()<<", "<<
- (int)d_qcachemisses.get10()<<". Max queries/second: "<<(int)d_qcachemisses.getMax()<<
- "<br>"<<endl;
-
- ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
- if(req->getvars["ring"].empty()) {
+ ret << "<!DOCTYPE html>" << endl;
+ ret << "<html><head>" << endl;
+ ret << "<title>PowerDNS Authoritative Server Monitor</title>" << endl;
+ ret << R"(<link rel="stylesheet" href="style.css"/>)" << endl;
+ ret << "</head><body>" << endl;
+
+ ret << "<div class=\"row\">" << endl;
+ ret << "<div class=\"headl columns\">";
+ ret << R"(<a href="/" id="appname">PowerDNS )" << htmlescape(VERSION);
+ if (!arg()["config-name"].empty()) {
+ ret << " [" << htmlescape(arg()["config-name"]) << "]";
+ }
+ ret << "</a></div>" << endl;
+ ret << "<div class=\"header columns\"></div></div>";
+ ret << R"(<div class="row"><div class="all columns">)";
+
+ time_t passed = time(nullptr) - g_starttime;
+
+ ret << "<p>Uptime: " << humanDuration(passed) << "<br>" << endl;
+
+ ret << "Queries/second, 1, 5, 10 minute averages: " << std::setprecision(3) << (int)d_queries.get1() << ", " << (int)d_queries.get5() << ", " << (int)d_queries.get10() << ". Max queries/second: " << (int)d_queries.getMax() << "<br>" << endl;
+
+ if (d_cachemisses.get10() + d_cachehits.get10() > 0) {
+ ret << "Cache hitrate, 1, 5, 10 minute averages: " << makePercentage((d_cachehits.get1() * 100.0) / ((d_cachehits.get1()) + (d_cachemisses.get1()))) << ", " << makePercentage((d_cachehits.get5() * 100.0) / ((d_cachehits.get5()) + (d_cachemisses.get5()))) << ", " << makePercentage((d_cachehits.get10() * 100.0) / ((d_cachehits.get10()) + (d_cachemisses.get10()))) << "<br>" << endl;
+ }
+
+ if (d_qcachemisses.get10() + d_qcachehits.get10() > 0) {
+ ret << "Backend query cache hitrate, 1, 5, 10 minute averages: " << std::setprecision(2) << makePercentage((d_qcachehits.get1() * 100.0) / ((d_qcachehits.get1()) + (d_qcachemisses.get1()))) << ", " << makePercentage((d_qcachehits.get5() * 100.0) / ((d_qcachehits.get5()) + (d_qcachemisses.get5()))) << ", " << makePercentage((d_qcachehits.get10() * 100.0) / ((d_qcachehits.get10()) + (d_qcachemisses.get10()))) << "<br>" << endl;
+ }
+
+ ret << "Backend query load, 1, 5, 10 minute averages: " << std::setprecision(3) << (int)d_qcachemisses.get1() << ", " << (int)d_qcachemisses.get5() << ", " << (int)d_qcachemisses.get10() << ". Max queries/second: " << (int)d_qcachemisses.getMax() << "<br>" << endl;
+
+ ret << "Total queries: " << S.read("udp-queries") << ". Question/answer latency: " << static_cast<double>(S.read("latency")) / 1000.0 << "ms</p><br>" << endl;
+ if (req->getvars["ring"].empty()) {
auto entries = S.listRings();
- for(const auto &i: entries) {
- printtable(ret, i, S.getRingTitle(i));
+ for (const auto& entry : entries) {
+ printtable(ret, entry, S.getRingTitle(entry));
}
printvars(ret);
- if(arg().mustDo("webserver-print-arguments"))
+ if (arg().mustDo("webserver-print-arguments")) {
printargs(ret);
+ }
+ }
+ else if (S.ringExists(req->getvars["ring"])) {
+ printtable(ret, req->getvars["ring"], S.getRingTitle(req->getvars["ring"]), 100);
}
- else if(S.ringExists(req->getvars["ring"]))
- printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100);
- ret<<"</div></div>"<<endl;
- ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>© 2013 - 2022 <a href=\"https://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
- ret<<"</body></html>"<<endl;
+ ret << "</div></div>" << endl;
+ ret << "<footer class=\"row\">" << fullVersionString() << "<br>© <a href=\"https://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>" << endl;
+ ret << "</body></html>" << endl;
resp->body = ret.str();
resp->status = 200;
}
/** Helper to build a record content as needed. */
-static inline string makeRecordContent(const QType& qtype, const string& content, bool noDot) {
+static inline string makeRecordContent(const QType& qtype, const string& content, bool noDot)
+{
// noDot: for backend storage, pass true. for API users, pass false.
- auto drc = DNSRecordContent::mastermake(qtype.getCode(), QClass::IN, content);
+ auto drc = DNSRecordContent::make(qtype.getCode(), QClass::IN, content);
return drc->getZoneRepresentation(noDot);
}
/** "Normalize" record content for API consumers. */
-static inline string makeApiRecordContent(const QType& qtype, const string& content) {
+static inline string makeApiRecordContent(const QType& qtype, const string& content)
+{
return makeRecordContent(qtype, content, false);
}
/** "Normalize" record content for backend storage. */
-static inline string makeBackendRecordContent(const QType& qtype, const string& content) {
+static inline string makeBackendRecordContent(const QType& qtype, const string& content)
+{
return makeRecordContent(qtype, content, true);
}
-static Json::object getZoneInfo(const DomainInfo& di, DNSSECKeeper* dk) {
- string zoneId = apiZoneNameToId(di.zone);
- vector<string> masters;
- masters.reserve(di.masters.size());
- for(const auto& m : di.masters) {
- masters.push_back(m.toStringWithPortExcept(53));
+static Json::object getZoneInfo(const DomainInfo& domainInfo, DNSSECKeeper* dnssecKeeper)
+{
+ string zoneId = apiZoneNameToId(domainInfo.zone);
+ vector<string> primaries;
+ primaries.reserve(domainInfo.primaries.size());
+ for (const auto& primary : domainInfo.primaries) {
+ primaries.push_back(primary.toStringWithPortExcept(53));
}
auto obj = Json::object{
// id is the canonical lookup key, which doesn't actually match the name (in some cases)
{"id", zoneId},
{"url", "/api/v1/servers/localhost/zones/" + zoneId},
- {"name", di.zone.toString()},
- {"kind", di.getKindString()},
- {"catalog", (!di.catalog.empty() ? di.catalog.toString() : "")},
- {"account", di.account},
- {"masters", std::move(masters)},
- {"serial", (double)di.serial},
- {"notified_serial", (double)di.notified_serial},
- {"last_check", (double)di.last_check}};
- if (dk) {
- obj["dnssec"] = dk->isSecuredZone(di.zone);
+ {"name", domainInfo.zone.toString()},
+ {"kind", domainInfo.getKindString()},
+ {"catalog", (!domainInfo.catalog.empty() ? domainInfo.catalog.toString() : "")},
+ {"account", domainInfo.account},
+ {"masters", std::move(primaries)},
+ {"serial", (double)domainInfo.serial},
+ {"notified_serial", (double)domainInfo.notified_serial},
+ {"last_check", (double)domainInfo.last_check}};
+ if (dnssecKeeper != nullptr) {
+ obj["dnssec"] = dnssecKeeper->isSecuredZone(domainInfo.zone);
string soa_edit;
- dk->getSoaEdit(di.zone, soa_edit, false);
- obj["edited_serial"] = (double)calculateEditSOA(di.serial, soa_edit, di.zone);
+ dnssecKeeper->getSoaEdit(domainInfo.zone, soa_edit, false);
+ obj["edited_serial"] = (double)calculateEditSOA(domainInfo.serial, soa_edit, domainInfo.zone);
}
return obj;
}
-static bool shouldDoRRSets(HttpRequest* req) {
- if (req->getvars.count("rrsets") == 0 || req->getvars["rrsets"] == "true")
+static bool shouldDoRRSets(HttpRequest* req)
+{
+ if (req->getvars.count("rrsets") == 0 || req->getvars["rrsets"] == "true") {
return true;
- if (req->getvars["rrsets"] == "false")
+ }
+ if (req->getvars["rrsets"] == "false") {
return false;
- throw ApiException("'rrsets' request parameter value '"+req->getvars["rrsets"]+"' is not supported");
+ }
+
+ throw ApiException("'rrsets' request parameter value '" + req->getvars["rrsets"] + "' is not supported");
}
-static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* resp, HttpRequest* req) {
- DomainInfo di;
- if(!B.getDomainInfo(zonename, di)) {
+static void fillZone(UeberBackend& backend, const DNSName& zonename, HttpResponse* resp, HttpRequest* req)
+{
+ DomainInfo domainInfo;
+
+ if (!backend.getDomainInfo(zonename, domainInfo)) {
throw HttpNotFoundException();
}
- DNSSECKeeper dk(&B);
- Json::object doc = getZoneInfo(di, &dk);
+ DNSSECKeeper dnssecKeeper(&backend);
+ Json::object doc = getZoneInfo(domainInfo, &dnssecKeeper);
// extra stuff getZoneInfo doesn't do for us (more expensive)
string soa_edit_api;
- di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
+ domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
doc["soa_edit_api"] = soa_edit_api;
string soa_edit;
- di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
+ domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
doc["soa_edit"] = soa_edit;
string nsec3param;
bool nsec3narrowbool = false;
- bool is_secured = dk.isSecuredZone(zonename);
+ bool is_secured = dnssecKeeper.isSecuredZone(zonename);
if (is_secured) { // ignore NSEC3PARAM and NSEC3NARROW metadata present in the db for unsigned zones
- di.backend->getDomainMetadataOne(zonename, "NSEC3PARAM", nsec3param);
+ domainInfo.backend->getDomainMetadataOne(zonename, "NSEC3PARAM", nsec3param);
string nsec3narrow;
- di.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow);
+ domainInfo.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow);
if (nsec3narrow == "1") {
nsec3narrowbool = true;
}
doc["dnssec"] = is_secured;
string api_rectify;
- di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
+ domainInfo.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
doc["api_rectify"] = (api_rectify == "1");
// TSIG
- vector<string> tsig_master, tsig_slave;
- di.backend->getDomainMetadata(zonename, "TSIG-ALLOW-AXFR", tsig_master);
- di.backend->getDomainMetadata(zonename, "AXFR-MASTER-TSIG", tsig_slave);
+ vector<string> tsig_primary;
+ vector<string> tsig_secondary;
+ domainInfo.backend->getDomainMetadata(zonename, "TSIG-ALLOW-AXFR", tsig_primary);
+ domainInfo.backend->getDomainMetadata(zonename, "AXFR-MASTER-TSIG", tsig_secondary);
- Json::array tsig_master_keys;
- for (const auto& keyname : tsig_master) {
- tsig_master_keys.push_back(apiZoneNameToId(DNSName(keyname)));
+ Json::array tsig_primary_keys;
+ for (const auto& keyname : tsig_primary) {
+ tsig_primary_keys.emplace_back(apiZoneNameToId(DNSName(keyname)));
}
- doc["master_tsig_key_ids"] = tsig_master_keys;
+ doc["master_tsig_key_ids"] = tsig_primary_keys;
- Json::array tsig_slave_keys;
- for (const auto& keyname : tsig_slave) {
- tsig_slave_keys.push_back(apiZoneNameToId(DNSName(keyname)));
+ Json::array tsig_secondary_keys;
+ for (const auto& keyname : tsig_secondary) {
+ tsig_secondary_keys.emplace_back(apiZoneNameToId(DNSName(keyname)));
}
- doc["slave_tsig_key_ids"] = tsig_slave_keys;
+ doc["slave_tsig_key_ids"] = tsig_secondary_keys;
if (shouldDoRRSets(req)) {
vector<DNSResourceRecord> records;
// load all records + sort
{
- DNSResourceRecord rr;
+ DNSResourceRecord resourceRecord;
if (req->getvars.count("rrset_name") == 0) {
- di.backend->list(zonename, di.id, true); // incl. disabled
- } else {
- QType qt;
+ domainInfo.backend->list(zonename, static_cast<int>(domainInfo.id), true); // incl. disabled
+ }
+ else {
+ QType qType;
if (req->getvars.count("rrset_type") == 0) {
- qt = QType::ANY;
- } else {
- qt = req->getvars["rrset_type"];
+ qType = QType::ANY;
}
- di.backend->lookup(qt, DNSName(req->getvars["rrset_name"]), di.id);
+ else {
+ qType = req->getvars["rrset_type"];
+ }
+ domainInfo.backend->lookup(qType, DNSName(req->getvars["rrset_name"]), static_cast<int>(domainInfo.id));
}
- while(di.backend->get(rr)) {
- if (!rr.qtype.getCode())
+ while (domainInfo.backend->get(resourceRecord)) {
+ if (resourceRecord.qtype.getCode() == 0) {
continue; // skip empty non-terminals
- records.push_back(rr);
+ }
+ records.push_back(resourceRecord);
}
- sort(records.begin(), records.end(), [](const DNSResourceRecord& a, const DNSResourceRecord& b) {
- /* if you ever want to update this comparison function,
- please be aware that you will also need to update the conditions in the code merging
- the records and comments below */
- if (a.qname == b.qname) {
- return b.qtype < a.qtype;
- }
- return b.qname < a.qname;
- });
+ sort(records.begin(), records.end(), [](const DNSResourceRecord& rrA, const DNSResourceRecord& rrB) {
+ /* if you ever want to update this comparison function,
+ please be aware that you will also need to update the conditions in the code merging
+ the records and comments below */
+ if (rrA.qname == rrB.qname) {
+ return rrB.qtype < rrA.qtype;
+ }
+ return rrB.qname < rrA.qname;
+ });
}
// load all comments + sort
{
Comment comment;
- di.backend->listComments(di.id);
- while(di.backend->getComment(comment)) {
+ domainInfo.backend->listComments(domainInfo.id);
+ while (domainInfo.backend->getComment(comment)) {
comments.push_back(comment);
}
- sort(comments.begin(), comments.end(), [](const Comment& a, const Comment& b) {
- /* if you ever want to update this comparison function,
- please be aware that you will also need to update the conditions in the code merging
- the records and comments below */
- if (a.qname == b.qname) {
- return b.qtype < a.qtype;
- }
- return b.qname < a.qname;
- });
+ sort(comments.begin(), comments.end(), [](const Comment& rrA, const Comment& rrB) {
+ /* if you ever want to update this comparison function,
+ please be aware that you will also need to update the conditions in the code merging
+ the records and comments below */
+ if (rrA.qname == rrB.qname) {
+ return rrB.qtype < rrA.qtype;
+ }
+ return rrB.qname < rrA.qname;
+ });
}
Json::array rrsets;
Json::array rrset_comments;
DNSName current_qname;
QType current_qtype;
- uint32_t ttl;
+ uint32_t ttl = 0;
auto rit = records.begin();
auto cit = comments.begin();
current_qname = rit->qname;
current_qtype = rit->qtype;
ttl = rit->ttl;
- } else {
+ }
+ else {
current_qname = cit->qname;
current_qtype = cit->qtype;
ttl = 0;
}
- while(rit != records.end() && rit->qname == current_qname && rit->qtype == current_qtype) {
+ while (rit != records.end() && rit->qname == current_qname && rit->qtype == current_qtype) {
ttl = min(ttl, rit->ttl);
- rrset_records.push_back(Json::object {
- { "disabled", rit->disabled },
- { "content", makeApiRecordContent(rit->qtype, rit->content) }
- });
+ rrset_records.push_back(Json::object{
+ {"disabled", rit->disabled},
+ {"content", makeApiRecordContent(rit->qtype, rit->content)}});
rit++;
}
while (cit != comments.end() && cit->qname == current_qname && cit->qtype == current_qtype) {
- rrset_comments.push_back(Json::object {
- { "modified_at", (double)cit->modified_at },
- { "account", cit->account },
- { "content", cit->content }
- });
+ rrset_comments.push_back(Json::object{
+ {"modified_at", (double)cit->modified_at},
+ {"account", cit->account},
+ {"content", cit->content}});
cit++;
}
rrset["records"] = rrset_records;
rrset["comments"] = rrset_comments;
rrset["ttl"] = (double)ttl;
- rrsets.push_back(rrset);
+ rrsets.emplace_back(rrset);
rrset.clear();
rrset_records.clear();
rrset_comments.clear();
resp->setJsonBody(doc);
}
-void productServerStatisticsFetch(map<string,string>& out)
+void productServerStatisticsFetch(map<string, string>& out)
{
vector<string> items = S.getEntries();
- for(const string& item : items) {
+ for (const string& item : items) {
out[item] = std::to_string(S.read(item));
}
}
}
-static void validateGatheredRRType(const DNSResourceRecord& rr) {
- if (rr.qtype.getCode() == QType::OPT || rr.qtype.getCode() == QType::TSIG) {
- throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.toString()+": invalid type given");
+static void validateGatheredRRType(const DNSResourceRecord& resourceRecord)
+{
+ if (resourceRecord.qtype.getCode() == QType::OPT || resourceRecord.qtype.getCode() == QType::TSIG) {
+ throw ApiException("RRset " + resourceRecord.qname.toString() + " IN " + resourceRecord.qtype.toString() + ": invalid type given");
}
}
-static void gatherRecords(const Json& container, const DNSName& qname, const QType& qtype, const int ttl, vector<DNSResourceRecord>& new_records) {
- DNSResourceRecord rr;
- rr.qname = qname;
- rr.qtype = qtype;
- rr.auth = true;
- rr.ttl = ttl;
+static void gatherRecords(const Json& container, const DNSName& qname, const QType& qtype, const uint32_t ttl, vector<DNSResourceRecord>& new_records)
+{
+ DNSResourceRecord resourceRecord;
+ resourceRecord.qname = qname;
+ resourceRecord.qtype = qtype;
+ resourceRecord.auth = true;
+ resourceRecord.ttl = ttl;
- validateGatheredRRType(rr);
+ validateGatheredRRType(resourceRecord);
const auto& items = container["records"].array_items();
- for(const auto& record : items) {
+ for (const auto& record : items) {
string content = stringFromJson(record, "content");
- rr.disabled = false;
- if(!record["disabled"].is_null()) {
- rr.disabled = boolFromJson(record, "disabled");
+ if (record.object_items().count("priority") > 0) {
+ throw std::runtime_error("`priority` element is not allowed in record");
+ }
+ resourceRecord.disabled = false;
+ if (!record["disabled"].is_null()) {
+ resourceRecord.disabled = boolFromJson(record, "disabled");
}
// validate that the client sent something we can actually parse, and require that data to be dotted.
try {
- if (rr.qtype.getCode() != QType::AAAA) {
- string tmp = makeApiRecordContent(rr.qtype, content);
+ if (resourceRecord.qtype.getCode() != QType::AAAA) {
+ string tmp = makeApiRecordContent(resourceRecord.qtype, content);
if (!pdns_iequals(tmp, content)) {
- throw std::runtime_error("Not in expected format (parsed as '"+tmp+"')");
+ throw std::runtime_error("Not in expected format (parsed as '" + tmp + "')");
}
- } else {
- struct in6_addr tmpbuf;
+ }
+ else {
+ struct in6_addr tmpbuf
+ {
+ };
if (inet_pton(AF_INET6, content.c_str(), &tmpbuf) != 1 || content.find('.') != string::npos) {
throw std::runtime_error("Invalid IPv6 address");
}
}
- rr.content = makeBackendRecordContent(rr.qtype, content);
+ resourceRecord.content = makeBackendRecordContent(resourceRecord.qtype, content);
}
- catch(std::exception& e)
- {
- throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.toString()+" '"+content+"': "+e.what());
+ catch (std::exception& e) {
+ throw ApiException("Record " + resourceRecord.qname.toString() + "/" + resourceRecord.qtype.toString() + " '" + content + "': " + e.what());
}
- new_records.push_back(rr);
+ new_records.push_back(resourceRecord);
}
}
-static void gatherComments(const Json& container, const DNSName& qname, const QType& qtype, vector<Comment>& new_comments) {
- Comment c;
- c.qname = qname;
- c.qtype = qtype;
+static void gatherComments(const Json& container, const DNSName& qname, const QType& qtype, vector<Comment>& new_comments)
+{
+ Comment comment;
+ comment.qname = qname;
+ comment.qtype = qtype;
time_t now = time(nullptr);
- for (const auto& comment : container["comments"].array_items()) {
- c.modified_at = intFromJson(comment, "modified_at", now);
- c.content = stringFromJson(comment, "content");
- c.account = stringFromJson(comment, "account");
- new_comments.push_back(c);
+ for (const auto& currentComment : container["comments"].array_items()) {
+ // FIXME 2036 issue internally in uintFromJson
+ comment.modified_at = uintFromJson(currentComment, "modified_at", now);
+ comment.content = stringFromJson(currentComment, "content");
+ comment.account = stringFromJson(currentComment, "account");
+ new_comments.push_back(comment);
}
}
-static void checkDefaultDNSSECAlgos() {
+static void checkDefaultDNSSECAlgos()
+{
int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algorithm"]);
int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algorithm"]);
int k_size = arg().asNum("default-ksk-size");
int z_size = arg().asNum("default-zsk-size");
// Sanity check DNSSEC parameters
- if (::arg()["default-zsk-algorithm"] != "") {
- if (k_algo == -1)
+ if (!::arg()["default-zsk-algorithm"].empty()) {
+ if (k_algo == -1) {
throw ApiException("default-ksk-algorithm setting is set to unknown algorithm: " + ::arg()["default-ksk-algorithm"]);
- else if (k_algo <= 10 && k_size == 0)
- throw ApiException("default-ksk-algorithm is set to an algorithm("+::arg()["default-ksk-algorithm"]+") that requires a non-zero default-ksk-size!");
+ }
+ if (k_algo <= 10 && k_size == 0) {
+ throw ApiException("default-ksk-algorithm is set to an algorithm(" + ::arg()["default-ksk-algorithm"] + ") that requires a non-zero default-ksk-size!");
+ }
}
- if (::arg()["default-zsk-algorithm"] != "") {
- if (z_algo == -1)
+ if (!::arg()["default-zsk-algorithm"].empty()) {
+ if (z_algo == -1) {
throw ApiException("default-zsk-algorithm setting is set to unknown algorithm: " + ::arg()["default-zsk-algorithm"]);
- else if (z_algo <= 10 && z_size == 0)
- throw ApiException("default-zsk-algorithm is set to an algorithm("+::arg()["default-zsk-algorithm"]+") that requires a non-zero default-zsk-size!");
+ }
+ if (z_algo <= 10 && z_size == 0) {
+ throw ApiException("default-zsk-algorithm is set to an algorithm(" + ::arg()["default-zsk-algorithm"] + ") that requires a non-zero default-zsk-size!");
+ }
}
}
-static void throwUnableToSecure(const DNSName& zonename) {
+static void throwUnableToSecure(const DNSName& zonename)
+{
throw ApiException("No backend was able to secure '" + zonename.toString() + "', most likely because no DNSSEC"
- + "capable backends are loaded, or because the backends have DNSSEC disabled. Check your configuration.");
+ + "capable backends are loaded, or because the backends have DNSSEC disabled. Check your configuration.");
+}
+
+/*
+ * Add KSK and ZSK to an existing zone. Algorithms and sizes will be chosen per configuration.
+ */
+static void addDefaultDNSSECKeys(DNSSECKeeper& dnssecKeeper, const DNSName& zonename)
+{
+ checkDefaultDNSSECAlgos();
+ int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algorithm"]);
+ int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algorithm"]);
+ int k_size = arg().asNum("default-ksk-size");
+ int z_size = arg().asNum("default-zsk-size");
+
+ if (k_algo != -1) {
+ int64_t keyID{-1};
+ if (!dnssecKeeper.addKey(zonename, true, k_algo, keyID, k_size)) {
+ throwUnableToSecure(zonename);
+ }
+ }
+
+ if (z_algo != -1) {
+ int64_t keyID{-1};
+ if (!dnssecKeeper.addKey(zonename, false, z_algo, keyID, z_size)) {
+ throwUnableToSecure(zonename);
+ }
+ }
}
-static void extractDomainInfoFromDocument(const Json& document, boost::optional<DomainInfo::DomainKind>& kind, boost::optional<vector<ComboAddress>>& masters, boost::optional<DNSName>& catalog, boost::optional<string>& account)
+static bool isZoneApiRectifyEnabled(const DomainInfo& domainInfo)
+{
+ string api_rectify;
+ domainInfo.backend->getDomainMetadataOne(domainInfo.zone, "API-RECTIFY", api_rectify);
+ if (api_rectify.empty() && ::arg().mustDo("default-api-rectify")) {
+ api_rectify = "1";
+ }
+ return api_rectify == "1";
+}
+
+static void extractDomainInfoFromDocument(const Json& document, boost::optional<DomainInfo::DomainKind>& kind, boost::optional<vector<ComboAddress>>& primaries, boost::optional<DNSName>& catalog, boost::optional<string>& account)
{
if (document["kind"].is_string()) {
kind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
- } else {
+ }
+ else {
kind = boost::none;
}
if (document["masters"].is_array()) {
- masters = vector<ComboAddress>();
- for(const auto& value : document["masters"].array_items()) {
- string master = value.string_value();
- if (master.empty())
- throw ApiException("Master can not be an empty string");
+ primaries = vector<ComboAddress>();
+ for (const auto& value : document["masters"].array_items()) {
+ string primary = value.string_value();
+ if (primary.empty()) {
+ throw ApiException("Primary can not be an empty string");
+ }
try {
- masters->emplace_back(master, 53);
- } catch (const PDNSException &e) {
- throw ApiException("Master (" + master + ") is not an IP address: " + e.reason);
+ primaries->emplace_back(primary, 53);
+ }
+ catch (const PDNSException& e) {
+ throw ApiException("Primary (" + primary + ") is not an IP address: " + e.reason);
}
}
- } else {
- masters = boost::none;
+ }
+ else {
+ primaries = boost::none;
}
if (document["catalog"].is_string()) {
if (document["account"].is_string()) {
account = document["account"].string_value();
- } else {
+ }
+ else {
account = boost::none;
}
}
+/*
+ * Build vector of TSIG Key ids from domain update document.
+ * jsonArray: JSON array element to extract TSIG key ids from.
+ * metadata: returned list of domain key ids for setDomainMetadata
+ */
+static void extractJsonTSIGKeyIds(UeberBackend& backend, const Json& jsonArray, vector<string>& metadata)
+{
+ for (const auto& value : jsonArray.array_items()) {
+ auto keyname(apiZoneIdToName(value.string_value()));
+ DNSName keyAlgo;
+ string keyContent;
+ if (!backend.getTSIGKey(keyname, keyAlgo, keyContent)) {
+ throw ApiException("A TSIG key with the name '" + keyname.toLogString() + "' does not exist");
+ }
+ metadata.push_back(keyname.toString());
+ }
+}
+
// Must be called within backend transaction.
-static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& di, const DNSName& zonename, const Json& document, bool zoneWasModified) {
+static void updateDomainSettingsFromDocument(UeberBackend& backend, DomainInfo& domainInfo, const DNSName& zonename, const Json& document, bool zoneWasModified)
+{
boost::optional<DomainInfo::DomainKind> kind;
- boost::optional<vector<ComboAddress>> masters;
+ boost::optional<vector<ComboAddress>> primaries;
boost::optional<DNSName> catalog;
boost::optional<string> account;
- extractDomainInfoFromDocument(document, kind, masters, catalog, account);
+ extractDomainInfoFromDocument(document, kind, primaries, catalog, account);
if (kind) {
- di.backend->setKind(zonename, *kind);
+ domainInfo.backend->setKind(zonename, *kind);
+ domainInfo.kind = *kind;
}
- if (masters) {
- di.backend->setMasters(zonename, *masters);
+ if (primaries) {
+ domainInfo.backend->setPrimaries(zonename, *primaries);
}
if (catalog) {
- di.backend->setCatalog(zonename, *catalog);
+ domainInfo.backend->setCatalog(zonename, *catalog);
}
if (account) {
- di.backend->setAccount(zonename, *account);
+ domainInfo.backend->setAccount(zonename, *account);
}
if (document["soa_edit_api"].is_string()) {
- di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].string_value());
+ domainInfo.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].string_value());
}
if (document["soa_edit"].is_string()) {
- di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].string_value());
+ domainInfo.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].string_value());
}
try {
bool api_rectify = boolFromJson(document, "api_rectify");
- di.backend->setDomainMetadataOne(zonename, "API-RECTIFY", api_rectify ? "1" : "0");
+ domainInfo.backend->setDomainMetadataOne(zonename, "API-RECTIFY", api_rectify ? "1" : "0");
+ }
+ catch (const JsonException&) {
}
- catch (const JsonException&) {}
-
- DNSSECKeeper dk(&B);
+ DNSSECKeeper dnssecKeeper(&backend);
bool shouldRectify = zoneWasModified;
bool dnssecInJSON = false;
bool dnssecDocVal = false;
dnssecDocVal = boolFromJson(document, "dnssec");
dnssecInJSON = true;
}
- catch (const JsonException&) {}
+ catch (const JsonException&) {
+ }
try {
nsec3paramDocVal = stringFromJson(document, "nsec3param");
nsec3paramInJSON = true;
}
- catch (const JsonException&) {}
-
+ catch (const JsonException&) {
+ }
- bool isDNSSECZone = dk.isSecuredZone(zonename);
- bool isPresigned = dk.isPresigned(zonename);
+ bool isDNSSECZone = dnssecKeeper.isSecuredZone(zonename);
+ bool isPresigned = dnssecKeeper.isPresigned(zonename);
if (dnssecInJSON) {
if (dnssecDocVal) {
if (!isDNSSECZone) {
- checkDefaultDNSSECAlgos();
-
- int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algorithm"]);
- int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algorithm"]);
- int k_size = arg().asNum("default-ksk-size");
- int z_size = arg().asNum("default-zsk-size");
-
- if (k_algo != -1) {
- int64_t id;
- if (!dk.addKey(zonename, true, k_algo, id, k_size)) {
- throwUnableToSecure(zonename);
- }
- }
-
- if (z_algo != -1) {
- int64_t id;
- if (!dk.addKey(zonename, false, z_algo, id, z_size)) {
- throwUnableToSecure(zonename);
- }
- }
+ addDefaultDNSSECKeys(dnssecKeeper, zonename);
// Used later for NSEC3PARAM
- isDNSSECZone = dk.isSecuredZone(zonename);
+ isDNSSECZone = dnssecKeeper.isSecuredZone(zonename);
if (!isDNSSECZone) {
throwUnableToSecure(zonename);
shouldRectify = true;
updateNsec3Param = true;
}
- } else {
+ }
+ else {
// "dnssec": false in json
if (isDNSSECZone) {
- string info, error;
- if (!dk.unSecureZone(zonename, error)) {
- throw ApiException("Error while un-securing zone '"+ zonename.toString()+"': " + error);
+ string info;
+ string error;
+ if (!dnssecKeeper.unSecureZone(zonename, error)) {
+ throw ApiException("Error while un-securing zone '" + zonename.toString() + "': " + error);
}
- isDNSSECZone = dk.isSecuredZone(zonename, false);
+ isDNSSECZone = dnssecKeeper.isSecuredZone(zonename, false);
if (isDNSSECZone) {
- throw ApiException("Unable to un-secure zone '"+ zonename.toString()+"'");
+ throw ApiException("Unable to un-secure zone '" + zonename.toString() + "'");
}
shouldRectify = true;
updateNsec3Param = true;
if (nsec3paramDocVal.empty()) {
// Switch to NSEC
- if (!dk.unsetNSEC3PARAM(zonename)) {
+ if (!dnssecKeeper.unsetNSEC3PARAM(zonename)) {
throw ApiException("Unable to remove NSEC3PARAMs from zone '" + zonename.toString());
}
}
else {
// Set the NSEC3PARAMs
NSEC3PARAMRecordContent ns3pr(nsec3paramDocVal);
- string error_msg = "";
- if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) {
- throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"' are invalid. " + error_msg);
+ string error_msg;
+ if (!dnssecKeeper.checkNSEC3PARAM(ns3pr, error_msg)) {
+ throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() + "' are invalid. " + error_msg);
}
- if (!dk.setNSEC3PARAM(zonename, ns3pr, boolFromJson(document, "nsec3narrow", false))) {
- throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() +
- "' passed our basic sanity checks, but cannot be used with the current backend.");
+ if (!dnssecKeeper.setNSEC3PARAM(zonename, ns3pr, boolFromJson(document, "nsec3narrow", false))) {
+ throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() + "' passed our basic sanity checks, but cannot be used with the current backend.");
}
}
}
if (shouldRectify && !isPresigned) {
// Rectify
- string api_rectify;
- di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
- if (api_rectify.empty()) {
- if (::arg().mustDo("default-api-rectify")) {
- api_rectify = "1";
- }
- }
- if (api_rectify == "1") {
+ if (isZoneApiRectifyEnabled(domainInfo)) {
string info;
string error_msg;
- if (!dk.rectifyZone(zonename, error_msg, info, false)) {
+ if (!dnssecKeeper.rectifyZone(zonename, error_msg, info, false) && !domainInfo.isSecondaryType()) {
+ // for Secondary zones, it is possible that rectifying was not needed (example: empty zone).
throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
}
}
// Increase serial
string soa_edit_api_kind;
- di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
+ domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
if (!soa_edit_api_kind.empty()) {
- SOAData sd;
- if (!B.getSOAUncached(zonename, sd))
+ SOAData soaData;
+ if (!backend.getSOAUncached(zonename, soaData)) {
return;
+ }
string soa_edit_kind;
- di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
+ domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
- DNSResourceRecord rr;
- if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
- if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+ DNSResourceRecord resourceRecord;
+ if (makeIncreasedSOARecord(soaData, soa_edit_api_kind, soa_edit_kind, resourceRecord)) {
+ if (!domainInfo.backend->replaceRRSet(domainInfo.id, resourceRecord.qname, resourceRecord.qtype, vector<DNSResourceRecord>(1, resourceRecord))) {
throw ApiException("Hosting backend does not support editing records.");
}
}
if (!document["master_tsig_key_ids"].is_null()) {
vector<string> metadata;
- for(const auto& value : document["master_tsig_key_ids"].array_items()) {
- auto keyname(apiZoneIdToName(value.string_value()));
- DNSName keyAlgo;
- string keyContent;
- if (!B.getTSIGKey(keyname, keyAlgo, keyContent)) {
- throw ApiException("A TSIG key with the name '"+keyname.toLogString()+"' does not exist");
- }
- metadata.push_back(keyname.toString());
- }
- if (!di.backend->setDomainMetadata(zonename, "TSIG-ALLOW-AXFR", metadata)) {
- throw HttpInternalServerErrorException("Unable to set new TSIG master keys for zone '" + zonename.toLogString() + "'");
+ extractJsonTSIGKeyIds(backend, document["master_tsig_key_ids"], metadata);
+ if (!domainInfo.backend->setDomainMetadata(zonename, "TSIG-ALLOW-AXFR", metadata)) {
+ throw HttpInternalServerErrorException("Unable to set new TSIG primary keys for zone '" + zonename.toLogString() + "'");
}
}
if (!document["slave_tsig_key_ids"].is_null()) {
vector<string> metadata;
- for(const auto& value : document["slave_tsig_key_ids"].array_items()) {
- auto keyname(apiZoneIdToName(value.string_value()));
- DNSName keyAlgo;
- string keyContent;
- if (!B.getTSIGKey(keyname, keyAlgo, keyContent)) {
- throw ApiException("A TSIG key with the name '"+keyname.toLogString()+"' does not exist");
- }
- metadata.push_back(keyname.toString());
- }
- if (!di.backend->setDomainMetadata(zonename, "AXFR-MASTER-TSIG", metadata)) {
- throw HttpInternalServerErrorException("Unable to set new TSIG slave keys for zone '" + zonename.toLogString() + "'");
+ extractJsonTSIGKeyIds(backend, document["slave_tsig_key_ids"], metadata);
+ if (!domainInfo.backend->setDomainMetadata(zonename, "AXFR-MASTER-TSIG", metadata)) {
+ throw HttpInternalServerErrorException("Unable to set new TSIG secondary keys for zone '" + zonename.toLogString() + "'");
}
}
}
-static bool isValidMetadataKind(const string& kind, bool readonly) {
- static vector<string> builtinOptions {
+static bool isValidMetadataKind(const string& kind, bool readonly)
+{
+ static vector<string> builtinOptions{
"ALLOW-AXFR-FROM",
"AXFR-SOURCE",
"ALLOW-DNSUPDATE-FROM",
"SLAVE-RENOTIFY",
"SOA-EDIT",
"TSIG-ALLOW-AXFR",
- "TSIG-ALLOW-DNSUPDATE"
+ "TSIG-ALLOW-DNSUPDATE",
};
// the following options do not allow modifications via API
- static vector<string> protectedOptions {
+ static vector<string> protectedOptions{
"API-RECTIFY",
"AXFR-MASTER-TSIG",
"NSEC3NARROW",
"NSEC3PARAM",
"PRESIGNED",
"LUA-AXFR-SCRIPT",
- "TSIG-ALLOW-AXFR"
+ "TSIG-ALLOW-AXFR",
};
- if (kind.find("X-") == 0)
+ if (kind.find("X-") == 0) {
return true;
+ }
bool found = false;
- for (const string& s : builtinOptions) {
- if (kind == s) {
- for (const string& s2 : protectedOptions) {
- if (!readonly && s == s2)
+ for (const string& builtinOption : builtinOptions) {
+ if (kind == builtinOption) {
+ for (const string& protectedOption : protectedOptions) {
+ if (!readonly && builtinOption == protectedOption) {
return false;
+ }
}
found = true;
break;
*/
#include "apidocfiles.h"
-void apiDocs(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
-
+void apiDocs(HttpRequest* req, HttpResponse* resp)
+{
if (req->accept_yaml) {
resp->setYamlBody(g_api_swagger_yaml);
- } else if (req->accept_json) {
+ }
+ else if (req->accept_json) {
resp->setJsonBody(g_api_swagger_json);
- } else {
+ }
+ else {
resp->setPlainBody(g_api_swagger_yaml);
}
}
-static void apiZoneMetadata(HttpRequest* req, HttpResponse *resp) {
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
- UeberBackend B;
- DomainInfo di;
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
+class ZoneData
+{
+public:
+ ZoneData(HttpRequest* req) :
+ zoneName(apiZoneIdToName((req)->parameters["id"])),
+ dnssecKeeper(DNSSECKeeper{&backend})
+ {
+ try {
+ if (!backend.getDomainInfo(zoneName, domainInfo)) {
+ throw HttpNotFoundException();
+ }
+ }
+ catch (const PDNSException& e) {
+ throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason);
+ }
}
- if (req->method == "GET") {
- map<string, vector<string> > md;
- Json::array document;
+ DNSName zoneName;
+ UeberBackend backend{};
+ DNSSECKeeper dnssecKeeper;
+ DomainInfo domainInfo{};
+};
- if (!B.getAllDomainMetadata(zonename, md))
- throw HttpNotFoundException();
+static void apiZoneMetadataGET(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
- for (const auto& i : md) {
- Json::array entries;
- for (const string& j : i.second)
- entries.push_back(j);
+ map<string, vector<string>> metas;
+ Json::array document;
- Json::object key {
- { "type", "Metadata" },
- { "kind", i.first },
- { "metadata", entries }
- };
+ if (!zoneData.backend.getAllDomainMetadata(zoneData.zoneName, metas)) {
+ throw HttpNotFoundException();
+ }
- document.push_back(key);
+ for (const auto& meta : metas) {
+ Json::array entries;
+ for (const string& value : meta.second) {
+ entries.emplace_back(value);
}
- resp->setJsonBody(document);
- } else if (req->method == "POST") {
- auto document = req->json();
- string kind;
- vector<string> entries;
+ Json::object key{
+ {"type", "Metadata"},
+ {"kind", meta.first},
+ {"metadata", entries}};
+ document.emplace_back(key);
+ }
+ resp->setJsonBody(document);
+}
- try {
- kind = stringFromJson(document, "kind");
- } catch (const JsonException&) {
- throw ApiException("kind is not specified or not a string");
- }
+static void apiZoneMetadataPOST(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+
+ const auto& document = req->json();
+ string kind;
+ vector<string> entries;
+
+ try {
+ kind = stringFromJson(document, "kind");
+ }
+ catch (const JsonException&) {
+ throw ApiException("kind is not specified or not a string");
+ }
- if (!isValidMetadataKind(kind, false))
- throw ApiException("Unsupported metadata kind '" + kind + "'");
+ if (!isValidMetadataKind(kind, false)) {
+ throw ApiException("Unsupported metadata kind '" + kind + "'");
+ }
- vector<string> vecMetadata;
+ vector<string> vecMetadata;
- if (!B.getDomainMetadata(zonename, kind, vecMetadata))
- throw ApiException("Could not retrieve metadata entries for domain '" +
- zonename.toString() + "'");
+ if (!zoneData.backend.getDomainMetadata(zoneData.zoneName, kind, vecMetadata)) {
+ throw ApiException("Could not retrieve metadata entries for domain '" + zoneData.zoneName.toString() + "'");
+ }
- auto& metadata = document["metadata"];
- if (!metadata.is_array())
- throw ApiException("metadata is not specified or not an array");
+ const auto& metadata = document["metadata"];
+ if (!metadata.is_array()) {
+ throw ApiException("metadata is not specified or not an array");
+ }
- for (const auto& i : metadata.array_items()) {
- if (!i.is_string())
- throw ApiException("metadata must be strings");
- else if (std::find(vecMetadata.cbegin(),
- vecMetadata.cend(),
- i.string_value()) == vecMetadata.cend()) {
- vecMetadata.push_back(i.string_value());
- }
+ for (const auto& value : metadata.array_items()) {
+ if (!value.is_string()) {
+ throw ApiException("metadata must be strings");
+ }
+ if (std::find(vecMetadata.cbegin(),
+ vecMetadata.cend(),
+ value.string_value())
+ == vecMetadata.cend()) {
+ vecMetadata.push_back(value.string_value());
}
+ }
- if (!B.setDomainMetadata(zonename, kind, vecMetadata))
- throw ApiException("Could not update metadata entries for domain '" +
- zonename.toString() + "'");
+ if (!zoneData.backend.setDomainMetadata(zoneData.zoneName, kind, vecMetadata)) {
+ throw ApiException("Could not update metadata entries for domain '" + zoneData.zoneName.toString() + "'");
+ }
- DNSSECKeeper::clearMetaCache(zonename);
+ DNSSECKeeper::clearMetaCache(zoneData.zoneName);
- Json::array respMetadata;
- for (const string& s : vecMetadata)
- respMetadata.push_back(s);
+ Json::array respMetadata;
+ for (const string& value : vecMetadata) {
+ respMetadata.emplace_back(value);
+ }
- Json::object key {
- { "type", "Metadata" },
- { "kind", document["kind"] },
- { "metadata", respMetadata }
- };
+ Json::object key{
+ {"type", "Metadata"},
+ {"kind", document["kind"]},
+ {"metadata", respMetadata}};
- resp->status = 201;
- resp->setJsonBody(key);
- } else
- throw HttpMethodNotAllowedException();
+ resp->status = 201;
+ resp->setJsonBody(key);
}
-static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) {
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
+static void apiZoneMetadataKindGET(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+
+ string kind = req->parameters["kind"];
+
+ vector<string> metadata;
+ Json::object document;
+ Json::array entries;
- UeberBackend B;
- DomainInfo di;
- if (!B.getDomainInfo(zonename, di)) {
+ if (!zoneData.backend.getDomainMetadata(zoneData.zoneName, kind, metadata)) {
throw HttpNotFoundException();
}
+ if (!isValidMetadataKind(kind, true)) {
+ throw ApiException("Unsupported metadata kind '" + kind + "'");
+ }
- string kind = req->parameters["kind"];
-
- if (req->method == "GET") {
- vector<string> metadata;
- Json::object document;
- Json::array entries;
+ document["type"] = "Metadata";
+ document["kind"] = kind;
- if (!B.getDomainMetadata(zonename, kind, metadata))
- throw HttpNotFoundException();
- else if (!isValidMetadataKind(kind, true))
- throw ApiException("Unsupported metadata kind '" + kind + "'");
+ for (const string& value : metadata) {
+ entries.emplace_back(value);
+ }
- document["type"] = "Metadata";
- document["kind"] = kind;
+ document["metadata"] = entries;
+ resp->setJsonBody(document);
+}
- for (const string& i : metadata)
- entries.push_back(i);
+static void apiZoneMetadataKindPUT(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
- document["metadata"] = entries;
- resp->setJsonBody(document);
- } else if (req->method == "PUT") {
- auto document = req->json();
+ string kind = req->parameters["kind"];
- if (!isValidMetadataKind(kind, false))
- throw ApiException("Unsupported metadata kind '" + kind + "'");
+ const auto& document = req->json();
- vector<string> vecMetadata;
- auto& metadata = document["metadata"];
- if (!metadata.is_array())
- throw ApiException("metadata is not specified or not an array");
+ if (!isValidMetadataKind(kind, false)) {
+ throw ApiException("Unsupported metadata kind '" + kind + "'");
+ }
- for (const auto& i : metadata.array_items()) {
- if (!i.is_string())
- throw ApiException("metadata must be strings");
- vecMetadata.push_back(i.string_value());
+ vector<string> vecMetadata;
+ const auto& metadata = document["metadata"];
+ if (!metadata.is_array()) {
+ throw ApiException("metadata is not specified or not an array");
+ }
+ for (const auto& value : metadata.array_items()) {
+ if (!value.is_string()) {
+ throw ApiException("metadata must be strings");
}
+ vecMetadata.push_back(value.string_value());
+ }
- if (!B.setDomainMetadata(zonename, kind, vecMetadata))
- throw ApiException("Could not update metadata entries for domain '" + zonename.toString() + "'");
+ if (!zoneData.backend.setDomainMetadata(zoneData.zoneName, kind, vecMetadata)) {
+ throw ApiException("Could not update metadata entries for domain '" + zoneData.zoneName.toString() + "'");
+ }
+
+ DNSSECKeeper::clearMetaCache(zoneData.zoneName);
- DNSSECKeeper::clearMetaCache(zonename);
+ Json::object key{
+ {"type", "Metadata"},
+ {"kind", kind},
+ {"metadata", metadata}};
+
+ resp->setJsonBody(key);
+}
- Json::object key {
- { "type", "Metadata" },
- { "kind", kind },
- { "metadata", metadata }
- };
+static void apiZoneMetadataKindDELETE(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
- resp->setJsonBody(key);
- } else if (req->method == "DELETE") {
- if (!isValidMetadataKind(kind, false))
- throw ApiException("Unsupported metadata kind '" + kind + "'");
+ const string& kind = req->parameters["kind"];
+ if (!isValidMetadataKind(kind, false)) {
+ throw ApiException("Unsupported metadata kind '" + kind + "'");
+ }
- vector<string> md; // an empty vector will do it
- if (!B.setDomainMetadata(zonename, kind, md))
- throw ApiException("Could not delete metadata for domain '" + zonename.toString() + "' (" + kind + ")");
+ vector<string> metadata; // an empty vector will do it
+ if (!zoneData.backend.setDomainMetadata(zoneData.zoneName, kind, metadata)) {
+ throw ApiException("Could not delete metadata for domain '" + zoneData.zoneName.toString() + "' (" + kind + ")");
+ }
- DNSSECKeeper::clearMetaCache(zonename);
- } else
- throw HttpMethodNotAllowedException();
+ DNSSECKeeper::clearMetaCache(zoneData.zoneName);
+ resp->status = 204;
}
// Throws 404 if the key with inquireKeyId does not exist
-static void apiZoneCryptoKeysCheckKeyExists(const DNSName& zonename, int inquireKeyId, DNSSECKeeper *dk) {
- DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false);
+static void apiZoneCryptoKeysCheckKeyExists(const DNSName& zonename, int inquireKeyId, DNSSECKeeper* dnssecKeeper)
+{
+ DNSSECKeeper::keyset_t keyset = dnssecKeeper->getKeys(zonename, false);
bool found = false;
- for(const auto& value : keyset) {
- if (value.second.id == (unsigned) inquireKeyId) {
+ for (const auto& value : keyset) {
+ if (value.second.id == (unsigned)inquireKeyId) {
found = true;
break;
}
}
}
-static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, HttpResponse *resp, DNSSECKeeper *dk) {
- DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false);
+static inline int getInquireKeyId(HttpRequest* req, const DNSName& zonename, DNSSECKeeper* dnsseckeeper)
+{
+ int inquireKeyId = -1;
+ if (req->parameters.count("key_id") == 1) {
+ inquireKeyId = std::stoi(req->parameters["key_id"]);
+ apiZoneCryptoKeysCheckKeyExists(zonename, inquireKeyId, dnsseckeeper);
+ }
+ return inquireKeyId;
+}
+
+static void apiZoneCryptokeysExport(const DNSName& zonename, int64_t inquireKeyId, HttpResponse* resp, DNSSECKeeper* dnssec_dk)
+{
+ DNSSECKeeper::keyset_t keyset = dnssec_dk->getKeys(zonename, false);
bool inquireSingleKey = inquireKeyId >= 0;
Json::array doc;
- for(const auto& value : keyset) {
+ for (const auto& value : keyset) {
if (inquireSingleKey && (unsigned)inquireKeyId != value.second.id) {
continue;
}
string keyType;
switch (value.second.keyType) {
- case DNSSECKeeper::KSK: keyType="ksk"; break;
- case DNSSECKeeper::ZSK: keyType="zsk"; break;
- case DNSSECKeeper::CSK: keyType="csk"; break;
- }
-
- Json::object key {
- { "type", "Cryptokey" },
- { "id", (int)value.second.id },
- { "active", value.second.active },
- { "published", value.second.published },
- { "keytype", keyType },
- { "flags", (uint16_t)value.first.getFlags() },
- { "dnskey", value.first.getDNSKEY().getZoneRepresentation() },
- { "algorithm", DNSSECKeeper::algorithm2name(value.first.getAlgorithm()) },
- { "bits", value.first.getKey()->getBits() }
- };
+ case DNSSECKeeper::KSK:
+ keyType = "ksk";
+ break;
+ case DNSSECKeeper::ZSK:
+ keyType = "zsk";
+ break;
+ case DNSSECKeeper::CSK:
+ keyType = "csk";
+ break;
+ }
+
+ Json::object key{
+ {"type", "Cryptokey"},
+ {"id", static_cast<int>(value.second.id)},
+ {"active", value.second.active},
+ {"published", value.second.published},
+ {"keytype", keyType},
+ {"flags", static_cast<uint16_t>(value.first.getFlags())},
+ {"dnskey", value.first.getDNSKEY().getZoneRepresentation()},
+ {"algorithm", DNSSECKeeper::algorithm2name(value.first.getAlgorithm())},
+ {"bits", value.first.getKey()->getBits()}};
string publishCDS;
- dk->getPublishCDS(zonename, publishCDS);
+ dnssec_dk->getPublishCDS(zonename, publishCDS);
vector<string> digestAlgos;
stringtok(digestAlgos, publishCDS, ", ");
std::set<unsigned int> CDSalgos;
- for(auto const &digestAlgo : digestAlgos) {
+ for (auto const& digestAlgo : digestAlgos) {
CDSalgos.insert(pdns::checked_stoi<unsigned int>(digestAlgo));
}
if (value.second.keyType == DNSSECKeeper::KSK || value.second.keyType == DNSSECKeeper::CSK) {
Json::array cdses;
Json::array dses;
- for(const uint8_t keyid : { DNSSECKeeper::DIGEST_SHA1, DNSSECKeeper::DIGEST_SHA256, DNSSECKeeper::DIGEST_GOST, DNSSECKeeper::DIGEST_SHA384 })
+ for (const uint8_t keyid : {DNSSECKeeper::DIGEST_SHA256, DNSSECKeeper::DIGEST_SHA384}) {
try {
- string ds = makeDSFromDNSKey(zonename, value.first.getDNSKEY(), keyid).getZoneRepresentation();
+ string dsRecordContent = makeDSFromDNSKey(zonename, value.first.getDNSKEY(), keyid).getZoneRepresentation();
- dses.push_back(ds);
+ dses.emplace_back(dsRecordContent);
- if (CDSalgos.count(keyid)) { cdses.push_back(ds); }
- } catch (...) {}
+ if (CDSalgos.count(keyid) != 0) {
+ cdses.emplace_back(dsRecordContent);
+ }
+ }
+ catch (...) {
+ }
+ }
key["ds"] = dses;
- if (cdses.size()) {
+ if (!cdses.empty()) {
key["cds"] = cdses;
}
}
resp->setJsonBody(key);
return;
}
- doc.push_back(key);
+ doc.emplace_back(key);
}
if (inquireSingleKey) {
throw HttpNotFoundException();
}
resp->setJsonBody(doc);
+}
+
+static void apiZoneCryptokeysGET(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+ const auto inquireKeyId = getInquireKeyId(req, zoneData.zoneName, &zoneData.dnssecKeeper);
+ apiZoneCryptokeysExport(zoneData.zoneName, inquireKeyId, resp, &zoneData.dnssecKeeper);
}
/*
* Case 3: the key or zone does not exist.
* The server returns 404 Not Found
* */
-static void apiZoneCryptokeysDELETE(const DNSName& zonename, int inquireKeyId, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
- if (dk->removeKey(zonename, inquireKeyId)) {
+static void apiZoneCryptokeysDELETE(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+ const auto inquireKeyId = getInquireKeyId(req, zoneData.zoneName, &zoneData.dnssecKeeper);
+
+ if (inquireKeyId == -1) {
+ throw HttpBadRequestException();
+ }
+
+ if (zoneData.dnssecKeeper.removeKey(zoneData.zoneName, inquireKeyId)) {
resp->body = "";
resp->status = 204;
- } else {
+ }
+ else {
resp->setErrorResult("Could not DELETE " + req->parameters["key_id"], 422);
}
}
* The server returns 201 Created and all public data about the added cryptokey
*/
-static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
- auto document = req->json();
+static void apiZoneCryptokeysPOST(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+
+ const auto& document = req->json();
string privatekey_fieldname = "privatekey";
auto privatekey = document["privatekey"];
if (privatekey.is_null()) {
}
bool active = boolFromJson(document, "active", false);
bool published = boolFromJson(document, "published", true);
- bool keyOrZone;
+ bool keyOrZone = false;
if (stringFromJson(document, "keytype") == "ksk" || stringFromJson(document, "keytype") == "csk") {
keyOrZone = true;
- } else if (stringFromJson(document, "keytype") == "zsk") {
+ }
+ else if (stringFromJson(document, "keytype") == "zsk") {
keyOrZone = false;
- } else {
+ }
+ else {
throw ApiException("Invalid keytype " + stringFromJson(document, "keytype"));
}
if (!docbits.is_null()) {
if (!docbits.is_number() || (fmod(docbits.number_value(), 1.0) != 0) || docbits.int_value() < 0) {
throw ApiException("'bits' must be a positive integer value");
- } else {
- bits = docbits.int_value();
}
+
+ bits = docbits.int_value();
}
int algorithm = DNSSECKeeper::shorthand2algorithm(keyOrZone ? ::arg()["default-ksk-algorithm"] : ::arg()["default-zsk-algorithm"]);
- auto providedAlgo = document["algorithm"];
+ const auto& providedAlgo = document["algorithm"];
if (providedAlgo.is_string()) {
algorithm = DNSSECKeeper::shorthand2algorithm(providedAlgo.string_value());
- if (algorithm == -1)
+ if (algorithm == -1) {
throw ApiException("Unknown algorithm: " + providedAlgo.string_value());
- } else if (providedAlgo.is_number()) {
+ }
+ }
+ else if (providedAlgo.is_number()) {
algorithm = providedAlgo.int_value();
- } else if (!providedAlgo.is_null()) {
+ }
+ else if (!providedAlgo.is_null()) {
throw ApiException("Unknown algorithm: " + providedAlgo.string_value());
}
try {
- if (!dk->addKey(zonename, keyOrZone, algorithm, insertedId, bits, active, published)) {
+ if (!zoneData.dnssecKeeper.addKey(zoneData.zoneName, keyOrZone, algorithm, insertedId, bits, active, published)) {
throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
}
- } catch (std::runtime_error& error) {
+ }
+ catch (std::runtime_error& error) {
throw ApiException(error.what());
}
- if (insertedId < 0)
+ if (insertedId < 0) {
throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
- } else if (document["bits"].is_null() && document["algorithm"].is_null()) {
- auto keyData = stringFromJson(document, privatekey_fieldname);
+ }
+ }
+ else if (document["bits"].is_null() && document["algorithm"].is_null()) {
+ const auto& keyData = stringFromJson(document, privatekey_fieldname);
DNSKEYRecordContent dkrc;
DNSSECPrivateKey dpk;
try {
}
catch (std::runtime_error& error) {
throw ApiException("Key could not be parsed. Make sure your key format is correct.");
- } try {
- if (!dk->addKey(zonename, dpk,insertedId, active, published)) {
+ }
+ try {
+ if (!zoneData.dnssecKeeper.addKey(zoneData.zoneName, dpk, insertedId, active, published)) {
throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
}
- } catch (std::runtime_error& error) {
+ }
+ catch (std::runtime_error& error) {
throw ApiException(error.what());
}
- if (insertedId < 0)
+ if (insertedId < 0) {
throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
- } else {
+ }
+ }
+ else {
throw ApiException("Either you submit just the 'privatekey' field or you leave 'privatekey' empty and submit the other fields.");
}
- apiZoneCryptokeysGET(zonename, insertedId, resp, dk);
+ apiZoneCryptokeysExport(zoneData.zoneName, insertedId, resp, &zoneData.dnssecKeeper);
resp->status = 201;
}
* Case 3: the backend returns false on de/activation. An error occurred.
* The sever returns 422 Unprocessable Entity with message "Could not de/activate Key: :cryptokey_id in Zone: :zone_name"
* */
-static void apiZoneCryptokeysPUT(const DNSName& zonename, int inquireKeyId, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
- //throws an exception if the Body is empty
- auto document = req->json();
- //throws an exception if the key does not exist or is not a bool
+static void apiZoneCryptokeysPUT(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+ const auto inquireKeyId = getInquireKeyId(req, zoneData.zoneName, &zoneData.dnssecKeeper);
+
+ if (inquireKeyId == -1) {
+ throw HttpBadRequestException();
+ }
+ // throws an exception if the Body is empty
+ const auto& document = req->json();
+ // throws an exception if the key does not exist or is not a bool
bool active = boolFromJson(document, "active");
bool published = boolFromJson(document, "published", true);
if (active) {
- if (!dk->activateKey(zonename, inquireKeyId)) {
- resp->setErrorResult("Could not activate Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
+ if (!zoneData.dnssecKeeper.activateKey(zoneData.zoneName, inquireKeyId)) {
+ resp->setErrorResult("Could not activate Key: " + req->parameters["key_id"] + " in Zone: " + zoneData.zoneName.toString(), 422);
return;
}
- } else {
- if (!dk->deactivateKey(zonename, inquireKeyId)) {
- resp->setErrorResult("Could not deactivate Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
+ }
+ else {
+ if (!zoneData.dnssecKeeper.deactivateKey(zoneData.zoneName, inquireKeyId)) {
+ resp->setErrorResult("Could not deactivate Key: " + req->parameters["key_id"] + " in Zone: " + zoneData.zoneName.toString(), 422);
return;
}
}
if (published) {
- if (!dk->publishKey(zonename, inquireKeyId)) {
- resp->setErrorResult("Could not publish Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
+ if (!zoneData.dnssecKeeper.publishKey(zoneData.zoneName, inquireKeyId)) {
+ resp->setErrorResult("Could not publish Key: " + req->parameters["key_id"] + " in Zone: " + zoneData.zoneName.toString(), 422);
return;
}
- } else {
- if (!dk->unpublishKey(zonename, inquireKeyId)) {
- resp->setErrorResult("Could not unpublish Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
+ }
+ else {
+ if (!zoneData.dnssecKeeper.unpublishKey(zoneData.zoneName, inquireKeyId)) {
+ resp->setErrorResult("Could not unpublish Key: " + req->parameters["key_id"] + " in Zone: " + zoneData.zoneName.toString(), 422);
return;
}
}
resp->body = "";
resp->status = 204;
- return;
}
-/*
- * This method chooses the right functionality for the request. It also checks for a cryptokey_id which has to be passed
- * by URL /api/v1/servers/:server_id/zones/:zone_name/cryptokeys/:cryptokey_id .
- * If the the HTTP-request-method isn't supported, the function returns a response with the 405 code (method not allowed).
- * */
-static void apiZoneCryptokeys(HttpRequest *req, HttpResponse *resp) {
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
- UeberBackend B;
- DNSSECKeeper dk(&B);
- DomainInfo di;
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
- }
-
- int inquireKeyId = -1;
- if (req->parameters.count("key_id")) {
- inquireKeyId = std::stoi(req->parameters["key_id"]);
- apiZoneCryptoKeysCheckKeyExists(zonename, inquireKeyId, &dk);
- }
-
- if (req->method == "GET") {
- apiZoneCryptokeysGET(zonename, inquireKeyId, resp, &dk);
- } else if (req->method == "DELETE") {
- if (inquireKeyId == -1)
- throw HttpBadRequestException();
- apiZoneCryptokeysDELETE(zonename, inquireKeyId, req, resp, &dk);
- } else if (req->method == "POST") {
- apiZoneCryptokeysPOST(zonename, req, resp, &dk);
- } else if (req->method == "PUT") {
- if (inquireKeyId == -1)
- throw HttpBadRequestException();
- apiZoneCryptokeysPUT(zonename, inquireKeyId, req, resp, &dk);
- } else {
- throw HttpMethodNotAllowedException(); //Returns method not allowed
- }
-}
-
-static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResourceRecord>& new_records, const DNSName& zonename) {
- DNSResourceRecord rr;
- vector<string> zonedata;
- stringtok(zonedata, zonestring, "\r\n");
+static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResourceRecord>& new_records, const DNSName& zonename)
+{
+ DNSResourceRecord resourceRecord;
+ vector<string> zonedata;
+ stringtok(zonedata, zonestring, "\r\n");
ZoneParserTNG zpt(zonedata, zonename);
zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
- bool seenSOA=false;
+ bool seenSOA = false;
string comment = "Imported via the API";
try {
- while(zpt.get(rr, &comment)) {
- if(seenSOA && rr.qtype.getCode() == QType::SOA)
+ while (zpt.get(resourceRecord, &comment)) {
+ if (seenSOA && resourceRecord.qtype.getCode() == QType::SOA) {
continue;
- if(rr.qtype.getCode() == QType::SOA)
- seenSOA=true;
- validateGatheredRRType(rr);
+ }
+ if (resourceRecord.qtype.getCode() == QType::SOA) {
+ seenSOA = true;
+ }
+ validateGatheredRRType(resourceRecord);
- new_records.push_back(rr);
+ new_records.push_back(resourceRecord);
}
}
- catch(std::exception& ae) {
- throw ApiException("An error occurred while parsing the zonedata: "+string(ae.what()));
+ catch (std::exception& ae) {
+ throw ApiException("An error occurred while parsing the zonedata: " + string(ae.what()));
}
}
static void checkNewRecords(vector<DNSResourceRecord>& records, const DNSName& zone)
{
sort(records.begin(), records.end(),
- [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
- /* we need _strict_ weak ordering */
- return std::tie(rec_a.qname, rec_a.qtype, rec_a.content) < std::tie(rec_b.qname, rec_b.qtype, rec_b.content);
- }
- );
+ [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
+ /* we need _strict_ weak ordering */
+ return std::tie(rec_a.qname, rec_a.qtype, rec_a.content) < std::tie(rec_b.qname, rec_b.qtype, rec_b.content);
+ });
DNSResourceRecord previous;
- for(const auto& rec : records) {
+ for (const auto& rec : records) {
if (previous.qname == rec.qname) {
if (previous.qtype == rec.qtype) {
if (onlyOneEntryTypes.count(rec.qtype.getCode()) != 0) {
- throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.toString()+" has more than one record");
+ throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + " has more than one record");
}
if (previous.content == rec.content) {
throw ApiException("Duplicate record in RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + " with content \"" + rec.content + "\"");
}
- } else if (exclusiveEntryTypes.count(rec.qtype.getCode()) != 0 || exclusiveEntryTypes.count(previous.qtype.getCode()) != 0) {
- throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.toString()+": Conflicts with another RRset");
+ }
+ else if (exclusiveEntryTypes.count(rec.qtype.getCode()) != 0 || exclusiveEntryTypes.count(previous.qtype.getCode()) != 0) {
+ throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + ": Conflicts with another RRset");
}
}
// Check if the DNSNames that should be hostnames, are hostnames
try {
checkHostnameCorrectness(rec);
- } catch (const std::exception& e) {
- throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.toString() + " " + e.what());
+ }
+ catch (const std::exception& e) {
+ throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + ": " + e.what());
}
previous = rec;
}
}
-static void checkTSIGKey(UeberBackend& B, const DNSName& keyname, const DNSName& algo, const string& content) {
+static void checkTSIGKey(UeberBackend& backend, const DNSName& keyname, const DNSName& algo, const string& content)
+{
DNSName algoFromDB;
string contentFromDB;
- if (B.getTSIGKey(keyname, algoFromDB, contentFromDB)) {
- throw HttpConflictException("A TSIG key with the name '"+keyname.toLogString()+"' already exists");
+ if (backend.getTSIGKey(keyname, algoFromDB, contentFromDB)) {
+ throw HttpConflictException("A TSIG key with the name '" + keyname.toLogString() + "' already exists");
}
- TSIGHashEnum the;
+ TSIGHashEnum the{};
if (!getTSIGHashEnum(algo, the)) {
throw ApiException("Unknown TSIG algorithm: " + algo.toLogString());
}
}
}
-static Json::object makeJSONTSIGKey(const DNSName& keyname, const DNSName& algo, const string& content) {
+static Json::object makeJSONTSIGKey(const DNSName& keyname, const DNSName& algo, const string& content)
+{
Json::object tsigkey = {
- { "name", keyname.toStringNoDot() },
- { "id", apiZoneNameToId(keyname) },
- { "algorithm", algo.toStringNoDot() },
- { "key", content },
- { "type", "TSIGKey" }
- };
+ {"name", keyname.toStringNoDot()},
+ {"id", apiZoneNameToId(keyname)},
+ {"algorithm", algo.toStringNoDot()},
+ {"key", content},
+ {"type", "TSIGKey"}};
return tsigkey;
}
-static Json::object makeJSONTSIGKey(const struct TSIGKey& key, bool doContent=true) {
+static Json::object makeJSONTSIGKey(const struct TSIGKey& key, bool doContent = true)
+{
return makeJSONTSIGKey(key.name, key.algorithm, doContent ? key.key : "");
}
-static void apiServerTSIGKeys(HttpRequest* req, HttpResponse* resp) {
- UeberBackend B;
- if (req->method == "GET") {
- vector<struct TSIGKey> keys;
+static void apiServerTSIGKeysGET(HttpRequest* /* req */, HttpResponse* resp)
+{
+ UeberBackend backend;
+ vector<struct TSIGKey> keys;
- if (!B.getTSIGKeys(keys)) {
- throw HttpInternalServerErrorException("Unable to retrieve TSIG keys");
- }
+ if (!backend.getTSIGKeys(keys)) {
+ throw HttpInternalServerErrorException("Unable to retrieve TSIG keys");
+ }
- Json::array doc;
+ Json::array doc;
- for(const auto &key : keys) {
- doc.push_back(makeJSONTSIGKey(key, false));
- }
- resp->setJsonBody(doc);
- } else if (req->method == "POST") {
- auto document = req->json();
- DNSName keyname(stringFromJson(document, "name"));
- DNSName algo(stringFromJson(document, "algorithm"));
- string content = document["key"].string_value();
+ for (const auto& key : keys) {
+ doc.emplace_back(makeJSONTSIGKey(key, false));
+ }
+ resp->setJsonBody(doc);
+}
- if (content.empty()) {
- try {
- content = makeTSIGKey(algo);
- } catch (const PDNSException& e) {
- throw HttpBadRequestException(e.reason);
- }
+static void apiServerTSIGKeysPOST(HttpRequest* req, HttpResponse* resp)
+{
+ UeberBackend backend;
+ const auto& document = req->json();
+ DNSName keyname(stringFromJson(document, "name"));
+ DNSName algo(stringFromJson(document, "algorithm"));
+ string content = document["key"].string_value();
+
+ if (content.empty()) {
+ try {
+ content = makeTSIGKey(algo);
+ }
+ catch (const PDNSException& exc) {
+ throw HttpBadRequestException(exc.reason);
}
+ }
+
+ // Will throw an ApiException or HttpConflictException on error
+ checkTSIGKey(backend, keyname, algo, content);
- // Will throw an ApiException or HttpConflictException on error
- checkTSIGKey(B, keyname, algo, content);
+ if (!backend.setTSIGKey(keyname, algo, content)) {
+ throw HttpInternalServerErrorException("Unable to add TSIG key");
+ }
- if(!B.setTSIGKey(keyname, algo, content)) {
- throw HttpInternalServerErrorException("Unable to add TSIG key");
+ resp->status = 201;
+ resp->setJsonBody(makeJSONTSIGKey(keyname, algo, content));
+}
+
+class TSIGKeyData
+{
+public:
+ TSIGKeyData(HttpRequest* req) :
+ keyName(apiZoneIdToName(req->parameters["id"]))
+ {
+ try {
+ if (!backend.getTSIGKey(keyName, algo, content)) {
+ throw HttpNotFoundException("TSIG key with name '" + keyName.toLogString() + "' not found");
+ }
+ }
+ catch (const PDNSException& e) {
+ throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason);
}
- resp->status = 201;
- resp->setJsonBody(makeJSONTSIGKey(keyname, algo, content));
- } else {
- throw HttpMethodNotAllowedException();
+ tsigKey.name = keyName;
+ tsigKey.algorithm = algo;
+ tsigKey.key = std::move(content);
}
-}
-static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) {
- UeberBackend B;
- DNSName keyname = apiZoneIdToName(req->parameters["id"]);
+ UeberBackend backend;
+ DNSName keyName;
DNSName algo;
string content;
+ struct TSIGKey tsigKey;
+};
- if (!B.getTSIGKey(keyname, algo, content)) {
- throw HttpNotFoundException("TSIG key with name '"+keyname.toLogString()+"' not found");
- }
+static void apiServerTSIGKeyDetailGET(HttpRequest* req, HttpResponse* resp)
+{
+ TSIGKeyData tsigKeyData{req};
- struct TSIGKey tsk;
- tsk.name = keyname;
- tsk.algorithm = algo;
- tsk.key = content;
+ resp->setJsonBody(makeJSONTSIGKey(tsigKeyData.tsigKey));
+}
- if (req->method == "GET") {
- resp->setJsonBody(makeJSONTSIGKey(tsk));
- } else if (req->method == "PUT") {
- json11::Json document;
- if (!req->body.empty()) {
- document = req->json();
- }
- if (document["name"].is_string()) {
- tsk.name = DNSName(document["name"].string_value());
- }
- if (document["algorithm"].is_string()) {
- tsk.algorithm = DNSName(document["algorithm"].string_value());
+static void apiServerTSIGKeyDetailPUT(HttpRequest* req, HttpResponse* resp)
+{
+ TSIGKeyData tsigKeyData{req};
- TSIGHashEnum the;
- if (!getTSIGHashEnum(tsk.algorithm, the)) {
- throw ApiException("Unknown TSIG algorithm: " + tsk.algorithm.toLogString());
- }
- }
- if (document["key"].is_string()) {
- string new_content = document["key"].string_value();
- string decoded;
- if (B64Decode(new_content, decoded) == -1) {
- throw ApiException("Can not base64 decode key content '" + new_content + "'");
- }
- tsk.key = new_content;
- }
- if (!B.setTSIGKey(tsk.name, tsk.algorithm, tsk.key)) {
- throw HttpInternalServerErrorException("Unable to save TSIG Key");
+ const auto& document = req->json();
+
+ if (document["name"].is_string()) {
+ tsigKeyData.tsigKey.name = DNSName(document["name"].string_value());
+ }
+ if (document["algorithm"].is_string()) {
+ tsigKeyData.tsigKey.algorithm = DNSName(document["algorithm"].string_value());
+
+ TSIGHashEnum the{};
+ if (!getTSIGHashEnum(tsigKeyData.tsigKey.algorithm, the)) {
+ throw ApiException("Unknown TSIG algorithm: " + tsigKeyData.tsigKey.algorithm.toLogString());
}
- if (tsk.name != keyname) {
- // Remove the old key
- if (!B.deleteTSIGKey(keyname)) {
- throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'");
- }
+ }
+ if (document["key"].is_string()) {
+ string new_content = document["key"].string_value();
+ string decoded;
+ if (B64Decode(new_content, decoded) == -1) {
+ throw ApiException("Can not base64 decode key content '" + new_content + "'");
}
- resp->setJsonBody(makeJSONTSIGKey(tsk));
- } else if (req->method == "DELETE") {
- if (!B.deleteTSIGKey(keyname)) {
- throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'");
- } else {
- resp->body = "";
- resp->status = 204;
+ tsigKeyData.tsigKey.key = std::move(new_content);
+ }
+ if (!tsigKeyData.backend.setTSIGKey(tsigKeyData.tsigKey.name, tsigKeyData.tsigKey.algorithm, tsigKeyData.tsigKey.key)) {
+ throw HttpInternalServerErrorException("Unable to save TSIG Key");
+ }
+ if (tsigKeyData.tsigKey.name != tsigKeyData.keyName) {
+ // Remove the old key
+ if (!tsigKeyData.backend.deleteTSIGKey(tsigKeyData.keyName)) {
+ throw HttpInternalServerErrorException("Unable to remove TSIG key '" + tsigKeyData.keyName.toStringNoDot() + "'");
}
- } else {
- throw HttpMethodNotAllowedException();
}
+ resp->setJsonBody(makeJSONTSIGKey(tsigKeyData.tsigKey));
}
-static void apiServerAutoprimaryDetail(HttpRequest* req, HttpResponse* resp) {
- UeberBackend B;
- if (req->method == "DELETE") {
- const AutoPrimary primary(req->parameters["ip"], req->parameters["nameserver"], "");
- if (!B.autoPrimaryRemove(primary))
- throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
- resp->body = "";
- resp->status = 204;
- } else {
- throw HttpMethodNotAllowedException();
+static void apiServerTSIGKeyDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+ TSIGKeyData tsigKeyData{req};
+ if (!tsigKeyData.backend.deleteTSIGKey(tsigKeyData.keyName)) {
+ throw HttpInternalServerErrorException("Unable to remove TSIG key '" + tsigKeyData.keyName.toStringNoDot() + "'");
}
+ resp->body = "";
+ resp->status = 204;
}
-static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) {
- UeberBackend B;
+static void apiServerAutoprimaryDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+ UeberBackend backend;
+ const AutoPrimary& primary{req->parameters["ip"], req->parameters["nameserver"], ""};
+ if (!backend.autoPrimaryRemove(primary)) {
+ throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
+ }
+ resp->body = "";
+ resp->status = 204;
+}
- if (req->method == "GET") {
- std::vector<AutoPrimary> primaries;
- if (!B.autoPrimariesList(primaries))
- throw HttpInternalServerErrorException("Unable to retrieve autoprimaries");
- Json::array doc;
- for (const auto& primary: primaries) {
- Json::object obj = {
- { "ip", primary.ip },
- { "nameserver", primary.nameserver },
- { "account", primary.account }
- };
- doc.push_back(obj);
- }
- resp->setJsonBody(doc);
- } else if (req->method == "POST") {
- auto document = req->json();
- AutoPrimary primary(stringFromJson(document, "ip"), stringFromJson(document, "nameserver"), "");
+static void apiServerAutoprimariesGET(HttpRequest* /* req */, HttpResponse* resp)
+{
+ UeberBackend backend;
- if (document["account"].is_string()) {
- primary.account = document["account"].string_value();
- }
+ std::vector<AutoPrimary> primaries;
+ if (!backend.autoPrimariesList(primaries)) {
+ throw HttpInternalServerErrorException("Unable to retrieve autoprimaries");
+ }
+ Json::array doc;
+ for (const auto& primary : primaries) {
+ const Json::object obj = {
+ {"ip", primary.ip},
+ {"nameserver", primary.nameserver},
+ {"account", primary.account}};
+ doc.emplace_back(obj);
+ }
+ resp->setJsonBody(doc);
+}
- if (primary.ip=="" or primary.nameserver=="") {
- throw ApiException("ip and nameserver fields must be filled");
- }
- if (!B.superMasterAdd(primary))
- throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
- resp->body = "";
- resp->status = 201;
- } else {
- throw HttpMethodNotAllowedException();
+static void apiServerAutoprimariesPOST(HttpRequest* req, HttpResponse* resp)
+{
+ UeberBackend backend;
+
+ const auto& document = req->json();
+
+ AutoPrimary primary(stringFromJson(document, "ip"), stringFromJson(document, "nameserver"), "");
+
+ if (document["account"].is_string()) {
+ primary.account = document["account"].string_value();
+ }
+
+ if (primary.ip.empty() or primary.nameserver.empty()) {
+ throw ApiException("ip and nameserver fields must be filled");
}
+ if (!backend.autoPrimaryAdd(primary)) {
+ throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
+ }
+ resp->body = "";
+ resp->status = 201;
}
-static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
- UeberBackend B;
- DNSSECKeeper dk(&B);
- if (req->method == "POST") {
- DomainInfo di;
- auto document = req->json();
- DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
- apiCheckNameAllowedCharacters(zonename.toString());
- zonename.makeUsLowerCase();
+// create new zone
+static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp)
+{
+ UeberBackend backend;
+ DNSSECKeeper dnssecKeeper(&backend);
+ DomainInfo domainInfo;
+ const auto& document = req->json();
+ DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
+ apiCheckNameAllowedCharacters(zonename.toString());
+ zonename.makeUsLowerCase();
- bool exists = B.getDomainInfo(zonename, di);
- if(exists)
- throw HttpConflictException();
+ bool exists = backend.getDomainInfo(zonename, domainInfo);
+ if (exists) {
+ throw HttpConflictException();
+ }
- // validate 'kind' is set
- DomainInfo::DomainKind zonekind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
+ boost::optional<DomainInfo::DomainKind> kind;
+ boost::optional<vector<ComboAddress>> primaries;
+ boost::optional<DNSName> catalog;
+ boost::optional<string> account;
+ extractDomainInfoFromDocument(document, kind, primaries, catalog, account);
- string zonestring = document["zone"].string_value();
- auto rrsets = document["rrsets"];
- if (rrsets.is_array() && zonestring != "")
- throw ApiException("You cannot give rrsets AND zone data as text");
+ // validate 'kind' is set
+ if (!kind) {
+ throw JsonException("Key 'kind' not present or not a String");
+ }
+ DomainInfo::DomainKind zonekind = *kind;
- auto nameservers = document["nameservers"];
- if (!nameservers.is_null() && !nameservers.is_array() && zonekind != DomainInfo::Slave && zonekind != DomainInfo::Consumer)
- throw ApiException("Nameservers is not a list");
+ string zonestring = document["zone"].string_value();
+ auto rrsets = document["rrsets"];
+ if (rrsets.is_array() && !zonestring.empty()) {
+ throw ApiException("You cannot give rrsets AND zone data as text");
+ }
- // if records/comments are given, load and check them
- bool have_soa = false;
- bool have_zone_ns = false;
- vector<DNSResourceRecord> new_records;
- vector<Comment> new_comments;
+ const auto& nameservers = document["nameservers"];
+ if (!nameservers.is_null() && !nameservers.is_array() && zonekind != DomainInfo::Secondary && zonekind != DomainInfo::Consumer) {
+ throw ApiException("Nameservers is not a list");
+ }
+
+ // if records/comments are given, load and check them
+ bool have_soa = false;
+ bool have_zone_ns = false;
+ vector<DNSResourceRecord> new_records;
+ vector<Comment> new_comments;
+ try {
if (rrsets.is_array()) {
for (const auto& rrset : rrsets.array_items()) {
DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
QType qtype;
qtype = stringFromJson(rrset, "type");
if (qtype.getCode() == 0) {
- throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
+ throw ApiException("RRset " + qname.toString() + " IN " + stringFromJson(rrset, "type") + ": unknown type given");
}
if (rrset["records"].is_array()) {
- int ttl = intFromJson(rrset, "ttl");
+ uint32_t ttl = uintFromJson(rrset, "ttl");
gatherRecords(rrset, qname, qtype, ttl, new_records);
}
if (rrset["comments"].is_array()) {
gatherComments(rrset, qname, qtype, new_comments);
}
}
- } else if (zonestring != "") {
+ }
+ else if (!zonestring.empty()) {
gatherRecordsFromZone(zonestring, new_records, zonename);
}
+ }
+ catch (const JsonException& exc) {
+ throw ApiException("New RRsets are invalid: " + string(exc.what()));
+ }
+
+ if (zonekind == DomainInfo::Consumer && !new_records.empty()) {
+ throw ApiException("Zone data MUST NOT be given for Consumer zones");
+ }
+
+ for (auto& resourceRecord : new_records) {
+ resourceRecord.qname.makeUsLowerCase();
+ if (!resourceRecord.qname.isPartOf(zonename) && resourceRecord.qname != zonename) {
+ throw ApiException("RRset " + resourceRecord.qname.toString() + " IN " + resourceRecord.qtype.toString() + ": Name is out of zone");
+ }
- for(auto& rr : new_records) {
- rr.qname.makeUsLowerCase();
- if (!rr.qname.isPartOf(zonename) && rr.qname != zonename)
- throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.toString()+": Name is out of zone");
- apiCheckQNameAllowedCharacters(rr.qname.toString());
+ apiCheckQNameAllowedCharacters(resourceRecord.qname.toString());
- if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
- have_soa = true;
- }
- if (rr.qtype.getCode() == QType::NS && rr.qname==zonename) {
- have_zone_ns = true;
- }
+ if (resourceRecord.qtype.getCode() == QType::SOA && resourceRecord.qname == zonename) {
+ have_soa = true;
+ }
+ if (resourceRecord.qtype.getCode() == QType::NS && resourceRecord.qname == zonename) {
+ have_zone_ns = true;
}
+ }
- // synthesize RRs as needed
- DNSResourceRecord autorr;
- autorr.qname = zonename;
- autorr.auth = true;
- autorr.ttl = ::arg().asNum("default-ttl");
-
- if (!have_soa && zonekind != DomainInfo::Slave && zonekind != DomainInfo::Consumer) {
- // synthesize a SOA record so the zone "really" exists
- string soa = ::arg()["default-soa-content"];
- boost::replace_all(soa, "@", zonename.toStringNoDot());
- SOAData sd;
- fillSOAData(soa, sd);
- sd.serial=document["serial"].int_value();
- autorr.qtype = QType::SOA;
- autorr.content = makeSOAContent(sd)->getZoneRepresentation(true);
- // updateDomainSettingsFromDocument will apply SOA-EDIT-API as needed
- new_records.push_back(autorr);
- }
-
- // create NS records if nameservers are given
- for (const auto& value : nameservers.array_items()) {
- const string& nameserver = value.string_value();
- if (nameserver.empty())
- throw ApiException("Nameservers must be non-empty strings");
- if (!isCanonical(nameserver))
- throw ApiException("Nameserver is not canonical: '" + nameserver + "'");
- try {
- // ensure the name parses
- autorr.content = DNSName(nameserver).toStringRootDot();
- } catch (...) {
- throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "'");
- }
- autorr.qtype = QType::NS;
- new_records.push_back(autorr);
- if (have_zone_ns) {
- throw ApiException("Nameservers list MUST NOT be mixed with zone-level NS in rrsets");
- }
+ // synthesize RRs as needed
+ DNSResourceRecord autorr;
+ autorr.qname = zonename;
+ autorr.auth = true;
+ autorr.ttl = ::arg().asNum("default-ttl");
+
+ if (!have_soa && zonekind != DomainInfo::Secondary && zonekind != DomainInfo::Consumer) {
+ // synthesize a SOA record so the zone "really" exists
+ string soa = ::arg()["default-soa-content"];
+ boost::replace_all(soa, "@", zonename.toStringNoDot());
+ SOAData soaData;
+ fillSOAData(soa, soaData);
+ soaData.serial = document["serial"].int_value();
+ autorr.qtype = QType::SOA;
+ autorr.content = makeSOAContent(soaData)->getZoneRepresentation(true);
+ // updateDomainSettingsFromDocument will apply SOA-EDIT-API as needed
+ new_records.push_back(autorr);
+ }
+
+ // create NS records if nameservers are given
+ for (const auto& value : nameservers.array_items()) {
+ const string& nameserver = value.string_value();
+ if (nameserver.empty()) {
+ throw ApiException("Nameservers must be non-empty strings");
+ }
+ if (zonekind == DomainInfo::Consumer) {
+ throw ApiException("Nameservers MUST NOT be given for Consumer zones");
+ }
+ if (!isCanonical(nameserver)) {
+ throw ApiException("Nameserver is not canonical: '" + nameserver + "'");
+ }
+ try {
+ // ensure the name parses
+ autorr.content = DNSName(nameserver).toStringRootDot();
+ }
+ catch (...) {
+ throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "'");
}
+ autorr.qtype = QType::NS;
+ new_records.push_back(autorr);
+ if (have_zone_ns) {
+ throw ApiException("Nameservers list MUST NOT be mixed with zone-level NS in rrsets");
+ }
+ }
- checkNewRecords(new_records, zonename);
+ checkNewRecords(new_records, zonename);
- if (boolFromJson(document, "dnssec", false)) {
- checkDefaultDNSSECAlgos();
+ if (boolFromJson(document, "dnssec", false)) {
+ checkDefaultDNSSECAlgos();
- if(document["nsec3param"].string_value().length() > 0) {
- NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value());
- string error_msg = "";
- if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) {
- throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"' are invalid. " + error_msg);
- }
+ if (document["nsec3param"].string_value().length() > 0) {
+ NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value());
+ string error_msg;
+ if (!dnssecKeeper.checkNSEC3PARAM(ns3pr, error_msg)) {
+ throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() + "' are invalid. " + error_msg);
}
}
+ }
- boost::optional<DomainInfo::DomainKind> kind;
- boost::optional<vector<ComboAddress>> masters;
- boost::optional<DNSName> catalog;
- boost::optional<string> account;
- extractDomainInfoFromDocument(document, kind, masters, catalog, account);
-
- // no going back after this
- if(!B.createDomain(zonename, kind.get_value_or(DomainInfo::Native), masters.get_value_or(vector<ComboAddress>()), account.get_value_or("")))
- throw ApiException("Creating domain '"+zonename.toString()+"' failed: backend refused");
+ // no going back after this
+ if (!backend.createDomain(zonename, kind.get_value_or(DomainInfo::Native), primaries.get_value_or(vector<ComboAddress>()), account.get_value_or(""))) {
+ throw ApiException("Creating domain '" + zonename.toString() + "' failed: backend refused");
+ }
- if(!B.getDomainInfo(zonename, di))
- throw ApiException("Creating domain '"+zonename.toString()+"' failed: lookup of domain ID failed");
+ if (!backend.getDomainInfo(zonename, domainInfo)) {
+ throw ApiException("Creating domain '" + zonename.toString() + "' failed: lookup of domain ID failed");
+ }
- di.backend->startTransaction(zonename, di.id);
+ domainInfo.backend->startTransaction(zonename, static_cast<int>(domainInfo.id));
- // will be overridden by updateDomainSettingsFromDocument, if given in document.
- di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", "DEFAULT");
+ // will be overridden by updateDomainSettingsFromDocument, if given in document.
+ domainInfo.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", "DEFAULT");
- for(auto rr : new_records) {
- rr.domain_id = di.id;
- di.backend->feedRecord(rr, DNSName());
- }
- for(Comment& c : new_comments) {
- c.domain_id = di.id;
- di.backend->feedComment(c);
+ for (auto& resourceRecord : new_records) {
+ resourceRecord.domain_id = static_cast<int>(domainInfo.id);
+ domainInfo.backend->feedRecord(resourceRecord, DNSName());
+ }
+ for (Comment& comment : new_comments) {
+ comment.domain_id = static_cast<int>(domainInfo.id);
+ if (!domainInfo.backend->feedComment(comment)) {
+ throw ApiException("Hosting backend does not support editing comments.");
}
+ }
- updateDomainSettingsFromDocument(B, di, zonename, document, !new_records.empty());
+ updateDomainSettingsFromDocument(backend, domainInfo, zonename, document, !new_records.empty());
- di.backend->commitTransaction();
+ if (!catalog && kind == DomainInfo::Primary) {
+ const auto& defaultCatalog = ::arg()["default-catalog-zone"];
+ if (!defaultCatalog.empty()) {
+ domainInfo.backend->setCatalog(zonename, DNSName(defaultCatalog));
+ }
+ }
- g_zoneCache.add(zonename, di.id); // make new zone visible
+ domainInfo.backend->commitTransaction();
- fillZone(B, zonename, resp, req);
- resp->status = 201;
- return;
- }
+ g_zoneCache.add(zonename, static_cast<int>(domainInfo.id)); // make new zone visible
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
+ fillZone(backend, zonename, resp, req);
+ resp->status = 201;
+}
+// list known zones
+static void apiServerZonesGET(HttpRequest* req, HttpResponse* resp)
+{
+ UeberBackend backend;
+ DNSSECKeeper dnssecKeeper(&backend);
vector<DomainInfo> domains;
- if (req->getvars.count("zone")) {
+ if (req->getvars.count("zone") != 0) {
string zone = req->getvars["zone"];
apiCheckNameAllowedCharacters(zone);
DNSName zonename = apiNameToDNSName(zone);
zonename.makeUsLowerCase();
- DomainInfo di;
- if (B.getDomainInfo(zonename, di)) {
- domains.push_back(di);
+ DomainInfo domainInfo;
+ if (backend.getDomainInfo(zonename, domainInfo)) {
+ domains.push_back(domainInfo);
}
- } else {
+ }
+ else {
try {
- B.getAllDomains(&domains, true, true); // incl. serial and disabled
- } catch(const PDNSException &e) {
- throw HttpInternalServerErrorException("Could not retrieve all domain information: " + e.reason);
+ backend.getAllDomains(&domains, true, true); // incl. serial and disabled
+ }
+ catch (const PDNSException& exception) {
+ throw HttpInternalServerErrorException("Could not retrieve all domain information: " + exception.reason);
}
}
bool with_dnssec = true;
- if (req->getvars.count("dnssec")) {
+ if (req->getvars.count("dnssec") != 0) {
// can send ?dnssec=false to improve performance.
string dnssec_flag = req->getvars["dnssec"];
if (dnssec_flag == "false") {
Json::array doc;
doc.reserve(domains.size());
- for(const DomainInfo& di : domains) {
- doc.push_back(getZoneInfo(di, with_dnssec ? &dk : nullptr));
+ for (const DomainInfo& domainInfo : domains) {
+ doc.emplace_back(getZoneInfo(domainInfo, with_dnssec ? &dnssecKeeper : nullptr));
}
resp->setJsonBody(doc);
}
-static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
+static void apiServerZoneDetailPUT(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
- UeberBackend B;
- DomainInfo di;
- try {
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
- }
- } catch(const PDNSException &e) {
- throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason);
- }
+ // update domain contents and/or settings
+ const auto& document = req->json();
- if(req->method == "PUT") {
- // update domain settings
+ auto rrsets = document["rrsets"];
+ bool zoneWasModified = false;
+ DomainInfo::DomainKind newKind = zoneData.domainInfo.kind;
+ if (document["kind"].is_string()) {
+ newKind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
+ }
- di.backend->startTransaction(zonename, -1);
- updateDomainSettingsFromDocument(B, di, zonename, req->json(), false);
- di.backend->commitTransaction();
+ // if records/comments are given, load, check and insert them
+ if (rrsets.is_array()) {
+ zoneWasModified = true;
+ bool haveSoa = false;
+ string soaEditApiKind;
+ string soaEditKind;
+ zoneData.domainInfo.backend->getDomainMetadataOne(zoneData.zoneName, "SOA-EDIT-API", soaEditApiKind);
+ zoneData.domainInfo.backend->getDomainMetadataOne(zoneData.zoneName, "SOA-EDIT", soaEditKind);
- resp->body = "";
- resp->status = 204; // No Content, but indicate success
- return;
- }
- else if(req->method == "DELETE") {
- // delete domain
+ vector<DNSResourceRecord> new_records;
+ vector<Comment> new_comments;
- di.backend->startTransaction(zonename, -1);
try {
- if(!di.backend->deleteDomain(zonename))
- throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
+ for (const auto& rrset : rrsets.array_items()) {
+ DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
+ apiCheckQNameAllowedCharacters(qname.toString());
+ QType qtype;
+ qtype = stringFromJson(rrset, "type");
+ if (qtype.getCode() == 0) {
+ throw ApiException("RRset " + qname.toString() + " IN " + stringFromJson(rrset, "type") + ": unknown type given");
+ }
+ if (rrset["records"].is_array()) {
+ uint32_t ttl = uintFromJson(rrset, "ttl");
+ gatherRecords(rrset, qname, qtype, ttl, new_records);
+ }
+ if (rrset["comments"].is_array()) {
+ gatherComments(rrset, qname, qtype, new_comments);
+ }
+ }
+ }
+ catch (const JsonException& exc) {
+ throw ApiException("New RRsets are invalid: " + string(exc.what()));
+ }
- di.backend->commitTransaction();
+ for (auto& resourceRecord : new_records) {
+ resourceRecord.qname.makeUsLowerCase();
+ if (!resourceRecord.qname.isPartOf(zoneData.zoneName) && resourceRecord.qname != zoneData.zoneName) {
+ throw ApiException("RRset " + resourceRecord.qname.toString() + " IN " + resourceRecord.qtype.toString() + ": Name is out of zone");
+ }
+ apiCheckQNameAllowedCharacters(resourceRecord.qname.toString());
- g_zoneCache.remove(zonename);
- } catch (...) {
- di.backend->abortTransaction();
- throw;
+ if (resourceRecord.qtype.getCode() == QType::SOA && resourceRecord.qname == zoneData.zoneName) {
+ haveSoa = true;
+ }
}
- // clear caches
- DNSSECKeeper::clearCaches(zonename);
- purgeAuthCaches(zonename.toString() + "$");
+ if (!haveSoa && newKind != DomainInfo::Secondary && newKind != DomainInfo::Consumer) {
+ // Require SOA if this is a primary zone.
+ throw ApiException("Must give SOA record for zone when replacing all RR sets");
+ }
+ if (newKind == DomainInfo::Consumer && !new_records.empty()) {
+ // Allow deleting all RRsets, just not modifying them.
+ throw ApiException("Modifying RRsets in Consumer zones is unsupported");
+ }
- // empty body on success
- resp->body = "";
- resp->status = 204; // No Content: declare that the zone is gone now
- return;
- } else if (req->method == "PATCH") {
- patchZone(B, req, resp);
- return;
- } else if (req->method == "GET") {
- fillZone(B, zonename, resp, req);
- return;
+ checkNewRecords(new_records, zoneData.zoneName);
+
+ zoneData.domainInfo.backend->startTransaction(zoneData.zoneName, static_cast<int>(zoneData.domainInfo.id));
+ for (auto& resourceRecord : new_records) {
+ resourceRecord.domain_id = static_cast<int>(zoneData.domainInfo.id);
+ zoneData.domainInfo.backend->feedRecord(resourceRecord, DNSName());
+ }
+ for (Comment& comment : new_comments) {
+ comment.domain_id = static_cast<int>(zoneData.domainInfo.id);
+ zoneData.domainInfo.backend->feedComment(comment);
+ }
+
+ if (!haveSoa && (newKind == DomainInfo::Secondary || newKind == DomainInfo::Consumer)) {
+ zoneData.domainInfo.backend->setStale(zoneData.domainInfo.id);
+ }
}
- throw HttpMethodNotAllowedException();
+ else {
+ // avoid deleting current zone contents
+ zoneData.domainInfo.backend->startTransaction(zoneData.zoneName, -1);
+ }
+
+ // updateDomainSettingsFromDocument will rectify the zone and update SOA serial.
+ updateDomainSettingsFromDocument(zoneData.backend, zoneData.domainInfo, zoneData.zoneName, document, zoneWasModified);
+ zoneData.domainInfo.backend->commitTransaction();
+
+ purgeAuthCaches(zoneData.zoneName.toString() + "$");
+
+ resp->body = "";
+ resp->status = 204; // No Content, but indicate success
}
-static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
+static void apiServerZoneDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
+ // delete domain
- ostringstream ss;
+ zoneData.domainInfo.backend->startTransaction(zoneData.zoneName, -1);
+ try {
+ if (!zoneData.domainInfo.backend->deleteDomain(zoneData.zoneName)) {
+ throw ApiException("Deleting domain '" + zoneData.zoneName.toString() + "' failed: backend delete failed/unsupported");
+ }
- UeberBackend B;
- DomainInfo di;
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
+ zoneData.domainInfo.backend->commitTransaction();
+
+ g_zoneCache.remove(zoneData.zoneName);
}
+ catch (...) {
+ zoneData.domainInfo.backend->abortTransaction();
+ throw;
+ }
+
+ // clear caches
+ DNSSECKeeper::clearCaches(zoneData.zoneName);
+ purgeAuthCaches(zoneData.zoneName.toString() + "$");
+
+ // empty body on success
+ resp->body = "";
+ resp->status = 204; // No Content: declare that the zone is gone now
+}
+
+static void apiServerZoneDetailPATCH(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+ patchZone(zoneData.backend, zoneData.zoneName, zoneData.domainInfo, req, resp);
+}
- DNSResourceRecord rr;
- SOAData sd;
- di.backend->list(zonename, di.id);
- while(di.backend->get(rr)) {
- if (!rr.qtype.getCode())
+static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+ fillZone(zoneData.backend, zoneData.zoneName, resp, req);
+}
+
+static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
+
+ ostringstream outputStringStream;
+
+ DNSResourceRecord resourceRecord;
+ SOAData soaData;
+ zoneData.domainInfo.backend->list(zoneData.zoneName, static_cast<int>(zoneData.domainInfo.id));
+ while (zoneData.domainInfo.backend->get(resourceRecord)) {
+ if (resourceRecord.qtype.getCode() == 0) {
continue; // skip empty non-terminals
+ }
- ss <<
- rr.qname.toString() << "\t" <<
- rr.ttl << "\t" <<
- "IN" << "\t" <<
- rr.qtype.toString() << "\t" <<
- makeApiRecordContent(rr.qtype, rr.content) <<
- endl;
+ outputStringStream << resourceRecord.qname.toString() << "\t" << resourceRecord.ttl << "\t"
+ << "IN"
+ << "\t" << resourceRecord.qtype.toString() << "\t" << makeApiRecordContent(resourceRecord.qtype, resourceRecord.content) << endl;
}
if (req->accept_json) {
- resp->setJsonBody(Json::object { { "zone", ss.str() } });
- } else {
+ resp->setJsonBody(Json::object{{"zone", outputStringStream.str()}});
+ }
+ else {
resp->headers["Content-Type"] = "text/plain; charset=us-ascii";
- resp->body = ss.str();
+ resp->body = outputStringStream.str();
}
}
-static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
- if(req->method != "PUT")
- throw HttpMethodNotAllowedException();
+static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
- UeberBackend B;
- DomainInfo di;
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
+ if (zoneData.domainInfo.primaries.empty()) {
+ throw ApiException("Domain '" + zoneData.zoneName.toString() + "' is not a secondary domain (or has no primary defined)");
}
- if(di.masters.empty())
- throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain (or has no master defined)");
-
- shuffle(di.masters.begin(), di.masters.end(), pdns::dns_random_engine());
- Communicator.addSuckRequest(zonename, di.masters.front(), SuckRequest::Api);
- resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front().toLogString());
+ shuffle(zoneData.domainInfo.primaries.begin(), zoneData.domainInfo.primaries.end(), pdns::dns_random_engine());
+ Communicator.addSuckRequest(zoneData.zoneName, zoneData.domainInfo.primaries.front(), SuckRequest::Api);
+ resp->setSuccessResult("Added retrieval request for '" + zoneData.zoneName.toString() + "' from primary " + zoneData.domainInfo.primaries.front().toLogString());
}
-static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) {
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
- if(req->method != "PUT")
- throw HttpMethodNotAllowedException();
-
- UeberBackend B;
- DomainInfo di;
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
- }
+static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
- if(!Communicator.notifyDomain(zonename, &B))
+ if (!Communicator.notifyDomain(zoneData.zoneName, &zoneData.backend)) {
throw ApiException("Failed to add to the queue - see server log");
+ }
resp->setSuccessResult("Notification queued");
}
-static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) {
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
- if(req->method != "PUT")
- throw HttpMethodNotAllowedException();
+static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp)
+{
+ ZoneData zoneData{req};
- UeberBackend B;
- DomainInfo di;
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
+ if (zoneData.dnssecKeeper.isPresigned(zoneData.zoneName)) {
+ throw ApiException("Zone '" + zoneData.zoneName.toString() + "' is pre-signed, not rectifying.");
}
- DNSSECKeeper dk(&B);
-
- if (dk.isPresigned(zonename))
- throw ApiException("Zone '" + zonename.toString() + "' is pre-signed, not rectifying.");
-
- string error_msg = "";
+ string error_msg;
string info;
- if (!dk.rectifyZone(zonename, error_msg, info, true))
- throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
+ if (!zoneData.dnssecKeeper.rectifyZone(zoneData.zoneName, error_msg, info, true)) {
+ throw ApiException("Failed to rectify '" + zoneData.zoneName.toString() + "' " + error_msg);
+ }
resp->setSuccessResult("Rectified");
}
-static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) {
- bool zone_disabled;
- SOAData sd;
- DomainInfo di;
- DNSName zonename = apiZoneIdToName(req->parameters["id"]);
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
- }
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): TODO Refactor this function.
+static void patchZone(UeberBackend& backend, const DNSName& zonename, DomainInfo& domainInfo, HttpRequest* req, HttpResponse* resp)
+{
+ bool zone_disabled = false;
+ SOAData soaData;
vector<DNSResourceRecord> new_records;
vector<Comment> new_comments;
Json document = req->json();
auto rrsets = document["rrsets"];
- if (!rrsets.is_array())
+ if (!rrsets.is_array()) {
throw ApiException("No rrsets given in update request");
+ }
- di.backend->startTransaction(zonename);
+ domainInfo.backend->startTransaction(zonename);
try {
string soa_edit_api_kind;
string soa_edit_kind;
- di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
- di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
+ domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
+ domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
bool soa_edit_done = false;
set<std::tuple<DNSName, QType, string>> seen;
QType qtype;
qtype = stringFromJson(rrset, "type");
if (qtype.getCode() == 0) {
- throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
+ throw ApiException("RRset " + qname.toString() + " IN " + stringFromJson(rrset, "type") + ": unknown type given");
}
- if(seen.count({qname, qtype, changetype}))
- {
- throw ApiException("Duplicate RRset "+qname.toString()+" IN "+qtype.toString()+" with changetype: "+changetype);
+ if (seen.count({qname, qtype, changetype}) != 0) {
+ throw ApiException("Duplicate RRset " + qname.toString() + " IN " + qtype.toString() + " with changetype: " + changetype);
}
seen.insert({qname, qtype, changetype});
if (changetype == "DELETE") {
// delete all matching qname/qtype RRs (and, implicitly comments).
- if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
+ if (!domainInfo.backend->replaceRRSet(domainInfo.id, qname, qtype, vector<DNSResourceRecord>())) {
throw ApiException("Hosting backend does not support editing records.");
}
}
else if (changetype == "REPLACE") {
// we only validate for REPLACE, as DELETE can be used to "fix" out of zone records.
- if (!qname.isPartOf(zonename) && qname != zonename)
- throw ApiException("RRset "+qname.toString()+" IN "+qtype.toString()+": Name is out of zone");
+ if (!qname.isPartOf(zonename) && qname != zonename) {
+ throw ApiException("RRset " + qname.toString() + " IN " + qtype.toString() + ": Name is out of zone");
+ }
bool replace_records = rrset["records"].is_array();
bool replace_comments = rrset["comments"].is_array();
new_records.clear();
new_comments.clear();
- if (replace_records) {
- // ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
- int ttl = intFromJson(rrset, "ttl");
-
- gatherRecords(rrset, qname, qtype, ttl, new_records);
-
- for(DNSResourceRecord& rr : new_records) {
- rr.domain_id = di.id;
- if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
- soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
+ try {
+ if (replace_records) {
+ // ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
+ uint32_t ttl = uintFromJson(rrset, "ttl");
+ gatherRecords(rrset, qname, qtype, ttl, new_records);
+
+ for (DNSResourceRecord& resourceRecord : new_records) {
+ resourceRecord.domain_id = static_cast<int>(domainInfo.id);
+ if (resourceRecord.qtype.getCode() == QType::SOA && resourceRecord.qname == zonename) {
+ soa_edit_done = increaseSOARecord(resourceRecord, soa_edit_api_kind, soa_edit_kind);
+ }
}
+ checkNewRecords(new_records, zonename);
}
- checkNewRecords(new_records, zonename);
- }
- if (replace_comments) {
- gatherComments(rrset, qname, qtype, new_comments);
+ if (replace_comments) {
+ gatherComments(rrset, qname, qtype, new_comments);
- for(Comment& c : new_comments) {
- c.domain_id = di.id;
+ for (Comment& comment : new_comments) {
+ comment.domain_id = static_cast<int>(domainInfo.id);
+ }
}
}
+ catch (const JsonException& e) {
+ throw ApiException("New RRsets are invalid: " + string(e.what()));
+ }
if (replace_records) {
bool ent_present = false;
- bool dname_seen = false, ns_seen = false;
+ bool dname_seen = false;
+ bool ns_seen = false;
- di.backend->lookup(QType(QType::ANY), qname, di.id);
- DNSResourceRecord rr;
- while (di.backend->get(rr)) {
- if (rr.qtype.getCode() == QType::ENT) {
+ domainInfo.backend->lookup(QType(QType::ANY), qname, static_cast<int>(domainInfo.id));
+ DNSResourceRecord resourceRecord;
+ while (domainInfo.backend->get(resourceRecord)) {
+ if (resourceRecord.qtype.getCode() == QType::ENT) {
ent_present = true;
/* that's fine, we will override it */
continue;
}
- if (qtype == QType::DNAME || rr.qtype == QType::DNAME)
+ if (qtype == QType::DNAME || resourceRecord.qtype == QType::DNAME) {
dname_seen = true;
- if (qtype == QType::NS || rr.qtype == QType::NS)
+ }
+ if (qtype == QType::NS || resourceRecord.qtype == QType::NS) {
ns_seen = true;
- if (qtype.getCode() != rr.qtype.getCode()
- && (exclusiveEntryTypes.count(qtype.getCode()) != 0
- || exclusiveEntryTypes.count(rr.qtype.getCode()) != 0)) {
+ }
+ if (qtype.getCode() != resourceRecord.qtype.getCode()
+ && (exclusiveEntryTypes.count(qtype.getCode()) != 0
+ || exclusiveEntryTypes.count(resourceRecord.qtype.getCode()) != 0)) {
// leave database handle in a consistent state
- while (di.backend->get(rr))
+ while (domainInfo.backend->get(resourceRecord)) {
;
+ }
- throw ApiException("RRset "+qname.toString()+" IN "+qtype.toString()+": Conflicts with pre-existing RRset");
+ throw ApiException("RRset " + qname.toString() + " IN " + qtype.toString() + ": Conflicts with pre-existing RRset");
}
}
if (dname_seen && ns_seen && qname != zonename) {
- throw ApiException("RRset "+qname.toString()+" IN "+qtype.toString()+": Cannot have both NS and DNAME except in zone apex");
+ throw ApiException("RRset " + qname.toString() + " IN " + qtype.toString() + ": Cannot have both NS and DNAME except in zone apex");
+ }
+ if (!new_records.empty() && domainInfo.kind == DomainInfo::Consumer) {
+ // Allow deleting all RRsets, just not modifying them.
+ throw ApiException("Modifying RRsets in Consumer zones is unsupported");
}
if (!new_records.empty() && ent_present) {
QType qt_ent{0};
- if (!di.backend->replaceRRSet(di.id, qname, qt_ent, new_records)) {
+ if (!domainInfo.backend->replaceRRSet(domainInfo.id, qname, qt_ent, new_records)) {
throw ApiException("Hosting backend does not support editing records.");
}
}
- if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
+ if (!domainInfo.backend->replaceRRSet(domainInfo.id, qname, qtype, new_records)) {
throw ApiException("Hosting backend does not support editing records.");
}
}
if (replace_comments) {
- if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
+ if (!domainInfo.backend->replaceComments(domainInfo.id, qname, qtype, new_comments)) {
throw ApiException("Hosting backend does not support editing comments.");
}
}
}
- else
+ else {
throw ApiException("Changetype not understood");
+ }
}
- zone_disabled = (!B.getSOAUncached(zonename, sd));
+ zone_disabled = (!backend.getSOAUncached(zonename, soaData));
// edit SOA (if needed)
if (!zone_disabled && !soa_edit_api_kind.empty() && !soa_edit_done) {
- DNSResourceRecord rr;
- if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
- if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+ DNSResourceRecord resourceRecord;
+ if (makeIncreasedSOARecord(soaData, soa_edit_api_kind, soa_edit_kind, resourceRecord)) {
+ if (!domainInfo.backend->replaceRRSet(domainInfo.id, resourceRecord.qname, resourceRecord.qtype, vector<DNSResourceRecord>(1, resourceRecord))) {
throw ApiException("Hosting backend does not support editing records.");
}
}
// return old and new serials in headers
- resp->headers["X-PDNS-Old-Serial"] = std::to_string(sd.serial);
- fillSOAData(rr.content, sd);
- resp->headers["X-PDNS-New-Serial"] = std::to_string(sd.serial);
+ resp->headers["X-PDNS-Old-Serial"] = std::to_string(soaData.serial);
+ fillSOAData(resourceRecord.content, soaData);
+ resp->headers["X-PDNS-New-Serial"] = std::to_string(soaData.serial);
}
-
- } catch(...) {
- di.backend->abortTransaction();
+ }
+ catch (...) {
+ domainInfo.backend->abortTransaction();
throw;
}
// Rectify
- DNSSECKeeper dk(&B);
- if (!zone_disabled && !dk.isPresigned(zonename)) {
- string api_rectify;
- if (!di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify) && ::arg().mustDo("default-api-rectify")) {
- api_rectify = "1";
- }
- if (api_rectify == "1") {
- string info;
- string error_msg;
- if (!dk.rectifyZone(zonename, error_msg, info, false)) {
- throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
- }
+ DNSSECKeeper dnssecKeeper(&backend);
+ if (!zone_disabled && !dnssecKeeper.isPresigned(zonename) && isZoneApiRectifyEnabled(domainInfo)) {
+ string info;
+ string error_msg;
+ if (!dnssecKeeper.rectifyZone(zonename, error_msg, info, false)) {
+ throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
}
}
- di.backend->commitTransaction();
+ domainInfo.backend->commitTransaction();
+ DNSSECKeeper::clearCaches(zonename);
purgeAuthCaches(zonename.toString() + "$");
resp->body = "";
resp->status = 204; // No Content, but indicate success
- return;
}
-static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "GET")
- throw HttpMethodNotAllowedException();
-
- string q = req->getvars["q"];
- string sMax = req->getvars["max"];
- string sObjectType = req->getvars["object_type"];
+static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
+{
+ string qVar = req->getvars["q"];
+ string sMaxVar = req->getvars["max"];
+ string sObjectTypeVar = req->getvars["object_type"];
- int maxEnts = 100;
- int ents = 0;
+ size_t maxEnts = 100;
+ size_t ents = 0;
// the following types of data can be searched for using the api
enum class ObjectType
ZONE,
RECORD,
COMMENT
- } objectType;
+ } objectType{};
- if (q.empty())
+ if (qVar.empty()) {
throw ApiException("Query q can't be blank");
- if (!sMax.empty())
- maxEnts = std::stoi(sMax);
- if (maxEnts < 1)
+ }
+ if (!sMaxVar.empty()) {
+ maxEnts = std::stoi(sMaxVar);
+ }
+ if (maxEnts < 1) {
throw ApiException("Maximum entries must be larger than 0");
+ }
- if (sObjectType.empty())
+ if (sObjectTypeVar.empty() || sObjectTypeVar == "all") {
objectType = ObjectType::ALL;
- else if (sObjectType == "all")
- objectType = ObjectType::ALL;
- else if (sObjectType == "zone")
+ }
+ else if (sObjectTypeVar == "zone") {
objectType = ObjectType::ZONE;
- else if (sObjectType == "record")
+ }
+ else if (sObjectTypeVar == "record") {
objectType = ObjectType::RECORD;
- else if (sObjectType == "comment")
+ }
+ else if (sObjectTypeVar == "comment") {
objectType = ObjectType::COMMENT;
- else
+ }
+ else {
throw ApiException("object_type must be one of the following options: all, zone, record, comment");
+ }
- SimpleMatch sm(q,true);
- UeberBackend B;
+ SimpleMatch simpleMatch(qVar, true);
+ UeberBackend backend;
vector<DomainInfo> domains;
vector<DNSResourceRecord> result_rr;
vector<Comment> result_c;
- map<int,DomainInfo> zoneIdZone;
- map<int,DomainInfo>::iterator val;
+ map<int, DomainInfo> zoneIdZone;
+ map<int, DomainInfo>::iterator val;
Json::array doc;
- B.getAllDomains(&domains, false, true);
+ backend.getAllDomains(&domains, false, true);
- for(const DomainInfo& di: domains)
- {
- if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ents < maxEnts && sm.match(di.zone)) {
- doc.push_back(Json::object {
- { "object_type", "zone" },
- { "zone_id", apiZoneNameToId(di.zone) },
- { "name", di.zone.toString() }
- });
+ for (const DomainInfo& domainInfo : domains) {
+ if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ents < maxEnts && simpleMatch.match(domainInfo.zone)) {
+ doc.push_back(Json::object{
+ {"object_type", "zone"},
+ {"zone_id", apiZoneNameToId(domainInfo.zone)},
+ {"name", domainInfo.zone.toString()}});
ents++;
}
- zoneIdZone[di.id] = di; // populate cache
+ zoneIdZone[static_cast<int>(domainInfo.id)] = domainInfo; // populate cache
}
- if ((objectType == ObjectType::ALL || objectType == ObjectType::RECORD) && B.searchRecords(q, maxEnts, result_rr))
- {
- for(const DNSResourceRecord& rr: result_rr)
- {
- if (!rr.qtype.getCode())
+ if ((objectType == ObjectType::ALL || objectType == ObjectType::RECORD) && backend.searchRecords(qVar, maxEnts, result_rr)) {
+ for (const DNSResourceRecord& resourceRecord : result_rr) {
+ if (resourceRecord.qtype.getCode() == 0) {
continue; // skip empty non-terminals
+ }
+
+ auto object = Json::object{
+ {"object_type", "record"},
+ {"name", resourceRecord.qname.toString()},
+ {"type", resourceRecord.qtype.toString()},
+ {"ttl", (double)resourceRecord.ttl},
+ {"disabled", resourceRecord.disabled},
+ {"content", makeApiRecordContent(resourceRecord.qtype, resourceRecord.content)}};
- auto object = Json::object {
- { "object_type", "record" },
- { "name", rr.qname.toString() },
- { "type", rr.qtype.toString() },
- { "ttl", (double)rr.ttl },
- { "disabled", rr.disabled },
- { "content", makeApiRecordContent(rr.qtype, rr.content) }
- };
- if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) {
+ val = zoneIdZone.find(resourceRecord.domain_id);
+ if (val != zoneIdZone.end()) {
object["zone_id"] = apiZoneNameToId(val->second.zone);
object["zone"] = val->second.zone.toString();
}
- doc.push_back(object);
+ doc.emplace_back(object);
}
}
- if ((objectType == ObjectType::ALL || objectType == ObjectType::COMMENT) && B.searchComments(q, maxEnts, result_c))
- {
- for(const Comment &c: result_c)
- {
- auto object = Json::object {
- { "object_type", "comment" },
- { "name", c.qname.toString() },
- { "type", c.qtype.toString() },
- { "content", c.content }
- };
- if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) {
+ if ((objectType == ObjectType::ALL || objectType == ObjectType::COMMENT) && backend.searchComments(qVar, maxEnts, result_c)) {
+ for (const Comment& comment : result_c) {
+ auto object = Json::object{
+ {"object_type", "comment"},
+ {"name", comment.qname.toString()},
+ {"type", comment.qtype.toString()},
+ {"content", comment.content}};
+
+ val = zoneIdZone.find(comment.domain_id);
+ if (val != zoneIdZone.end()) {
object["zone_id"] = apiZoneNameToId(val->second.zone);
object["zone"] = val->second.zone.toString();
}
- doc.push_back(object);
+ doc.emplace_back(object);
}
}
resp->setJsonBody(doc);
}
-static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
- if(req->method != "PUT")
- throw HttpMethodNotAllowedException();
-
+static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp)
+{
DNSName canon = apiNameToDNSName(req->getvars["domain"]);
if (g_zoneCache.isEnabled()) {
- DomainInfo di;
- UeberBackend B;
- if (B.getDomainInfo(canon, di, false)) {
+ DomainInfo domainInfo;
+ UeberBackend backend;
+ if (backend.getDomainInfo(canon, domainInfo, false)) {
// zone exists (uncached), add/update it in the zone cache.
// Handle this first, to avoid concurrent queries re-populating the other caches.
- g_zoneCache.add(di.zone, di.id);
+ g_zoneCache.add(domainInfo.zone, static_cast<int>(domainInfo.id));
}
else {
- g_zoneCache.remove(di.zone);
+ g_zoneCache.remove(domainInfo.zone);
}
}
+ DNSSECKeeper::clearCaches(canon);
// purge entire zone from cache, not just zone-level records.
uint64_t count = purgeAuthCaches(canon.toString() + "$");
- resp->setJsonBody(Json::object {
- { "count", (int) count },
- { "result", "Flushed cache." }
- });
+ resp->setJsonBody(Json::object{
+ {"count", (int)count},
+ {"result", "Flushed cache."}});
}
-static std::ostream& operator<<(std::ostream& os, StatType statType)
+static std::ostream& operator<<(std::ostream& outStream, StatType statType)
{
- switch (statType)
- {
- case StatType::counter: return os << "counter";
- case StatType::gauge: return os << "gauge";
+ switch (statType) {
+ case StatType::counter:
+ return outStream << "counter";
+ case StatType::gauge:
+ return outStream << "gauge";
};
- return os << static_cast<uint16_t>(statType);
+ return outStream << static_cast<uint16_t>(statType);
}
-static void prometheusMetrics(HttpRequest* req, HttpResponse* resp) {
- if (req->method != "GET")
- throw HttpMethodNotAllowedException();
-
+static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp)
+{
std::ostringstream output;
- for (const auto &metricName : S.getEntries()) {
+ for (const auto& metricName : S.getEntries()) {
// Prometheus suggest using '_' instead of '-'
std::string prometheusMetricName = "pdns_auth_" + boost::replace_all_copy(metricName, "-", "_");
output << prometheusMetricName << " " << S.read(metricName) << "\n";
}
- output << "# HELP pdns_auth_info " << "Info from PowerDNS, value is always 1" << "\n";
- output << "# TYPE pdns_auth_info " << "gauge" << "\n";
- output << "pdns_auth_info{version=\"" << VERSION << "\"} " << "1" << "\n";
+ output << "# HELP pdns_auth_info "
+ << "Info from PowerDNS, value is always 1"
+ << "\n";
+ output << "# TYPE pdns_auth_info "
+ << "gauge"
+ << "\n";
+ output << "pdns_auth_info{version=\"" << VERSION << "\"} "
+ << "1"
+ << "\n";
resp->body = output.str();
resp->headers["Content-Type"] = "text/plain";
resp->status = 200;
}
-void AuthWebServer::cssfunction(HttpRequest* /* req */, HttpResponse* resp)
+static void cssfunction(HttpRequest* /* req */, HttpResponse* resp)
{
resp->headers["Cache-Control"] = "max-age=86400";
resp->headers["Content-Type"] = "text/css";
ostringstream ret;
- ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
- ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
- ret<<"a { color: #0959c2; }"<<endl;
- ret<<"a:hover { color: #3B8EC8; }"<<endl;
- ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
- ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
- ret<<".row:after { clear: both; }"<<endl;
- ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
- ret<<".all { width: 100%; }"<<endl;
- ret<<".headl { width: 60%; }"<<endl;
- ret<<".header { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
- ret<<"background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJoAAAAUCAYAAAB1RSS/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACtgAAArYBAHIqtQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABBTSURBVGiBtVp7cFRVmv9u3763b7/f9It00iFACBohgCEyQYgKI49CLV3cWaoEZBcfo2shu7KOtZbjrqOuVQtVWFuOrPqPRU3NgOIDlkgyJEYJwUAqjzEJedFJupN0p9/v+9o/mtve7r790HF+VbeSPue7555zz+98z4ucOXNmgWVZBH4AK5PJGIPBQBqNxpTNZkthGMZCCUxMTBCDg4PyiYkJWTQaRc1mc7Kuri7a1NQU4ssxDAOffPKJAQCynvnII494ESTddO3aNaXT6SS4TplMRj/44IM+7ndXV5dqfn5ewh9306ZNQZqmobu7W11qri0tLX6tVkv19vYqpqampPw+BEFYtVpNGQwG0mKxpJYsWUIKjTE6OiodGBhQ8NcgkUgYjUZDORyOhM1mSxV6fjAYFF+6dEnLb9NoNOR9990X4H53dHSovV4vzpfZvn27T6FQ0Py2sbExorOzU+N2uwmWZUGv15N33nlnuLGxMZy7byyVQEJ//nd9Yuz/lJR/HBdrHSlJ9baIuuV1L4LJ8/Y49pc/KcJX39WRC4MEgskY3Lourmn5rQdbckfe2ijfOBZo+40xNXtNysR9KLZkdVK+9oBf0fBkCABA3NraamTZwjxSKpXUAw884G1paQkUIty5c+f0Fy5cWMIfx+l0Snt6ejTt7e26AwcOuKxWawoAQCQSQW9vr3pxcTHrJTY3Nwe5Tb18+bJ2bGxMzvWhKMpu27bNj6IoCwDQ1tamd7lcRM79genpaaK1tdVQcDG3sXbt2rBWq6X6+/sV3d3d2mKyy5cvj+7cudO7atWqGL99bGxMWuxZOp0utX37du+9994b5A4Qh2AwiObei6Ioe/fdd4eVSiUNAHD16lX1+Pi4nC+zadOmIJ9oZ8+eNeTu3/T0tLSvr0/V3d0dPXr0qJNrZ+KL6MKpjZWUbyxzQMmFIYJcGCISw5+qjE9+M4UqLJmx/RdeWBK+elKfGTjuR+OhWSxx86JS/9D/zsrufDzMdSXGv5J5/vBYBZuKiLi25HS3LDndLUuMX1IYHjvtynQUQjgcFp89e9b8zjvv2BmGyepjWRbeffdd2/nz55cUIqvT6ZSeOHHC7vf7xVyb3W6P58rNzc1liOfxeLJISNM04na7Me63z+fD+P1SqZQupHn+Wty8eVN+4sSJyv7+fnlp6R/g8/nw06dPW0+ePLmUJEmklDxN08iVK1dU5Y7f0dGhvnjxYkElQVFU1jP9Xz5j4pMsSzYwifvPPWnhfsdHPpdnkYwHlk4ivi9/baFDM2IAACYZEi1++qSVTzI+YkN/VEe++726JNE4TE1Nyc6cOWPkt3322Wf6/v7+ki8nEAhgH3zwQWYhDoejINGSyaQoFAphuf2zs7MSAIBIJIImEgmU32ez2RLlruOngGVZ+Oijj6w+n09cWjobg4ODyg8//NBSWhLgu+++K4toJEkin376qancObBkFIl/f7bo2ImxC0om5kUBACK9pzTFZJlEAI0O/kEJABAf+UJOh115+8VH5MZHGkGimc3mRK66BwBoa2szBAIBMUB6w1tbW415QgUwOjqqGB4elgIA1NTU5BGN02IulwsXOqUul0sCADA/P5+3qIqKip+NaARBMBiGMbnt0Wg0z68qF729vepr164pS8k5nU7ZwsJC0U0DAOjp6VHGYjE0t10kEgmqt5TrOwIYqqRWTbmuSQAASM9fiFKy5Fx/Wnaur7Ss53tC8IQ+/fTTM/F4HH3rrbcc/E1nWRYmJyeJtWvXRr7++mt1rnoGANi6devipk2bgsePH7dHIpGs8Ts7O7W1tbXxqqqqJIZhLN+keDweDADA7XbjuWPebpcAACwsLOT1V1VVFSSayWRKvvLKK5P8tmLBTVNTk//hhx/2vv/++5aBgYEsLeB0OqWF7gMAsFqtiYqKivj169c1ueaytbVVv2HDhnChewHS7/fKlSuqPXv2LBaTyw1gAABqa2sjhw4dck1PT0vOnz9v4O+NWFNdlluBqispAABUYSEp/6TgPmRkVba0rGppybFRpZksaDodDkeioqIiT/M4nU4JAMDIyEiez1JTUxN9/PHHFyoqKpJbtmzx5faPj4/LANKOr9VqzRqbi7D4vhof8/PzOMAPhMyZa948OSAIAjiOs/xLSFvzIZFImO3bt+fNn9OqhaDRaMiDBw/Obd26NY8oTqdTWmhtfPT29paMmkOhUJ6CkEgkjFKppOvq6mIvvviis76+PkNqVF1BiQ21yWJjoiobiRlWpQAACMeWaKk5EMu2RQEAiOr7YyBCi2YliMrN0aI+Wjwez+vn/KOZmZk8lbl69eoI97+QeQwEAhgXFFRVVWX1+/1+nGVZyE1bcPB6vRKWZSE35JdKpbTJZCp4qiiKQmZmZnDuEiKqEITWTtN0SfMDALBjx45FiUSSZ35HRkaKakQAgPn5ecnU1FRRQuv1+rz0Qn9/v+ry5ctqgPTh2rFjR9ZB0e78Hzcgedb2NhDQ7vq9C24fQNXm3/gww8qCxJTX/4OfcGyJAwBgS+pSqo3/XFADo0oLqdn2lkeQaAzDIB0dHWqPx5O3YK1WSzIMA7lmEQDAaDSSQv/zEQwGUQCA6urqLKJRFIV4PB6MH3GqVCqS3z83N4cvLi5mEaVUIOD1evHXX399GXedOnXKWkweIJ3r++abb/IcYqPRWDA3xodUKmWEyMCZ/1IolQvMfXcAabN7+vRp68cff2wS8nElVVvihl99cQtV27PmhapspOHvzzmJ5Tsy6RtELGGX7G+7JV2xIysHiqAYq/rFv3h0e96f57drHnjTo2n57TwiJrIOl6SyOWo6cPmWiNAwgj7am2++6Ugmk4IkrK2tjUWjUVRoMXK5PJOHkclkdJ4AAESjURQAYPny5YKRJ59odXV1EX6ea2ZmRpKbf/s5AwEAgO+//17+8ssv1/j9/jzNt3HjxmC542g0GjI318etXQgoirKcxrx+/brKYDAUJPW6desiFy5ciM/MzORpyM7OTl04HEYPHz7synURiJpfxizPj4+T8/0S0jOEiw2rUrh5TRJE+TRAFWba+KvPZung9Hxy9iohwpUMvnRjQkSo8zQ1ICJQbX7Zp2h8LpCa7ZEwUY8Yt21IiHXLMopCkEyFSFZZWRmz2+0FVSqXUL39v6AM5yTr9XpKrVZnab2RkRFZKpXKPHvlypUxvuM+PT0tCQaDWW+lWCDwUzA3N0cIkay2tjbS0tLiL3ccoYNWzPRWVVXFcBxnAACCwSAmRCIOCILA/v373QqFghLqv3Hjhrq9vb1gioIFBNLFoLI8gbKBILdHRNi8ocvOC6nVavLw4cOzAAAKhYJGEARytRo/5A6Hw4JMk8lkmRNht9vjAwMDmU0dGhril3TAbDanDAZD0u12EwAAw8PDCoZhspZQLBD4KRBa17Zt27wPPfSQVyQqO+0IQumHQloeIB0Jr169Onzjxg01QOHDzqGioiJ55MiRW8ePH68UCg6+/PJLY0tLS4Cv1RJjF2W+z5+2UEFnxiqgKhup2/muW7pyV1YAQEfmUN9n/2SOj57PRN4IirHKphe86q2vLSIozktHMBDq+p0u3PkfRpZKZOYtqWyOavd86BZrlxWOOjMTQVH2jjvuCL/wwgtOvV5PAaQ3QyqV5r20SCSSebmhUEiQaCqVKnNfLkk4QnEwmUyk2WzOaNDp6emsU14qEABIO87Hjh2b5K79+/e7i8kLVS0UCgXF19blINfEAwCoVCpBDcShsbExVKw/FzabLXXs2LFJIT81Go2K+YFPYqpDuvDx7ko+yQAA6NAs5jn9sD1+84KMa2OpJLLw0X2VfJIBALA0iYS6/svoO/ePWcni4KWXjKH2V0x8kgEAJG99Lfd8uLmSSfiFj+j999/v3bt3r/vgwYMzb7zxxthzzz03w9UqOVit1rzFjY6OZiY7NDSUl/4gCIIxmUyZcZYtW1ZQG0mlUloul9Nmszkjn1sCK6cigGEY63A4EtxlsViKOvQOhyOm0WiyyNve3q4vN+IESKeAhKJnISeej/r6+ijfzy2Evr4+Oad19Xo9dejQoVkhbev1ejNE83/xjAXYfPcqDRZ8nz9lhdtjhjr/U0d6RwoGLtH+j7WJyctSAADSM4SHu/9bsFwFAECHXVjwq381ChKtubk50NLSEmhsbAxrNBrBU7hixYq8XMvg4KByamqKmJubw7799ts8H6GqqirGV+XV1dWJQppCq9WSAABWq7WgT/hzBwIAaW3d0NCQpVkCgQDW1dVVVnnI5XLhp06dsuW24zjO1NTUFJ0viqJsfX19Sa3W09Ojfu+996xcCkapVNIoiuaxyGAwkAAAdHBaXIw4AGnNRnqHcQCAxOTlknXdxHirHAAgOXFJBkzxQ5ic6pD/6Nodh9uRT1YxPRaLoW+//XaVWCxmhXyMe+65J8D/jeM4a7FYEkKOL5ceWLp0aUGiVVZWliSax+PBX3rppRp+27PPPjtdLKhpamoKtre3Z53Sr776yrB58+a8LzH4GB4eVr722muCpaaGhoYgQRCFVEoGGzduDF65cqVkqevGjRvqgYEBld1uj8/NzUlIMtsNwnGc4VJMlH+yrNwhFbglxoyrUnTEXVKeDs2K039nSstG5rDyvdscLF26NNnQ0JAX7tM0jQiRzGQyJdevXx/Jba+srBQ0J3q9ngRIBwRisVhQ65UTCNA0jQQCAYx/CZXO+LDb7UmLxZJFYo/Hg1+9erVovTLXtHMgCILevXt30bISh5UrV8ZzTXchUBSFTExMyIQCj7q6ugh3KHDbugSIhN8hHxLb+iQAAGasK+2SmOvTsuY1pWWNqxI/mWgAAI8++uiCTqcrmcTEMIzZt2+fW8hMFvJbuNMoEokEM+FSqZQ2m81/k0+DAADWr1+fZ8IuXrxY8lu3XKAoyu7bt8/NmbFSEDLdPxYSiYTZu3dvJqmKYHJWturhomNKa34ZFskMNACAYt2hQDFZEaGh5XfsDQMAECt2R1Glreja5GsOBP4qoul0Ouro0aO3TCZTQTOkUqnII0eO3FqxYoUgoYRKVQAA/ISl0Ph/60+Dmpqa8syky+Ui+vr6yv4uTavVks8///ytUsV0oWf/GHk+pFIp/cQTT8zqdLos31q36+S8WFcjuE9iTVVK99CpTDQuXbk7qmz8taAGRlAJq9t50o2qllIAACKJitHu+cCF4ApBdS5d/XdB+fqnguLq6upobm4Kx/GyQ3m9Xk+9+uqrk21tbZquri6t1+vFWZYFi8WSdDgcsV27di1qtdqCYb3ZbCZra2sjueaW/yl0XV1dNBwOZ/mT/KIxB6VSSTkcjlhuey44X8lkMqVy5TmC6/V6qrGx0Z8bPY6OjsrWrFkT1el0ec9CUZRVqVSUWq2mqqur4xs2bAgL+XQSiYTJvZcf9Njt9uRdd90Vys2PcQnd5ubmAMMwcPPmTXk0GhUDpCsRVVVVsccee2yBS0PxIZLqacszfZPBP7+qj4+1Kilf+lNuYtkDEU3La3mfcmsfPL4gqfxFrJxPuYll22Kmp/omgpf+zZia7ZEyCT+KGVcn5WsP+uUNh0IAAP8PaQRnE4MgdzkAAAAASUVORK5CYII=);";
- ret<<" width: 154px; height: 20px; }"<<endl;
- ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
- ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
- ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
- ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
- ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
- ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
- ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
- ret<<"table.data tr:hover { background: white; }"<<endl;
- ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
- ret<<".resetring {float: right; }"<<endl;
- ret<<".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFCQn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvylUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK+bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CYII=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; background-repeat: no-repeat; }"<<endl;
- ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
- ret<<".resizering {float: right;}"<<endl;
+ ret << "* { box-sizing: border-box; margin: 0; padding: 0; }" << endl;
+ ret << "body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }" << endl;
+ ret << "a { color: #0959c2; }" << endl;
+ ret << "a:hover { color: #3B8EC8; }" << endl;
+ ret << ".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }" << endl;
+ ret << ".row:before, .row:after { display: table; content:\" \"; }" << endl;
+ ret << ".row:after { clear: both; }" << endl;
+ ret << ".columns { position: relative; min-height: 1px; float: left; }" << endl;
+ ret << ".all { width: 100%; }" << endl;
+ ret << ".headl { width: 60%; }" << endl;
+ ret << ".header { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
+ ret << "background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJoAAAAUCAYAAAB1RSS/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACtgAAArYBAHIqtQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABBTSURBVGiBtVp7cFRVmv9u3763b7/f9It00iFACBohgCEyQYgKI49CLV3cWaoEZBcfo2shu7KOtZbjrqOuVQtVWFuOrPqPRU3NgOIDlkgyJEYJwUAqjzEJedFJupN0p9/v+9o/mtve7r790HF+VbeSPue7555zz+98z4ucOXNmgWVZBH4AK5PJGIPBQBqNxpTNZkthGMZCCUxMTBCDg4PyiYkJWTQaRc1mc7Kuri7a1NQU4ssxDAOffPKJAQCynvnII494ESTddO3aNaXT6SS4TplMRj/44IM+7ndXV5dqfn5ewh9306ZNQZqmobu7W11qri0tLX6tVkv19vYqpqampPw+BEFYtVpNGQwG0mKxpJYsWUIKjTE6OiodGBhQ8NcgkUgYjUZDORyOhM1mSxV6fjAYFF+6dEnLb9NoNOR9990X4H53dHSovV4vzpfZvn27T6FQ0Py2sbExorOzU+N2uwmWZUGv15N33nlnuLGxMZy7byyVQEJ//nd9Yuz/lJR/HBdrHSlJ9baIuuV1L4LJ8/Y49pc/KcJX39WRC4MEgskY3Lourmn5rQdbckfe2ijfOBZo+40xNXtNysR9KLZkdVK+9oBf0fBkCABA3NraamTZwjxSKpXUAw884G1paQkUIty5c+f0Fy5cWMIfx+l0Snt6ejTt7e26AwcOuKxWawoAQCQSQW9vr3pxcTHrJTY3Nwe5Tb18+bJ2bGxMzvWhKMpu27bNj6IoCwDQ1tamd7lcRM79genpaaK1tdVQcDG3sXbt2rBWq6X6+/sV3d3d2mKyy5cvj+7cudO7atWqGL99bGxMWuxZOp0utX37du+9994b5A4Qh2AwiObei6Ioe/fdd4eVSiUNAHD16lX1+Pi4nC+zadOmIJ9oZ8+eNeTu3/T0tLSvr0/V3d0dPXr0qJNrZ+KL6MKpjZWUbyxzQMmFIYJcGCISw5+qjE9+M4UqLJmx/RdeWBK+elKfGTjuR+OhWSxx86JS/9D/zsrufDzMdSXGv5J5/vBYBZuKiLi25HS3LDndLUuMX1IYHjvtynQUQjgcFp89e9b8zjvv2BmGyepjWRbeffdd2/nz55cUIqvT6ZSeOHHC7vf7xVyb3W6P58rNzc1liOfxeLJISNM04na7Me63z+fD+P1SqZQupHn+Wty8eVN+4sSJyv7+fnlp6R/g8/nw06dPW0+ePLmUJEmklDxN08iVK1dU5Y7f0dGhvnjxYkElQVFU1jP9Xz5j4pMsSzYwifvPPWnhfsdHPpdnkYwHlk4ivi9/baFDM2IAACYZEi1++qSVTzI+YkN/VEe++726JNE4TE1Nyc6cOWPkt3322Wf6/v7+ki8nEAhgH3zwQWYhDoejINGSyaQoFAphuf2zs7MSAIBIJIImEgmU32ez2RLlruOngGVZ+Oijj6w+n09cWjobg4ODyg8//NBSWhLgu+++K4toJEkin376qancObBkFIl/f7bo2ImxC0om5kUBACK9pzTFZJlEAI0O/kEJABAf+UJOh115+8VH5MZHGkGimc3mRK66BwBoa2szBAIBMUB6w1tbW415QgUwOjqqGB4elgIA1NTU5BGN02IulwsXOqUul0sCADA/P5+3qIqKip+NaARBMBiGMbnt0Wg0z68qF729vepr164pS8k5nU7ZwsJC0U0DAOjp6VHGYjE0t10kEgmqt5TrOwIYqqRWTbmuSQAASM9fiFKy5Fx/Wnaur7Ss53tC8IQ+/fTTM/F4HH3rrbcc/E1nWRYmJyeJtWvXRr7++mt1rnoGANi6devipk2bgsePH7dHIpGs8Ts7O7W1tbXxqqqqJIZhLN+keDweDADA7XbjuWPebpcAACwsLOT1V1VVFSSayWRKvvLKK5P8tmLBTVNTk//hhx/2vv/++5aBgYEsLeB0OqWF7gMAsFqtiYqKivj169c1ueaytbVVv2HDhnChewHS7/fKlSuqPXv2LBaTyw1gAABqa2sjhw4dck1PT0vOnz9v4O+NWFNdlluBqispAABUYSEp/6TgPmRkVba0rGppybFRpZksaDodDkeioqIiT/M4nU4JAMDIyEiez1JTUxN9/PHHFyoqKpJbtmzx5faPj4/LANKOr9VqzRqbi7D4vhof8/PzOMAPhMyZa948OSAIAjiOs/xLSFvzIZFImO3bt+fNn9OqhaDRaMiDBw/Obd26NY8oTqdTWmhtfPT29paMmkOhUJ6CkEgkjFKppOvq6mIvvviis76+PkNqVF1BiQ21yWJjoiobiRlWpQAACMeWaKk5EMu2RQEAiOr7YyBCi2YliMrN0aI+Wjwez+vn/KOZmZk8lbl69eoI97+QeQwEAhgXFFRVVWX1+/1+nGVZyE1bcPB6vRKWZSE35JdKpbTJZCp4qiiKQmZmZnDuEiKqEITWTtN0SfMDALBjx45FiUSSZ35HRkaKakQAgPn5ecnU1FRRQuv1+rz0Qn9/v+ry5ctqgPTh2rFjR9ZB0e78Hzcgedb2NhDQ7vq9C24fQNXm3/gww8qCxJTX/4OfcGyJAwBgS+pSqo3/XFADo0oLqdn2lkeQaAzDIB0dHWqPx5O3YK1WSzIMA7lmEQDAaDSSQv/zEQwGUQCA6urqLKJRFIV4PB6MH3GqVCqS3z83N4cvLi5mEaVUIOD1evHXX399GXedOnXKWkweIJ3r++abb/IcYqPRWDA3xodUKmWEyMCZ/1IolQvMfXcAabN7+vRp68cff2wS8nElVVvihl99cQtV27PmhapspOHvzzmJ5Tsy6RtELGGX7G+7JV2xIysHiqAYq/rFv3h0e96f57drHnjTo2n57TwiJrIOl6SyOWo6cPmWiNAwgj7am2++6Ugmk4IkrK2tjUWjUVRoMXK5PJOHkclkdJ4AAESjURQAYPny5YKRJ59odXV1EX6ea2ZmRpKbf/s5AwEAgO+//17+8ssv1/j9/jzNt3HjxmC542g0GjI318etXQgoirKcxrx+/brKYDAUJPW6desiFy5ciM/MzORpyM7OTl04HEYPHz7synURiJpfxizPj4+T8/0S0jOEiw2rUrh5TRJE+TRAFWba+KvPZung9Hxy9iohwpUMvnRjQkSo8zQ1ICJQbX7Zp2h8LpCa7ZEwUY8Yt21IiHXLMopCkEyFSFZZWRmz2+0FVSqXUL39v6AM5yTr9XpKrVZnab2RkRFZKpXKPHvlypUxvuM+PT0tCQaDWW+lWCDwUzA3N0cIkay2tjbS0tLiL3ccoYNWzPRWVVXFcBxnAACCwSAmRCIOCILA/v373QqFghLqv3Hjhrq9vb1gioIFBNLFoLI8gbKBILdHRNi8ocvOC6nVavLw4cOzAAAKhYJGEARytRo/5A6Hw4JMk8lkmRNht9vjAwMDmU0dGhril3TAbDanDAZD0u12EwAAw8PDCoZhspZQLBD4KRBa17Zt27wPPfSQVyQqO+0IQumHQloeIB0Jr169Onzjxg01QOHDzqGioiJ55MiRW8ePH68UCg6+/PJLY0tLS4Cv1RJjF2W+z5+2UEFnxiqgKhup2/muW7pyV1YAQEfmUN9n/2SOj57PRN4IirHKphe86q2vLSIozktHMBDq+p0u3PkfRpZKZOYtqWyOavd86BZrlxWOOjMTQVH2jjvuCL/wwgtOvV5PAaQ3QyqV5r20SCSSebmhUEiQaCqVKnNfLkk4QnEwmUyk2WzOaNDp6emsU14qEABIO87Hjh2b5K79+/e7i8kLVS0UCgXF19blINfEAwCoVCpBDcShsbExVKw/FzabLXXs2LFJIT81Go2K+YFPYqpDuvDx7ko+yQAA6NAs5jn9sD1+84KMa2OpJLLw0X2VfJIBALA0iYS6/svoO/ePWcni4KWXjKH2V0x8kgEAJG99Lfd8uLmSSfiFj+j999/v3bt3r/vgwYMzb7zxxthzzz03w9UqOVit1rzFjY6OZiY7NDSUl/4gCIIxmUyZcZYtW1ZQG0mlUloul9Nmszkjn1sCK6cigGEY63A4EtxlsViKOvQOhyOm0WiyyNve3q4vN+IESKeAhKJnISeej/r6+ijfzy2Evr4+Oad19Xo9dejQoVkhbev1ejNE83/xjAXYfPcqDRZ8nz9lhdtjhjr/U0d6RwoGLtH+j7WJyctSAADSM4SHu/9bsFwFAECHXVjwq381ChKtubk50NLSEmhsbAxrNBrBU7hixYq8XMvg4KByamqKmJubw7799ts8H6GqqirGV+XV1dWJQppCq9WSAABWq7WgT/hzBwIAaW3d0NCQpVkCgQDW1dVVVnnI5XLhp06dsuW24zjO1NTUFJ0viqJsfX19Sa3W09Ojfu+996xcCkapVNIoiuaxyGAwkAAAdHBaXIw4AGnNRnqHcQCAxOTlknXdxHirHAAgOXFJBkzxQ5ic6pD/6Nodh9uRT1YxPRaLoW+//XaVWCxmhXyMe+65J8D/jeM4a7FYEkKOL5ceWLp0aUGiVVZWliSax+PBX3rppRp+27PPPjtdLKhpamoKtre3Z53Sr776yrB58+a8LzH4GB4eVr722muCpaaGhoYgQRCFVEoGGzduDF65cqVkqevGjRvqgYEBld1uj8/NzUlIMtsNwnGc4VJMlH+yrNwhFbglxoyrUnTEXVKeDs2K039nSstG5rDyvdscLF26NNnQ0JAX7tM0jQiRzGQyJdevXx/Jba+srBQ0J3q9ngRIBwRisVhQ65UTCNA0jQQCAYx/CZXO+LDb7UmLxZJFYo/Hg1+9erVovTLXtHMgCILevXt30bISh5UrV8ZzTXchUBSFTExMyIQCj7q6ugh3KHDbugSIhN8hHxLb+iQAAGasK+2SmOvTsuY1pWWNqxI/mWgAAI8++uiCTqcrmcTEMIzZt2+fW8hMFvJbuNMoEokEM+FSqZQ2m81/k0+DAADWr1+fZ8IuXrxY8lu3XKAoyu7bt8/NmbFSEDLdPxYSiYTZu3dvJqmKYHJWturhomNKa34ZFskMNACAYt2hQDFZEaGh5XfsDQMAECt2R1Glreja5GsOBP4qoul0Ouro0aO3TCZTQTOkUqnII0eO3FqxYoUgoYRKVQAA/ISl0Ph/60+Dmpqa8syky+Ui+vr6yv4uTavVks8///ytUsV0oWf/GHk+pFIp/cQTT8zqdLos31q36+S8WFcjuE9iTVVK99CpTDQuXbk7qmz8taAGRlAJq9t50o2qllIAACKJitHu+cCF4ApBdS5d/XdB+fqnguLq6upobm4Kx/GyQ3m9Xk+9+uqrk21tbZquri6t1+vFWZYFi8WSdDgcsV27di1qtdqCYb3ZbCZra2sjueaW/yl0XV1dNBwOZ/mT/KIxB6VSSTkcjlhuey44X8lkMqVy5TmC6/V6qrGx0Z8bPY6OjsrWrFkT1el0ec9CUZRVqVSUWq2mqqur4xs2bAgL+XQSiYTJvZcf9Njt9uRdd90Vys2PcQnd5ubmAMMwcPPmTXk0GhUDpCsRVVVVsccee2yBS0PxIZLqacszfZPBP7+qj4+1Kilf+lNuYtkDEU3La3mfcmsfPL4gqfxFrJxPuYll22Kmp/omgpf+zZia7ZEyCT+KGVcn5WsP+uUNh0IAAP8PaQRnE4MgdzkAAAAASUVORK5CYII=);";
+ ret << " width: 154px; height: 20px; }" << endl;
+ ret << "a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }" << endl;
+ ret << "footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }" << endl;
+ ret << "footer.row { margin-top: 1em; margin-bottom: 1em; }" << endl;
+ ret << ".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }" << endl;
+ ret << "table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }" << endl;
+ ret << "table.data td { border-bottom: 1px solid #333; padding: 2px; }" << endl;
+ ret << "table.data tr:nth-child(2n) { background: #e2e2e2; }" << endl;
+ ret << "table.data tr:hover { background: white; }" << endl;
+ ret << ".ringmeta { margin-bottom: 5px; }" << endl;
+ ret << ".resetring {float: right; }" << endl;
+ ret << ".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFCQn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvylUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK+bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CYII=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; background-repeat: no-repeat; }" << endl;
+ ret << ".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}" << endl;
+ ret << ".resizering {float: right;}" << endl;
resp->body = ret.str();
resp->status = 200;
}
{
try {
setThreadName("pdns/webserver");
- if(::arg().mustDo("api")) {
- d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush);
- d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig);
- d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData);
- d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics);
- d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries/<ip>/<nameserver>", &apiServerAutoprimaryDetail);
- d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimaries);
- d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetail);
- d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeys);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", apiServerZoneAxfrRetrieve);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeys);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", apiZoneCryptokeys);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", apiServerZoneExport);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", apiZoneMetadataKind);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", apiZoneMetadata);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", apiServerZoneNotify);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/rectify", apiServerZoneRectify);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetail);
- d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones);
- d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail);
- d_ws->registerApiHandler("/api/v1/servers", apiServer);
- d_ws->registerApiHandler("/api/v1", apiDiscoveryV1);
- d_ws->registerApiHandler("/api/docs", apiDocs);
- d_ws->registerApiHandler("/api", apiDiscovery);
+ if (::arg().mustDo("api")) {
+ d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries/<ip>/<nameserver>", &apiServerAutoprimaryDetailDELETE, "DELETE");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimariesGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimariesPOST, "POST");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetailGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetailPUT, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetailDELETE, "DELETE");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeysGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeysPOST, "POST");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", apiServerZoneAxfrRetrieve, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeysGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeysPOST, "POST");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeysPUT, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeysDELETE, "DELETE");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", apiZoneCryptokeysGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", apiZoneCryptokeysPOST, "POST");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", apiServerZoneExport, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", apiZoneMetadataKindGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", apiZoneMetadataKindPUT, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", apiZoneMetadataKindDELETE, "DELETE");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", apiZoneMetadataGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", apiZoneMetadataPOST, "POST");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", apiServerZoneNotify, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/rectify", apiServerZoneRectify, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailPATCH, "PATCH");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailPUT, "PUT");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailDELETE, "DELETE");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesGET, "GET");
+ d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesPOST, "POST");
+ d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, "GET");
+ d_ws->registerApiHandler("/api/v1/servers", apiServer, "GET");
+ d_ws->registerApiHandler("/api/v1", apiDiscoveryV1, "GET");
+ d_ws->registerApiHandler("/api/docs", apiDocs, "GET");
+ d_ws->registerApiHandler("/api", apiDiscovery, "GET");
}
if (::arg().mustDo("webserver")) {
- d_ws->registerWebHandler("/style.css", [this](HttpRequest *req, HttpResponse *resp){cssfunction(req, resp);});
- d_ws->registerWebHandler("/", [this](HttpRequest *req, HttpResponse *resp){indexfunction(req, resp);});
- d_ws->registerWebHandler("/metrics", prometheusMetrics);
+ d_ws->registerWebHandler(
+ "/style.css", [](HttpRequest* req, HttpResponse* resp) { cssfunction(req, resp); }, "GET");
+ d_ws->registerWebHandler(
+ "/", [this](HttpRequest* req, HttpResponse* resp) { indexfunction(req, resp); }, "GET");
+ d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET");
}
d_ws->go();
}
- catch(...) {
- g_log<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
+ catch (...) {
+ g_log << Logger::Error << "AuthWebServer thread caught an exception, dying" << endl;
_exit(1);
}
}
#pragma once
#include <string>
#include <map>
-#include <time.h>
+#include <ctime>
#include <pthread.h>
#include "misc.hh"
#include "namespaces.hh"
#include "webserver.hh"
+#include "statbag.hh"
class Ewma
{
public:
- Ewma() : d_last(0), d_10(0), d_5(0), d_1(0), d_max(0){dt.set();}
- void submit(int val)
- {
- int rate=val-d_last;
- double difft=dt.udiff()/1000000.0;
- dt.set();
-
- d_10=((600.0-difft)*d_10+(difft*rate))/600.0;
- d_5=((300.0-difft)*d_5+(difft*rate))/300.0;
- d_1=((60.0-difft)*d_1+(difft*rate))/60.0;
- d_max=max(d_1,d_max);
-
- d_last=val;
- }
- double get10()
- {
- return d_10;
- }
- double get5()
- {
- return d_5;
- }
- double get1()
- {
- return d_1;
- }
- double getMax()
- {
- return d_max;
- }
+ Ewma();
+
+ void submit(int val);
+ [[nodiscard]] double get10() const;
+ [[nodiscard]] double get5() const;
+ [[nodiscard]] double get1() const;
+ [[nodiscard]] double getMax() const;
+
private:
DTime dt;
- int d_last;
- double d_10, d_5, d_1, d_max;
+ int d_last{};
+ double d_10{}, d_5{}, d_1{}, d_max{};
};
class AuthWebServer
{
public:
AuthWebServer();
- void go();
+ void go(StatBag& stats);
static string makePercentage(const double& val);
private:
void indexfunction(HttpRequest* req, HttpResponse* resp);
- void cssfunction(HttpRequest* req, HttpResponse* resp);
void jsonstat(HttpRequest* req, HttpResponse* resp);
void registerApiHandler(const string& url, std::function<void(HttpRequest*, HttpResponse*)> handler);
- void printvars(ostringstream &ret);
- void printargs(ostringstream &ret);
void webThread();
- void statThread();
+ void statThread(StatBag& stats);
time_t d_start;
double d_min10, d_min5, d_min1;
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#ifdef HAVE_XSK
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <fcntl.h>
+#include <iterator>
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/if_link.h>
+#include <linux/if_xdp.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdexcept>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/timerfd.h>
+#include <unistd.h>
+#include <vector>
+
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+extern "C"
+{
+#include <xdp/libxdp.h>
+}
+
+#include "gettime.hh"
+#include "xsk.hh"
+
+#ifdef DEBUG_UMEM
+namespace
+{
+struct UmemEntryStatus
+{
+ enum class Status : uint8_t
+ {
+ Free,
+ FillQueue,
+ Received,
+ TXQueue
+ };
+ Status status{Status::Free};
+};
+
+LockGuarded<std::unordered_map<uint64_t, UmemEntryStatus>> s_umems;
+
+void checkUmemIntegrity(const char* function, int line, uint64_t offset, const std::set<UmemEntryStatus::Status>& validStatuses, UmemEntryStatus::Status newStatus)
+{
+ auto umems = s_umems.lock();
+ if (validStatuses.count(umems->at(offset).status) == 0) {
+ std::cerr << "UMEM integrity check failed at " << function << ": " << line << ": status is " << static_cast<int>(umems->at(offset).status) << ", expected: ";
+ for (const auto status : validStatuses) {
+ std::cerr << static_cast<int>(status) << " ";
+ }
+ std::cerr << std::endl;
+ abort();
+ }
+ (*umems)[offset].status = newStatus;
+}
+}
+#endif /* DEBUG_UMEM */
+
+constexpr bool XskSocket::isPowOfTwo(uint32_t value) noexcept
+{
+ return value != 0 && (value & (value - 1)) == 0;
+}
+
+int XskSocket::firstTimeout()
+{
+ if (waitForDelay.empty()) {
+ return -1;
+ }
+ timespec now{};
+ gettime(&now);
+ const auto& firstTime = waitForDelay.top().getSendTime();
+ const auto res = timeDifference(now, firstTime);
+ if (res <= 0) {
+ return 0;
+ }
+ return res;
+}
+
+XskSocket::XskSocket(size_t frameNum_, std::string ifName_, uint32_t queue_id, const std::string& xskMapPath) :
+ frameNum(frameNum_), ifName(std::move(ifName_)), socket(nullptr, xsk_socket__delete), sharedEmptyFrameOffset(std::make_shared<LockGuarded<vector<uint64_t>>>())
+{
+ if (!isPowOfTwo(frameNum_) || !isPowOfTwo(frameSize)
+ || !isPowOfTwo(fqCapacity) || !isPowOfTwo(cqCapacity) || !isPowOfTwo(rxCapacity) || !isPowOfTwo(txCapacity)) {
+ throw std::runtime_error("The number of frame , the size of frame and the capacity of rings must is a pow of 2");
+ }
+ getMACFromIfName();
+
+ memset(&cq, 0, sizeof(cq));
+ memset(&fq, 0, sizeof(fq));
+ memset(&tx, 0, sizeof(tx));
+ memset(&rx, 0, sizeof(rx));
+
+ xsk_umem_config umemCfg{};
+ umemCfg.fill_size = fqCapacity;
+ umemCfg.comp_size = cqCapacity;
+ umemCfg.frame_size = frameSize;
+ umemCfg.frame_headroom = XSK_UMEM__DEFAULT_FRAME_HEADROOM;
+ umemCfg.flags = 0;
+ umem.umemInit(frameNum_ * frameSize, &cq, &fq, &umemCfg);
+
+ {
+ xsk_socket_config socketCfg{};
+ socketCfg.rx_size = rxCapacity;
+ socketCfg.tx_size = txCapacity;
+ socketCfg.bind_flags = XDP_USE_NEED_WAKEUP;
+ socketCfg.xdp_flags = XDP_FLAGS_SKB_MODE;
+ socketCfg.libxdp_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD;
+ xsk_socket* tmp = nullptr;
+ auto ret = xsk_socket__create(&tmp, ifName.c_str(), queue_id, umem.umem, &rx, &tx, &socketCfg);
+ if (ret != 0) {
+ throw std::runtime_error("Error creating a xsk socket of if_name " + ifName + ": " + stringerror(ret));
+ }
+ socket = std::unique_ptr<xsk_socket, decltype(&xsk_socket__delete)>(tmp, xsk_socket__delete);
+ }
+
+ uniqueEmptyFrameOffset.reserve(frameNum);
+ {
+ for (uint64_t idx = 0; idx < frameNum; idx++) {
+ uniqueEmptyFrameOffset.push_back(idx * frameSize + XDP_PACKET_HEADROOM);
+#ifdef DEBUG_UMEM
+ {
+ auto umems = s_umems.lock();
+ (*umems)[idx * frameSize + XDP_PACKET_HEADROOM] = UmemEntryStatus();
+ }
+#endif /* DEBUG_UMEM */
+ }
+ }
+
+ fillFq(fqCapacity);
+
+ const auto xskfd = xskFd();
+ fds.push_back(pollfd{
+ .fd = xskfd,
+ .events = POLLIN,
+ .revents = 0});
+
+ const auto xskMapFd = FDWrapper(bpf_obj_get(xskMapPath.c_str()));
+
+ if (xskMapFd.getHandle() < 0) {
+ throw std::runtime_error("Error getting BPF map from path '" + xskMapPath + "'");
+ }
+
+ auto ret = bpf_map_update_elem(xskMapFd.getHandle(), &queue_id, &xskfd, 0);
+ if (ret != 0) {
+ throw std::runtime_error("Error inserting into xsk_map '" + xskMapPath + "': " + std::to_string(ret));
+ }
+}
+
+// see xdp.h in contrib/
+struct IPv4AndPort
+{
+ uint32_t addr;
+ uint16_t port;
+};
+struct IPv6AndPort
+{
+ struct in6_addr addr;
+ uint16_t port;
+};
+
+static FDWrapper getDestinationMap(const std::string& mapPath)
+{
+ auto destMapFd = FDWrapper(bpf_obj_get(mapPath.c_str()));
+ if (destMapFd.getHandle() < 0) {
+ throw std::runtime_error("Error getting the XSK destination addresses map path '" + mapPath + "'");
+ }
+ return destMapFd;
+}
+
+void XskSocket::clearDestinationMap(const std::string& mapPath, bool isV6)
+{
+ auto destMapFd = getDestinationMap(mapPath);
+ if (!isV6) {
+ IPv4AndPort prevKey{};
+ IPv4AndPort key{};
+ while (bpf_map_get_next_key(destMapFd.getHandle(), &prevKey, &key) == 0) {
+ bpf_map_delete_elem(destMapFd.getHandle(), &key);
+ prevKey = key;
+ }
+ }
+ else {
+ IPv6AndPort prevKey{};
+ IPv6AndPort key{};
+ while (bpf_map_get_next_key(destMapFd.getHandle(), &prevKey, &key) == 0) {
+ bpf_map_delete_elem(destMapFd.getHandle(), &key);
+ prevKey = key;
+ }
+ }
+}
+
+void XskSocket::addDestinationAddress(const std::string& mapPath, const ComboAddress& destination)
+{
+ auto destMapFd = getDestinationMap(mapPath);
+ bool value = true;
+ if (destination.isIPv4()) {
+ IPv4AndPort key{};
+ key.addr = destination.sin4.sin_addr.s_addr;
+ key.port = destination.sin4.sin_port;
+ auto ret = bpf_map_update_elem(destMapFd.getHandle(), &key, &value, 0);
+ if (ret != 0) {
+ throw std::runtime_error("Error inserting into xsk_map '" + mapPath + "': " + std::to_string(ret));
+ }
+ }
+ else {
+ IPv6AndPort key{};
+ key.addr = destination.sin6.sin6_addr;
+ key.port = destination.sin6.sin6_port;
+ auto ret = bpf_map_update_elem(destMapFd.getHandle(), &key, &value, 0);
+ if (ret != 0) {
+ throw std::runtime_error("Error inserting into XSK destination addresses map '" + mapPath + "': " + std::to_string(ret));
+ }
+ }
+}
+
+void XskSocket::removeDestinationAddress(const std::string& mapPath, const ComboAddress& destination)
+{
+ auto destMapFd = getDestinationMap(mapPath);
+ if (destination.isIPv4()) {
+ IPv4AndPort key{};
+ key.addr = destination.sin4.sin_addr.s_addr;
+ key.port = destination.sin4.sin_port;
+ bpf_map_delete_elem(destMapFd.getHandle(), &key);
+ }
+ else {
+ IPv6AndPort key{};
+ key.addr = destination.sin6.sin6_addr;
+ key.port = destination.sin6.sin6_port;
+ bpf_map_delete_elem(destMapFd.getHandle(), &key);
+ }
+}
+
+void XskSocket::fillFq(uint32_t fillSize) noexcept
+{
+ {
+ // if we have less than holdThreshold frames in the shared queue (which might be an issue
+ // when the XskWorker needs empty frames), move frames from the unique container into the
+ // shared one. This might not be optimal right now.
+ auto frames = sharedEmptyFrameOffset->lock();
+ if (frames->size() < holdThreshold) {
+ const auto moveSize = std::min(holdThreshold - frames->size(), uniqueEmptyFrameOffset.size());
+ if (moveSize > 0) {
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+ frames->insert(frames->end(), std::make_move_iterator(uniqueEmptyFrameOffset.end() - moveSize), std::make_move_iterator(uniqueEmptyFrameOffset.end()));
+ uniqueEmptyFrameOffset.resize(uniqueEmptyFrameOffset.size() - moveSize);
+ }
+ }
+ }
+
+ if (uniqueEmptyFrameOffset.size() < fillSize) {
+ return;
+ }
+
+ uint32_t idx{0};
+ auto toFill = xsk_ring_prod__reserve(&fq, fillSize, &idx);
+ if (toFill == 0) {
+ return;
+ }
+ uint32_t processed = 0;
+ for (; processed < toFill; processed++) {
+ *xsk_ring_prod__fill_addr(&fq, idx++) = uniqueEmptyFrameOffset.back();
+#ifdef DEBUG_UMEM
+ checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, uniqueEmptyFrameOffset.back(), {UmemEntryStatus::Status::Free}, UmemEntryStatus::Status::FillQueue);
+#endif /* DEBUG_UMEM */
+ uniqueEmptyFrameOffset.pop_back();
+ }
+
+ xsk_ring_prod__submit(&fq, processed);
+}
+
+int XskSocket::wait(int timeout)
+{
+ auto waitAtMost = std::min(timeout, firstTimeout());
+ return poll(fds.data(), fds.size(), waitAtMost);
+}
+
+[[nodiscard]] uint64_t XskSocket::frameOffset(const XskPacket& packet) const noexcept
+{
+ return packet.getFrameOffsetFrom(umem.bufBase);
+}
+
+[[nodiscard]] int XskSocket::xskFd() const noexcept
+{
+ return xsk_socket__fd(socket.get());
+}
+
+void XskSocket::send(std::vector<XskPacket>& packets)
+{
+ while (!packets.empty()) {
+ auto packetSize = packets.size();
+ if (packetSize > std::numeric_limits<uint32_t>::max()) {
+ packetSize = std::numeric_limits<uint32_t>::max();
+ }
+ size_t toSend = std::min(static_cast<uint32_t>(packetSize), txCapacity);
+ uint32_t idx{0};
+ auto toFill = xsk_ring_prod__reserve(&tx, toSend, &idx);
+ if (toFill == 0) {
+ return;
+ }
+
+ size_t queued = 0;
+ for (const auto& packet : packets) {
+ if (queued == toFill) {
+ break;
+ }
+ *xsk_ring_prod__tx_desc(&tx, idx++) = {
+ .addr = frameOffset(packet),
+ .len = packet.getFrameLen(),
+ .options = 0};
+#ifdef DEBUG_UMEM
+ checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, frameOffset(packet), {UmemEntryStatus::Status::Free, UmemEntryStatus::Status::Received}, UmemEntryStatus::Status::TXQueue);
+#endif /* DEBUG_UMEM */
+ queued++;
+ }
+ xsk_ring_prod__submit(&tx, toFill);
+ packets.erase(packets.begin(), packets.begin() + toFill);
+ }
+}
+
+std::vector<XskPacket> XskSocket::recv(uint32_t recvSizeMax, uint32_t* failedCount)
+{
+ uint32_t idx{0};
+ std::vector<XskPacket> res;
+ // how many descriptors to packets have been filled
+ const auto recvSize = xsk_ring_cons__peek(&rx, recvSizeMax, &idx);
+ if (recvSize == 0) {
+ return res;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ const auto baseAddr = reinterpret_cast<uint64_t>(umem.bufBase);
+ uint32_t failed = 0;
+ uint32_t processed = 0;
+ res.reserve(recvSize);
+ for (; processed < recvSize; processed++) {
+ try {
+ const auto* desc = xsk_ring_cons__rx_desc(&rx, idx++);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr)
+ XskPacket packet = XskPacket(reinterpret_cast<uint8_t*>(desc->addr + baseAddr), desc->len, frameSize);
+#ifdef DEBUG_UMEM
+ checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, frameOffset(packet), {UmemEntryStatus::Status::Free, UmemEntryStatus::Status::FillQueue}, UmemEntryStatus::Status::Received);
+#endif /* DEBUG_UMEM */
+
+ if (!packet.parse(false)) {
+ ++failed;
+ markAsFree(packet);
+ }
+ else {
+ res.push_back(packet);
+ }
+ }
+ catch (const std::exception& exp) {
+ std::cerr << "Exception while processing the XSK RX queue: " << exp.what() << std::endl;
+ break;
+ }
+ catch (...) {
+ std::cerr << "Exception while processing the XSK RX queue" << std::endl;
+ break;
+ }
+ }
+
+ // this releases the descriptor, but not the packet (umem entries)
+ // which will only be made available again when pushed into the fill
+ // queue
+ xsk_ring_cons__release(&rx, processed);
+ if (failedCount != nullptr) {
+ *failedCount = failed;
+ }
+
+ return res;
+}
+
+void XskSocket::pickUpReadyPacket(std::vector<XskPacket>& packets)
+{
+ timespec now{};
+ gettime(&now);
+ while (!waitForDelay.empty() && timeDifference(now, waitForDelay.top().getSendTime()) <= 0) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+ auto& top = const_cast<XskPacket&>(waitForDelay.top());
+ packets.push_back(top);
+ waitForDelay.pop();
+ }
+}
+
+void XskSocket::recycle(size_t size) noexcept
+{
+ uint32_t idx{0};
+ const auto completeSize = xsk_ring_cons__peek(&cq, size, &idx);
+ if (completeSize == 0) {
+ return;
+ }
+ uniqueEmptyFrameOffset.reserve(uniqueEmptyFrameOffset.size() + completeSize);
+ uint32_t processed = 0;
+ for (; processed < completeSize; ++processed) {
+ uniqueEmptyFrameOffset.push_back(*xsk_ring_cons__comp_addr(&cq, idx++));
+#ifdef DEBUG_UMEM
+ checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, uniqueEmptyFrameOffset.back(), {UmemEntryStatus::Status::Received, UmemEntryStatus::Status::TXQueue}, UmemEntryStatus::Status::Free);
+#endif /* DEBUG_UMEM */
+ }
+ xsk_ring_cons__release(&cq, processed);
+}
+
+void XskSocket::XskUmem::umemInit(size_t memSize, xsk_ring_cons* completionQueue, xsk_ring_prod* fillQueue, xsk_umem_config* config)
+{
+ size = memSize;
+ bufBase = static_cast<uint8_t*>(mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+ if (bufBase == MAP_FAILED) {
+ throw std::runtime_error("mmap failed");
+ }
+ auto ret = xsk_umem__create(&umem, bufBase, size, fillQueue, completionQueue, config);
+ if (ret != 0) {
+ munmap(bufBase, size);
+ throw std::runtime_error("Error creating a umem of size " + std::to_string(size) + ": " + stringerror(ret));
+ }
+}
+
+std::string XskSocket::getMetrics() const
+{
+ xdp_statistics stats{};
+ socklen_t optlen = sizeof(stats);
+ int err = getsockopt(xskFd(), SOL_XDP, XDP_STATISTICS, &stats, &optlen);
+ if (err != 0) {
+ return "";
+ }
+ if (optlen != sizeof(struct xdp_statistics)) {
+ return "";
+ }
+
+ ostringstream ret;
+ ret << "RX dropped: " << std::to_string(stats.rx_dropped) << std::endl;
+ ret << "RX invalid descs: " << std::to_string(stats.rx_invalid_descs) << std::endl;
+ ret << "TX invalid descs: " << std::to_string(stats.tx_invalid_descs) << std::endl;
+ ret << "RX ring full: " << std::to_string(stats.rx_ring_full) << std::endl;
+ ret << "RX fill ring empty descs: " << std::to_string(stats.rx_fill_ring_empty_descs) << std::endl;
+ ret << "TX ring empty descs: " << std::to_string(stats.tx_ring_empty_descs) << std::endl;
+ return ret.str();
+}
+
+[[nodiscard]] std::string XskSocket::getXDPMode() const
+{
+#ifdef HAVE_BPF_XDP_QUERY
+ unsigned int itfIdx = if_nametoindex(ifName.c_str());
+ if (itfIdx == 0) {
+ return "unable to get interface index";
+ }
+ bpf_xdp_query_opts info{};
+ info.sz = sizeof(info);
+ int ret = bpf_xdp_query(static_cast<int>(itfIdx), 0, &info);
+ if (ret != 0) {
+ return {};
+ }
+ switch (info.attach_mode) {
+ case XDP_ATTACHED_DRV:
+ case XDP_ATTACHED_HW:
+ return "native";
+ case XDP_ATTACHED_SKB:
+ return "emulated";
+ default:
+ return "unknown";
+ }
+#else /* HAVE_BPF_XDP_QUERY */
+ return "undetected";
+#endif /* HAVE_BPF_XDP_QUERY */
+}
+
+void XskSocket::markAsFree(const XskPacket& packet)
+{
+ auto offset = frameOffset(packet);
+#ifdef DEBUG_UMEM
+ checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, offset, {UmemEntryStatus::Status::Received, UmemEntryStatus::Status::TXQueue}, UmemEntryStatus::Status::Free);
+#endif /* DEBUG_UMEM */
+
+ uniqueEmptyFrameOffset.push_back(offset);
+}
+
+XskSocket::XskUmem::~XskUmem()
+{
+ if (umem != nullptr) {
+ xsk_umem__delete(umem);
+ }
+ if (bufBase != nullptr) {
+ munmap(bufBase, size);
+ }
+}
+
+[[nodiscard]] size_t XskPacket::getL4HeaderOffset() const noexcept
+{
+ return sizeof(ethhdr) + (v6 ? (sizeof(ipv6hdr)) : sizeof(iphdr));
+}
+
+[[nodiscard]] size_t XskPacket::getDataOffset() const noexcept
+{
+ return getL4HeaderOffset() + sizeof(udphdr);
+}
+
+[[nodiscard]] size_t XskPacket::getDataSize() const noexcept
+{
+ return frameLength - getDataOffset();
+}
+
+[[nodiscard]] ethhdr XskPacket::getEthernetHeader() const noexcept
+{
+ ethhdr ethHeader{};
+ if (frameLength >= sizeof(ethHeader)) {
+ memcpy(ðHeader, frame, sizeof(ethHeader));
+ }
+ return ethHeader;
+}
+
+void XskPacket::setEthernetHeader(const ethhdr& ethHeader) noexcept
+{
+ if (frameLength < sizeof(ethHeader)) {
+ frameLength = sizeof(ethHeader);
+ }
+ memcpy(frame, ðHeader, sizeof(ethHeader));
+}
+
+[[nodiscard]] iphdr XskPacket::getIPv4Header() const noexcept
+{
+ iphdr ipv4Header{};
+ assert(frameLength >= (sizeof(ethhdr) + sizeof(ipv4Header)));
+ assert(!v6);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(&ipv4Header, frame + sizeof(ethhdr), sizeof(ipv4Header));
+ return ipv4Header;
+}
+
+void XskPacket::setIPv4Header(const iphdr& ipv4Header) noexcept
+{
+ assert(frameLength >= (sizeof(ethhdr) + sizeof(iphdr)));
+ assert(!v6);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(frame + sizeof(ethhdr), &ipv4Header, sizeof(ipv4Header));
+}
+
+[[nodiscard]] ipv6hdr XskPacket::getIPv6Header() const noexcept
+{
+ ipv6hdr ipv6Header{};
+ assert(frameLength >= (sizeof(ethhdr) + sizeof(ipv6Header)));
+ assert(v6);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(&ipv6Header, frame + sizeof(ethhdr), sizeof(ipv6Header));
+ return ipv6Header;
+}
+
+void XskPacket::setIPv6Header(const ipv6hdr& ipv6Header) noexcept
+{
+ assert(frameLength >= (sizeof(ethhdr) + sizeof(ipv6Header)));
+ assert(v6);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(frame + sizeof(ethhdr), &ipv6Header, sizeof(ipv6Header));
+}
+
+[[nodiscard]] udphdr XskPacket::getUDPHeader() const noexcept
+{
+ udphdr udpHeader{};
+ assert(frameLength >= (sizeof(ethhdr) + (v6 ? sizeof(ipv6hdr) : sizeof(iphdr)) + sizeof(udpHeader)));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(&udpHeader, frame + getL4HeaderOffset(), sizeof(udpHeader));
+ return udpHeader;
+}
+
+void XskPacket::setUDPHeader(const udphdr& udpHeader) noexcept
+{
+ assert(frameLength >= (sizeof(ethhdr) + (v6 ? sizeof(ipv6hdr) : sizeof(iphdr)) + sizeof(udpHeader)));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(frame + getL4HeaderOffset(), &udpHeader, sizeof(udpHeader));
+}
+
+bool XskPacket::parse(bool fromSetHeader)
+{
+ if (frameLength <= sizeof(ethhdr)) {
+ return false;
+ }
+
+ auto ethHeader = getEthernetHeader();
+ uint8_t l4Protocol{0};
+ if (ethHeader.h_proto == htons(ETH_P_IP)) {
+ if (frameLength < (sizeof(ethhdr) + sizeof(iphdr) + sizeof(udphdr))) {
+ return false;
+ }
+ v6 = false;
+ auto ipHeader = getIPv4Header();
+ if (ipHeader.ihl != (static_cast<uint8_t>(sizeof(iphdr) / 4))) {
+ // ip options is not supported now!
+ return false;
+ }
+ // check ip.check == ipv4Checksum() is not needed!
+ // We check it in BPF program
+ // we don't, actually.
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ from = makeComboAddressFromRaw(4, reinterpret_cast<const char*>(&ipHeader.saddr), sizeof(ipHeader.saddr));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ to = makeComboAddressFromRaw(4, reinterpret_cast<const char*>(&ipHeader.daddr), sizeof(ipHeader.daddr));
+ l4Protocol = ipHeader.protocol;
+ if (!fromSetHeader && (frameLength - sizeof(ethhdr)) != ntohs(ipHeader.tot_len)) {
+ // too small, or too large (trailing data), go away
+ return false;
+ }
+ }
+ else if (ethHeader.h_proto == htons(ETH_P_IPV6)) {
+ if (frameLength < (sizeof(ethhdr) + sizeof(ipv6hdr) + sizeof(udphdr))) {
+ return false;
+ }
+ v6 = true;
+ auto ipHeader = getIPv6Header();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ from = makeComboAddressFromRaw(6, reinterpret_cast<const char*>(&ipHeader.saddr), sizeof(ipHeader.saddr));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ to = makeComboAddressFromRaw(6, reinterpret_cast<const char*>(&ipHeader.daddr), sizeof(ipHeader.daddr));
+ l4Protocol = ipHeader.nexthdr;
+ if (!fromSetHeader && (frameLength - (sizeof(ethhdr) + sizeof(ipv6hdr))) != ntohs(ipHeader.payload_len)) {
+ return false;
+ }
+ }
+ else {
+ return false;
+ }
+
+ if (l4Protocol != IPPROTO_UDP) {
+ return false;
+ }
+
+ // check udp.check == ipv4Checksum() is not needed!
+ // We check it in BPF program
+ // we don't, actually.
+ auto udpHeader = getUDPHeader();
+ if (!fromSetHeader) {
+ // Because of XskPacket::setHeader
+ if (getDataOffset() > frameLength) {
+ return false;
+ }
+
+ if (getDataSize() + sizeof(udphdr) != ntohs(udpHeader.len)) {
+ return false;
+ }
+ }
+
+ from.setPort(ntohs(udpHeader.source));
+ to.setPort(ntohs(udpHeader.dest));
+ return true;
+}
+
+uint32_t XskPacket::getDataLen() const noexcept
+{
+ return getDataSize();
+}
+
+uint32_t XskPacket::getFrameLen() const noexcept
+{
+ return frameLength;
+}
+
+size_t XskPacket::getCapacity() const noexcept
+{
+ return frameSize;
+}
+
+void XskPacket::changeDirectAndUpdateChecksum() noexcept
+{
+ auto ethHeader = getEthernetHeader();
+ {
+ std::array<uint8_t, ETH_ALEN> tmp{};
+ static_assert(tmp.size() == sizeof(ethHeader.h_dest), "Size Error");
+ static_assert(tmp.size() == sizeof(ethHeader.h_source), "Size Error");
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ memcpy(tmp.data(), ethHeader.h_dest, tmp.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ memcpy(ethHeader.h_dest, ethHeader.h_source, tmp.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ memcpy(ethHeader.h_source, tmp.data(), tmp.size());
+ }
+ if (ethHeader.h_proto == htons(ETH_P_IPV6)) {
+ // IPV6
+ auto ipv6 = getIPv6Header();
+ std::swap(ipv6.daddr, ipv6.saddr);
+ assert(ipv6.nexthdr == IPPROTO_UDP);
+
+ auto udp = getUDPHeader();
+ std::swap(udp.dest, udp.source);
+ udp.len = htons(getDataSize() + sizeof(udp));
+ udp.check = 0;
+ /* needed to get the correct checksum */
+ setIPv6Header(ipv6);
+ setUDPHeader(udp);
+ udp.check = tcp_udp_v6_checksum(&ipv6);
+ rewriteIpv6Header(&ipv6, getFrameLen());
+ setIPv6Header(ipv6);
+ setUDPHeader(udp);
+ }
+ else {
+ // IPV4
+ auto ipv4 = getIPv4Header();
+ std::swap(ipv4.daddr, ipv4.saddr);
+ assert(ipv4.protocol == IPPROTO_UDP);
+
+ auto udp = getUDPHeader();
+ std::swap(udp.dest, udp.source);
+ udp.len = htons(getDataSize() + sizeof(udp));
+ udp.check = 0;
+ /* needed to get the correct checksum */
+ setIPv4Header(ipv4);
+ setUDPHeader(udp);
+ udp.check = tcp_udp_v4_checksum(&ipv4);
+ rewriteIpv4Header(&ipv4, getFrameLen());
+ setIPv4Header(ipv4);
+ setUDPHeader(udp);
+ }
+ setEthernetHeader(ethHeader);
+}
+
+void XskPacket::rewriteIpv4Header(struct iphdr* ipv4header, size_t frameLen) noexcept
+{
+ ipv4header->version = 4;
+ ipv4header->ihl = sizeof(iphdr) / 4;
+ ipv4header->tos = 0;
+ ipv4header->tot_len = htons(frameLen - sizeof(ethhdr));
+ ipv4header->id = 0;
+ ipv4header->frag_off = 0;
+ ipv4header->ttl = DefaultTTL;
+ ipv4header->check = 0;
+ ipv4header->check = ipv4Checksum(ipv4header);
+}
+
+void XskPacket::rewriteIpv6Header(struct ipv6hdr* ipv6header, size_t frameLen) noexcept
+{
+ ipv6header->version = 6;
+ ipv6header->priority = 0;
+ ipv6header->payload_len = htons(frameLen - sizeof(ethhdr) - sizeof(ipv6hdr));
+ ipv6header->hop_limit = DefaultTTL;
+ memset(&ipv6header->flow_lbl, 0, sizeof(ipv6header->flow_lbl));
+}
+
+bool XskPacket::isIPV6() const noexcept
+{
+ return v6;
+}
+
+XskPacket::XskPacket(uint8_t* frame_, size_t dataSize, size_t frameSize_) :
+ frame(frame_), frameLength(dataSize), frameSize(frameSize_ - XDP_PACKET_HEADROOM)
+{
+}
+
+PacketBuffer XskPacket::clonePacketBuffer() const
+{
+ const auto size = getDataSize();
+ PacketBuffer tmp(size);
+ if (size > 0) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(tmp.data(), frame + getDataOffset(), size);
+ }
+ return tmp;
+}
+
+bool XskPacket::setPayload(const PacketBuffer& buf)
+{
+ const auto bufSize = buf.size();
+ const auto currentCapacity = getCapacity();
+ if (bufSize == 0 || bufSize > currentCapacity) {
+ return false;
+ }
+ flags |= UPDATE;
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(frame + getDataOffset(), buf.data(), bufSize);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ frameLength = getDataOffset() + bufSize;
+ return true;
+}
+
+void XskPacket::addDelay(const int relativeMilliseconds) noexcept
+{
+ gettime(&sendTime);
+ sendTime.tv_nsec += static_cast<int64_t>(relativeMilliseconds) * 1000000L;
+ sendTime.tv_sec += sendTime.tv_nsec / 1000000000L;
+ sendTime.tv_nsec %= 1000000000L;
+}
+
+bool operator<(const XskPacket& lhs, const XskPacket& rhs) noexcept
+{
+ return lhs.getSendTime() < rhs.getSendTime();
+}
+
+const ComboAddress& XskPacket::getFromAddr() const noexcept
+{
+ return from;
+}
+
+const ComboAddress& XskPacket::getToAddr() const noexcept
+{
+ return to;
+}
+
+void XskWorker::notify(int desc)
+{
+ uint64_t value = 1;
+ ssize_t res = 0;
+ while ((res = write(desc, &value, sizeof(value))) == EINTR) {
+ }
+ if (res != sizeof(value)) {
+ throw runtime_error("Unable Wake Up XskSocket Failed");
+ }
+}
+
+XskWorker::XskWorker() :
+ workerWaker(createEventfd()), xskSocketWaker(createEventfd())
+{
+}
+
+void XskWorker::pushToProcessingQueue(XskPacket& packet)
+{
+#if defined(__SANITIZE_THREAD__)
+ if (!incomingPacketsQueue.lock()->push(packet)) {
+#else
+ if (!incomingPacketsQueue.push(packet)) {
+#endif
+ markAsFree(packet);
+ }
+}
+
+void XskWorker::pushToSendQueue(XskPacket& packet)
+{
+#if defined(__SANITIZE_THREAD__)
+ if (!outgoingPacketsQueue.lock()->push(packet)) {
+#else
+ if (!outgoingPacketsQueue.push(packet)) {
+#endif
+ markAsFree(packet);
+ }
+}
+
+const void* XskPacket::getPayloadData() const
+{
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ return frame + getDataOffset();
+}
+
+void XskPacket::setAddr(const ComboAddress& from_, MACAddr fromMAC, const ComboAddress& to_, MACAddr toMAC) noexcept
+{
+ auto ethHeader = getEthernetHeader();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ memcpy(ethHeader.h_dest, toMAC.data(), toMAC.size());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ memcpy(ethHeader.h_source, fromMAC.data(), fromMAC.size());
+ setEthernetHeader(ethHeader);
+ to = to_;
+ from = from_;
+ v6 = !to.isIPv4();
+ flags = 0;
+}
+
+void XskPacket::rewrite() noexcept
+{
+ flags |= REWRITE;
+ auto ethHeader = getEthernetHeader();
+ if (!v6) {
+ ethHeader.h_proto = htons(ETH_P_IP);
+
+ auto ipHeader = getIPv4Header();
+ ipHeader.daddr = to.sin4.sin_addr.s_addr;
+ ipHeader.saddr = from.sin4.sin_addr.s_addr;
+
+ auto udpHeader = getUDPHeader();
+ ipHeader.protocol = IPPROTO_UDP;
+ udpHeader.source = from.sin4.sin_port;
+ udpHeader.dest = to.sin4.sin_port;
+ udpHeader.len = htons(getDataSize() + sizeof(udpHeader));
+ udpHeader.check = 0;
+ /* needed to get the correct checksum */
+ setIPv4Header(ipHeader);
+ setUDPHeader(udpHeader);
+ udpHeader.check = tcp_udp_v4_checksum(&ipHeader);
+ rewriteIpv4Header(&ipHeader, getFrameLen());
+ setIPv4Header(ipHeader);
+ setUDPHeader(udpHeader);
+ }
+ else {
+ ethHeader.h_proto = htons(ETH_P_IPV6);
+
+ auto ipHeader = getIPv6Header();
+ memcpy(&ipHeader.daddr, &to.sin6.sin6_addr, sizeof(ipHeader.daddr));
+ memcpy(&ipHeader.saddr, &from.sin6.sin6_addr, sizeof(ipHeader.saddr));
+
+ auto udpHeader = getUDPHeader();
+ ipHeader.nexthdr = IPPROTO_UDP;
+ udpHeader.source = from.sin6.sin6_port;
+ udpHeader.dest = to.sin6.sin6_port;
+ udpHeader.len = htons(getDataSize() + sizeof(udpHeader));
+ udpHeader.check = 0;
+ /* needed to get the correct checksum */
+ setIPv6Header(ipHeader);
+ setUDPHeader(udpHeader);
+ udpHeader.check = tcp_udp_v6_checksum(&ipHeader);
+ setIPv6Header(ipHeader);
+ setUDPHeader(udpHeader);
+ }
+
+ setEthernetHeader(ethHeader);
+}
+
+[[nodiscard]] __be16 XskPacket::ipv4Checksum(const struct iphdr* ipHeader) noexcept
+{
+ auto partial = ip_checksum_partial(ipHeader, sizeof(iphdr), 0);
+ return ip_checksum_fold(partial);
+}
+
+[[nodiscard]] __be16 XskPacket::tcp_udp_v4_checksum(const struct iphdr* ipHeader) const noexcept
+{
+ // ip options is not supported !!!
+ const auto l4Length = static_cast<uint16_t>(getDataSize() + sizeof(udphdr));
+ auto sum = tcp_udp_v4_header_checksum_partial(ipHeader->saddr, ipHeader->daddr, ipHeader->protocol, l4Length);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ sum = ip_checksum_partial(frame + getL4HeaderOffset(), l4Length, sum);
+ return ip_checksum_fold(sum);
+}
+
+[[nodiscard]] __be16 XskPacket::tcp_udp_v6_checksum(const struct ipv6hdr* ipv6) const noexcept
+{
+ const auto l4Length = static_cast<uint16_t>(getDataSize() + sizeof(udphdr));
+ uint64_t sum = tcp_udp_v6_header_checksum_partial(&ipv6->saddr, &ipv6->daddr, ipv6->nexthdr, l4Length);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ sum = ip_checksum_partial(frame + getL4HeaderOffset(), l4Length, sum);
+ return ip_checksum_fold(sum);
+}
+
+[[nodiscard]] uint64_t XskPacket::ip_checksum_partial(const void* ptr, const size_t len, uint64_t sum) noexcept
+{
+ size_t position{0};
+ /* Main loop: 32 bits at a time */
+ for (position = 0; position < len; position += sizeof(uint32_t)) {
+ uint32_t value{};
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(&value, static_cast<const uint8_t*>(ptr) + position, sizeof(value));
+ sum += value;
+ }
+
+ /* Handle un-32bit-aligned trailing bytes */
+ if ((len - position) >= 2) {
+ uint16_t value{};
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ memcpy(&value, static_cast<const uint8_t*>(ptr) + position, sizeof(value));
+ sum += value;
+ position += sizeof(value);
+ }
+
+ if ((len - position) > 0) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ const auto* ptr8 = static_cast<const uint8_t*>(ptr) + position;
+ sum += ntohs(*ptr8 << 8); /* RFC says pad last byte */
+ }
+
+ return sum;
+}
+
+[[nodiscard]] __be16 XskPacket::ip_checksum_fold(uint64_t sum) noexcept
+{
+ while ((sum & ~0xffffffffULL) != 0U) {
+ sum = (sum >> 32) + (sum & 0xffffffffULL);
+ }
+ while ((sum & 0xffff0000ULL) != 0U) {
+ sum = (sum >> 16) + (sum & 0xffffULL);
+ }
+
+ return static_cast<__be16>(~sum);
+}
+
+#ifndef __packed
+#define packed_attribute __attribute__((packed))
+#else
+#define packed_attribute __packed
+#endif
+
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+[[nodiscard]] uint64_t XskPacket::tcp_udp_v4_header_checksum_partial(__be32 src_ip, __be32 dst_ip, uint8_t protocol, uint16_t len) noexcept
+{
+ struct header
+ {
+ __be32 src_ip;
+ __be32 dst_ip;
+ __uint8_t mbz;
+ __uint8_t protocol;
+ __be16 length;
+ };
+ /* The IPv4 pseudo-header is defined in RFC 793, Section 3.1. */
+ struct ipv4_pseudo_header_t
+ {
+ /* We use a union here to avoid aliasing issues with gcc -O2 */
+ union
+ {
+ header packed_attribute fields;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
+ uint32_t words[3];
+ };
+ };
+ ipv4_pseudo_header_t pseudo_header{};
+ static_assert(sizeof(pseudo_header) == 12, "IPv4 pseudo-header size is incorrect");
+
+ /* Fill in the pseudo-header. */
+ pseudo_header.fields.src_ip = src_ip;
+ pseudo_header.fields.dst_ip = dst_ip;
+ pseudo_header.fields.mbz = 0;
+ pseudo_header.fields.protocol = protocol;
+ pseudo_header.fields.length = htons(len);
+ return ip_checksum_partial(&pseudo_header, sizeof(pseudo_header), 0);
+}
+
+[[nodiscard]] uint64_t XskPacket::tcp_udp_v6_header_checksum_partial(const struct in6_addr* src_ip, const struct in6_addr* dst_ip, uint8_t protocol, uint32_t len) noexcept
+{
+ struct header
+ {
+ struct in6_addr src_ip;
+ struct in6_addr dst_ip;
+ __be32 length;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
+ __uint8_t mbz[3];
+ __uint8_t next_header;
+ };
+ /* The IPv6 pseudo-header is defined in RFC 2460, Section 8.1. */
+ struct ipv6_pseudo_header_t
+ {
+ /* We use a union here to avoid aliasing issues with gcc -O2 */
+ union
+ {
+ header packed_attribute fields;
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
+ uint32_t words[10];
+ };
+ };
+ ipv6_pseudo_header_t pseudo_header{};
+ static_assert(sizeof(pseudo_header) == 40, "IPv6 pseudo-header size is incorrect");
+
+ /* Fill in the pseudo-header. */
+ pseudo_header.fields.src_ip = *src_ip;
+ pseudo_header.fields.dst_ip = *dst_ip;
+ pseudo_header.fields.length = htonl(len);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ memset(pseudo_header.fields.mbz, 0, sizeof(pseudo_header.fields.mbz));
+ pseudo_header.fields.next_header = protocol;
+ return ip_checksum_partial(&pseudo_header, sizeof(pseudo_header), 0);
+}
+
+void XskPacket::setHeader(PacketBuffer& buf)
+{
+ memcpy(frame, buf.data(), buf.size());
+ frameLength = buf.size();
+ buf.clear();
+ flags = 0;
+ if (!parse(true)) {
+ throw std::runtime_error("Error setting the XSK frame header");
+ }
+}
+
+PacketBuffer XskPacket::cloneHeaderToPacketBuffer() const
+{
+ const auto size = getFrameLen() - getDataSize();
+ PacketBuffer tmp(size);
+ memcpy(tmp.data(), frame, size);
+ return tmp;
+}
+
+int XskWorker::createEventfd()
+{
+ auto desc = ::eventfd(0, EFD_CLOEXEC);
+ if (desc < 0) {
+ throw runtime_error("Unable create eventfd");
+ }
+ return desc;
+}
+
+void XskWorker::waitForXskSocket() const noexcept
+{
+ uint64_t value = read(workerWaker, &value, sizeof(value));
+}
+
+void XskWorker::notifyXskSocket() const
+{
+ notify(xskSocketWaker);
+}
+
+std::shared_ptr<XskWorker> XskWorker::create()
+{
+ return std::make_shared<XskWorker>();
+}
+
+void XskSocket::addWorker(std::shared_ptr<XskWorker> worker)
+{
+ const auto socketWaker = worker->xskSocketWaker.getHandle();
+ worker->umemBufBase = umem.bufBase;
+ d_workers.insert({socketWaker, std::move(worker)});
+ fds.push_back(pollfd{
+ .fd = socketWaker,
+ .events = POLLIN,
+ .revents = 0});
+};
+
+void XskSocket::addWorkerRoute(const std::shared_ptr<XskWorker>& worker, const ComboAddress& dest)
+{
+ d_workerRoutes.lock()->insert({dest, worker});
+}
+
+void XskSocket::removeWorkerRoute(const ComboAddress& dest)
+{
+ d_workerRoutes.lock()->erase(dest);
+}
+
+uint64_t XskWorker::frameOffset(const XskPacket& packet) const noexcept
+{
+ return packet.getFrameOffsetFrom(umemBufBase);
+}
+
+void XskWorker::notifyWorker() const
+{
+ notify(workerWaker);
+}
+
+void XskSocket::getMACFromIfName()
+{
+ ifreq ifr{};
+ auto desc = FDWrapper(::socket(AF_INET, SOCK_DGRAM, 0));
+ if (desc < 0) {
+ throw std::runtime_error("Error creating a socket to get the MAC address of interface " + ifName);
+ }
+
+ if (ifName.size() >= IFNAMSIZ) {
+ throw std::runtime_error("Unable to get MAC address for interface " + ifName + ": name too long");
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ strncpy(ifr.ifr_name, ifName.c_str(), ifName.length() + 1);
+ if (ioctl(desc.getHandle(), SIOCGIFHWADDR, &ifr) < 0 || ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
+ throw std::runtime_error("Error getting MAC address for interface " + ifName);
+ }
+ static_assert(sizeof(ifr.ifr_hwaddr.sa_data) >= std::tuple_size<decltype(source)>{}, "The size of an ARPHRD_ETHER MAC address is smaller than expected");
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+ memcpy(source.data(), ifr.ifr_hwaddr.sa_data, source.size());
+}
+
+[[nodiscard]] int XskSocket::timeDifference(const timespec& lhs, const timespec& rhs) noexcept
+{
+ const auto res = lhs.tv_sec * 1000 + lhs.tv_nsec / 1000000L - (rhs.tv_sec * 1000 + rhs.tv_nsec / 1000000L);
+ return static_cast<int>(res);
+}
+
+void XskWorker::cleanWorkerNotification() const noexcept
+{
+ uint64_t value = read(xskSocketWaker, &value, sizeof(value));
+}
+
+void XskWorker::cleanSocketNotification() const noexcept
+{
+ uint64_t value = read(workerWaker, &value, sizeof(value));
+}
+
+std::vector<pollfd> getPollFdsForWorker(XskWorker& info)
+{
+ std::vector<pollfd> fds;
+ int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
+ if (timerfd < 0) {
+ throw std::runtime_error("create_timerfd failed");
+ }
+ fds.push_back(pollfd{
+ .fd = info.workerWaker,
+ .events = POLLIN,
+ .revents = 0,
+ });
+ fds.push_back(pollfd{
+ .fd = timerfd,
+ .events = POLLIN,
+ .revents = 0,
+ });
+ return fds;
+}
+
+void XskWorker::fillUniqueEmptyOffset()
+{
+ auto frames = sharedEmptyFrameOffset->lock();
+ const auto moveSize = std::min(static_cast<size_t>(32), frames->size());
+ if (moveSize > 0) {
+ // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+ uniqueEmptyFrameOffset.insert(uniqueEmptyFrameOffset.end(), std::make_move_iterator(frames->end() - moveSize), std::make_move_iterator(frames->end()));
+ frames->resize(frames->size() - moveSize);
+ }
+}
+
+std::optional<XskPacket> XskWorker::getEmptyFrame()
+{
+ if (!uniqueEmptyFrameOffset.empty()) {
+ auto offset = uniqueEmptyFrameOffset.back();
+ uniqueEmptyFrameOffset.pop_back();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ return XskPacket(offset + umemBufBase, 0, frameSize);
+ }
+ fillUniqueEmptyOffset();
+ if (!uniqueEmptyFrameOffset.empty()) {
+ auto offset = uniqueEmptyFrameOffset.back();
+ uniqueEmptyFrameOffset.pop_back();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ return XskPacket(offset + umemBufBase, 0, frameSize);
+ }
+ return std::nullopt;
+}
+
+void XskWorker::markAsFree(const XskPacket& packet)
+{
+ auto offset = frameOffset(packet);
+#ifdef DEBUG_UMEM
+ checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, offset, {UmemEntryStatus::Status::Received, UmemEntryStatus::Status::TXQueue}, UmemEntryStatus::Status::Free);
+#endif /* DEBUG_UMEM */
+ uniqueEmptyFrameOffset.push_back(offset);
+}
+
+uint32_t XskPacket::getFlags() const noexcept
+{
+ return flags;
+}
+
+void XskPacket::updatePacket() noexcept
+{
+ if ((flags & UPDATE) == 0U) {
+ return;
+ }
+ if ((flags & REWRITE) == 0U) {
+ changeDirectAndUpdateChecksum();
+ }
+}
+#endif /* HAVE_XSK */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+#include "config.h"
+
+#ifdef HAVE_XSK
+#include <array>
+#include <bits/types/struct_timespec.h>
+#include <boost/lockfree/spsc_queue.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/member.hpp>
+#include <cstdint>
+#include <memory>
+#include <poll.h>
+#include <queue>
+#include <stdexcept>
+#include <string>
+#include <unistd.h>
+#include <unordered_map>
+#include <vector>
+
+#include <xdp/xsk.h>
+
+#include "iputils.hh"
+#include "lock.hh"
+#include "misc.hh"
+#include "noinitvector.hh"
+
+class XskPacket;
+class XskWorker;
+class XskSocket;
+
+using MACAddr = std::array<uint8_t, 6>;
+
+// We use an XskSocket to manage an AF_XDP Socket corresponding to a NIC queue.
+// The XDP program running in the kernel redirects the data to the XskSocket in userspace.
+// We allocate frames that are placed into the descriptors in the fill queue, allowing the kernel to put incoming packets into the frames and place descriptors into the rx queue.
+// Once we have read the descriptors from the rx queue we release them, but we own the frames.
+// After we are done with the frame, we place them into descriptors of either the fill queue (empty frames) or tx queues (packets to be sent).
+// Once the kernel is done, it places descriptors referencing these frames into the cq where we can recycle them (packets destined to the tx queue or empty frame to the fill queue queue).
+
+// XskSocket routes packets to multiple worker threads registered on XskSocket via XskSocket::addWorker based on the destination port number of the packet.
+// The kernel and the worker thread holding XskWorker will wake up the XskSocket through XskFd and the Eventfd corresponding to each worker thread, respectively.
+
+class XskSocket
+{
+ struct XskUmem
+ {
+ xsk_umem* umem{nullptr};
+ uint8_t* bufBase{nullptr};
+ size_t size{0};
+ void umemInit(size_t memSize, xsk_ring_cons* completionQueue, xsk_ring_prod* fillQueue, xsk_umem_config* config);
+ ~XskUmem();
+ XskUmem() = default;
+ };
+ using WorkerContainer = std::unordered_map<int, std::shared_ptr<XskWorker>>;
+ WorkerContainer d_workers;
+ using WorkerRoutesMap = std::unordered_map<ComboAddress, std::shared_ptr<XskWorker>, ComboAddress::addressPortOnlyHash>;
+ // it might be better to move to a StateHolder for performance
+ LockGuarded<WorkerRoutesMap> d_workerRoutes;
+ // number of frames to keep in sharedEmptyFrameOffset
+ static constexpr size_t holdThreshold = 256;
+ // number of frames to insert into the fill queue
+ static constexpr size_t fillThreshold = 128;
+ static constexpr size_t frameSize = 2048;
+ // number of entries (frames) in the umem
+ const size_t frameNum;
+ // responses that have been delayed
+ std::priority_queue<XskPacket> waitForDelay;
+ MACAddr source{};
+ const std::string ifName;
+ // AF_XDP socket then worker waker sockets
+ vector<pollfd> fds;
+ // list of frames, aka (indexes of) umem entries that can be reused to fill fq,
+ // collected from packets that we could not route (unknown destination),
+ // could not parse, were dropped during processing (!UPDATE), or
+ // simply recycled from cq after being processed by the kernel
+ vector<uint64_t> uniqueEmptyFrameOffset;
+ // completion ring: queue where sent packets are stored by the kernel
+ xsk_ring_cons cq{};
+ // rx ring: queue where the incoming packets are stored, read by XskRouter
+ xsk_ring_cons rx{};
+ // fill ring: queue where umem entries available to be filled (put into rx) are stored
+ xsk_ring_prod fq{};
+ // tx ring: queue where outgoing packets are stored
+ xsk_ring_prod tx{};
+ std::unique_ptr<xsk_socket, void (*)(xsk_socket*)> socket;
+ XskUmem umem;
+
+ static constexpr uint32_t fqCapacity = XSK_RING_PROD__DEFAULT_NUM_DESCS * 4;
+ static constexpr uint32_t cqCapacity = XSK_RING_CONS__DEFAULT_NUM_DESCS * 4;
+ static constexpr uint32_t rxCapacity = XSK_RING_CONS__DEFAULT_NUM_DESCS * 2;
+ static constexpr uint32_t txCapacity = XSK_RING_PROD__DEFAULT_NUM_DESCS * 2;
+
+ constexpr static bool isPowOfTwo(uint32_t value) noexcept;
+ [[nodiscard]] static int timeDifference(const timespec& lhs, const timespec& rhs) noexcept;
+
+ [[nodiscard]] uint64_t frameOffset(const XskPacket& packet) const noexcept;
+ [[nodiscard]] int firstTimeout();
+ void getMACFromIfName();
+
+public:
+ static void clearDestinationMap(const std::string& mapPath, bool isV6);
+ static void addDestinationAddress(const std::string& mapPath, const ComboAddress& destination);
+ static void removeDestinationAddress(const std::string& mapPath, const ComboAddress& destination);
+ static constexpr size_t getFrameSize()
+ {
+ return frameSize;
+ }
+ // list of free umem entries that can be reused
+ std::shared_ptr<LockGuarded<vector<uint64_t>>> sharedEmptyFrameOffset;
+ XskSocket(size_t frameNum, std::string ifName, uint32_t queue_id, const std::string& xskMapPath);
+ [[nodiscard]] int xskFd() const noexcept;
+ // wait until one event has occurred
+ [[nodiscard]] int wait(int timeout);
+ // add as many packets as possible to the rx queue for sending */
+ void send(std::vector<XskPacket>& packets);
+ // look at incoming packets in rx, return them if parsing succeeeded
+ [[nodiscard]] std::vector<XskPacket> recv(uint32_t recvSizeMax, uint32_t* failedCount);
+ void addWorker(std::shared_ptr<XskWorker> worker);
+ void addWorkerRoute(const std::shared_ptr<XskWorker>& worker, const ComboAddress& dest);
+ void removeWorkerRoute(const ComboAddress& dest);
+ [[nodiscard]] std::string getMetrics() const;
+ [[nodiscard]] std::string getXDPMode() const;
+ void markAsFree(const XskPacket& packet);
+ [[nodiscard]] const std::shared_ptr<XskWorker>& getWorkerByDescriptor(int desc) const
+ {
+ return d_workers.at(desc);
+ }
+ [[nodiscard]] std::shared_ptr<XskWorker> getWorkerByDestination(const ComboAddress& destination)
+ {
+ auto routes = d_workerRoutes.lock();
+ auto workerIt = routes->find(destination);
+ if (workerIt == routes->end()) {
+ return nullptr;
+ }
+ return workerIt->second;
+ }
+ [[nodiscard]] const std::vector<pollfd>& getDescriptors() const
+ {
+ return fds;
+ }
+ [[nodiscard]] MACAddr getSourceMACAddress() const
+ {
+ return source;
+ }
+ [[nodiscard]] const std::string& getInterfaceName() const
+ {
+ return ifName;
+ }
+ // pick ups available frames from uniqueEmptyFrameOffset
+ // insert entries from uniqueEmptyFrameOffset into fq
+ void fillFq(uint32_t fillSize = fillThreshold) noexcept;
+ // picks up entries that have been processed (sent) from cq and push them into uniqueEmptyFrameOffset
+ void recycle(size_t size) noexcept;
+ // look at delayed packets, and send the ones that are ready
+ void pickUpReadyPacket(std::vector<XskPacket>& packets);
+ void pushDelayed(XskPacket& packet)
+ {
+ waitForDelay.push(packet);
+ }
+};
+
+struct ethhdr;
+struct iphdr;
+struct ipv6hdr;
+struct udphdr;
+
+class XskPacket
+{
+public:
+ enum Flags : uint32_t
+ {
+ UPDATE = 1 << 0,
+ DELAY = 1 << 1,
+ REWRITE = 1 << 2
+ };
+
+private:
+ ComboAddress from;
+ ComboAddress to;
+ timespec sendTime{};
+ uint8_t* frame{nullptr};
+ size_t frameLength{0};
+ size_t frameSize{0};
+ uint32_t flags{0};
+ bool v6{false};
+
+ // You must set ipHeader.check = 0 before calling this method
+ [[nodiscard]] static __be16 ipv4Checksum(const struct iphdr*) noexcept;
+ [[nodiscard]] static uint64_t ip_checksum_partial(const void* p, size_t len, uint64_t sum) noexcept;
+ [[nodiscard]] static __be16 ip_checksum_fold(uint64_t sum) noexcept;
+ [[nodiscard]] static uint64_t tcp_udp_v4_header_checksum_partial(__be32 src_ip, __be32 dst_ip, uint8_t protocol, uint16_t len) noexcept;
+ [[nodiscard]] static uint64_t tcp_udp_v6_header_checksum_partial(const struct in6_addr* src_ip, const struct in6_addr* dst_ip, uint8_t protocol, uint32_t len) noexcept;
+ static void rewriteIpv4Header(struct iphdr* ipv4header, size_t frameLen) noexcept;
+ static void rewriteIpv6Header(struct ipv6hdr* ipv6header, size_t frameLen) noexcept;
+
+ // You must set l4Header.check = 0 before calling this method
+ // ip options is not supported
+ [[nodiscard]] __be16 tcp_udp_v4_checksum(const struct iphdr*) const noexcept;
+ // You must set l4Header.check = 0 before calling this method
+ [[nodiscard]] __be16 tcp_udp_v6_checksum(const struct ipv6hdr*) const noexcept;
+ /* offset of the L4 (udphdr) header (after ethhdr and iphdr/ipv6hdr) */
+ [[nodiscard]] size_t getL4HeaderOffset() const noexcept;
+ /* offset of the data after the UDP header */
+ [[nodiscard]] size_t getDataOffset() const noexcept;
+ [[nodiscard]] size_t getDataSize() const noexcept;
+ [[nodiscard]] ethhdr getEthernetHeader() const noexcept;
+ void setEthernetHeader(const ethhdr& ethHeader) noexcept;
+ [[nodiscard]] iphdr getIPv4Header() const noexcept;
+ void setIPv4Header(const iphdr& ipv4Header) noexcept;
+ [[nodiscard]] ipv6hdr getIPv6Header() const noexcept;
+ void setIPv6Header(const ipv6hdr& ipv6Header) noexcept;
+ [[nodiscard]] udphdr getUDPHeader() const noexcept;
+ void setUDPHeader(const udphdr& udpHeader) noexcept;
+ void changeDirectAndUpdateChecksum() noexcept;
+
+ constexpr static uint8_t DefaultTTL = 64;
+
+public:
+ [[nodiscard]] const ComboAddress& getFromAddr() const noexcept;
+ [[nodiscard]] const ComboAddress& getToAddr() const noexcept;
+ [[nodiscard]] const void* getPayloadData() const;
+ [[nodiscard]] bool isIPV6() const noexcept;
+ [[nodiscard]] size_t getCapacity() const noexcept;
+ [[nodiscard]] uint32_t getDataLen() const noexcept;
+ [[nodiscard]] uint32_t getFrameLen() const noexcept;
+ [[nodiscard]] PacketBuffer clonePacketBuffer() const;
+ [[nodiscard]] PacketBuffer cloneHeaderToPacketBuffer() const;
+ void setAddr(const ComboAddress& from_, MACAddr fromMAC, const ComboAddress& to_, MACAddr toMAC) noexcept;
+ bool setPayload(const PacketBuffer& buf);
+ void rewrite() noexcept;
+ void setHeader(PacketBuffer& buf);
+ XskPacket(uint8_t* frame, size_t dataSize, size_t frameSize);
+ void addDelay(int relativeMilliseconds) noexcept;
+ void updatePacket() noexcept;
+ // parse IP and UDP payloads
+ bool parse(bool fromSetHeader);
+ [[nodiscard]] uint32_t getFlags() const noexcept;
+ [[nodiscard]] timespec getSendTime() const noexcept
+ {
+ return sendTime;
+ }
+ [[nodiscard]] uint64_t getFrameOffsetFrom(const uint8_t* base) const noexcept
+ {
+ return frame - base;
+ }
+};
+bool operator<(const XskPacket& lhs, const XskPacket& rhs) noexcept;
+
+/* g++ defines __SANITIZE_THREAD__
+ clang++ supports the nice __has_feature(thread_sanitizer),
+ let's merge them */
+#if defined(__has_feature)
+#if __has_feature(thread_sanitizer)
+#define __SANITIZE_THREAD__ 1
+#endif
+#endif
+
+// XskWorker obtains XskPackets of specific ports in the NIC from XskSocket through cq.
+// After finishing processing the packet, XskWorker puts the packet into sq so that XskSocket decides whether to send it through the network card according to XskPacket::flags.
+// XskWorker wakes up XskSocket via xskSocketWaker after putting the packets in sq.
+class XskWorker
+{
+#if defined(__SANITIZE_THREAD__)
+ using XskPacketRing = LockGuarded<boost::lockfree::spsc_queue<XskPacket, boost::lockfree::capacity<XSK_RING_CONS__DEFAULT_NUM_DESCS * 2>>>;
+#else
+ using XskPacketRing = boost::lockfree::spsc_queue<XskPacket, boost::lockfree::capacity<XSK_RING_CONS__DEFAULT_NUM_DESCS * 2>>;
+#endif
+
+public:
+ // queue of packets to be processed by this worker
+ XskPacketRing incomingPacketsQueue;
+ // queue of packets processed by this worker (to be sent, or discarded)
+ XskPacketRing outgoingPacketsQueue;
+
+ uint8_t* umemBufBase{nullptr};
+ // list of frames that are shared with the XskRouter
+ std::shared_ptr<LockGuarded<vector<uint64_t>>> sharedEmptyFrameOffset;
+ // list of frames that we own, used to generate new packets (health-check)
+ vector<uint64_t> uniqueEmptyFrameOffset;
+ const size_t frameSize{XskSocket::getFrameSize()};
+ FDWrapper workerWaker;
+ FDWrapper xskSocketWaker;
+
+ XskWorker();
+ static int createEventfd();
+ static void notify(int desc);
+ static std::shared_ptr<XskWorker> create();
+ void pushToProcessingQueue(XskPacket& packet);
+ void pushToSendQueue(XskPacket& packet);
+ void markAsFree(const XskPacket& packet);
+ // notify worker that at least one packet is available for processing
+ void notifyWorker() const;
+ // notify the router that packets are ready to be sent
+ void notifyXskSocket() const;
+ void waitForXskSocket() const noexcept;
+ void cleanWorkerNotification() const noexcept;
+ void cleanSocketNotification() const noexcept;
+ [[nodiscard]] uint64_t frameOffset(const XskPacket& packet) const noexcept;
+ // reap empty umem entry from sharedEmptyFrameOffset into uniqueEmptyFrameOffset
+ void fillUniqueEmptyOffset();
+ // look for an empty umem entry in uniqueEmptyFrameOffset
+ // then sharedEmptyFrameOffset if needed
+ std::optional<XskPacket> getEmptyFrame();
+};
+std::vector<pollfd> getPollFdsForWorker(XskWorker& info);
+#else
+class XskSocket
+{
+};
+class XskPacket
+{
+};
+class XskWorker
+{
+};
+
+#endif /* HAVE_XSK */
i!=domains.end();
++i)
{
- if(i->type!="master" && i->type!="slave") {
- cerr<<" Warning! Skipping '"<<i->type<<"' zone '"<<i->name<<"'"<<endl;
- continue;
- }
+ if (i->type != "primary" && i->type != "secondary" && !i->type.empty() && i->type != "master" && i->type != "slave") {
+ cerr << " Warning! Skipping '" << i->type << "' zone '" << i->name << "'" << endl;
+ continue;
+ }
lines.clear();
try {
Json::object obj;
for(const auto& i: domains)
{
- if(i.type!="master" && i.type!="slave") {
- cerr<<" Warning! Skipping '"<<i.type<<"' zone '"<<i.name<<"'"<<endl;
- continue;
- }
+ if (i.type != "primary" && i.type != "secondary" && !i.type.empty() && i.type != "master" && i.type != "slave") {
+ cerr << " Warning! Skipping '" << i.type << "' zone '" << i.name << "'" << endl;
+ continue;
+ }
try
{
if( i.name != g_rootdnsname && i.name != DNSName("localhost") && i.name != DNSName("0.0.127.in-addr.arpa") )
cout<<"BEGIN TRANSACTION;"<<endl;
}
-static void emitDomain(const DNSName& domain, const vector<ComboAddress> *masters = nullptr) {
+static void emitDomain(const DNSName& domain, const vector<ComboAddress>* primaries = nullptr)
+{
string iDomain = domain.toStringRootDot();
- if(!::arg().mustDo("slave")) {
+ if (!::arg().mustDo("secondary")) {
cout<<"insert into domains (name,type) values ("<<toLower(sqlstr(iDomain))<<",'NATIVE');"<<endl;
}
else
{
string mstrs;
- if (masters != nullptr && ! masters->empty()) {
- for(const auto& mstr : *masters) {
+ if (primaries != nullptr && !primaries->empty()) {
+ for (const auto& mstr : *primaries) {
mstrs.append(mstr.toStringWithPortExcept(53));
mstrs.append(1, ' ');
}
}
-int main(int argc, char **argv)
+int main(int argc, char **argv) // NOLINT(readability-function-cognitive-complexity) 13379 https://github.com/PowerDNS/pdns/issues/13379 Habbie: zone2sql.cc, bindbackend2.cc: reduce complexity
try
{
reportAllTypes();
::arg().setSwitch("gmysql","Output in format suitable for default gmysqlbackend")="no";
::arg().setSwitch("gsqlite","Output in format suitable for default gsqlitebackend")="no";
::arg().setSwitch("verbose","Verbose comments on operation")="no";
- ::arg().setSwitch("slave","Keep BIND slaves as slaves. Only works with named-conf.")="no";
+ ::arg().setSwitch("secondary", "Keep BIND secondaries as secondaries. Only works with named-conf.") = "no";
::arg().setSwitch("json-comments","Parse json={} field for disabled & comments")="no";
::arg().setSwitch("transactions","If target SQL supports it, use transactions")="no";
::arg().setSwitch("on-error-resume-next","Continue after errors")="no";
for(const auto & domain : domains)
{
- if(domain.type!="master" && domain.type!="slave") {
- cerr<<" Warning! Skipping '"<<domain.type<<"' zone '"<<domain.name<<"'"<<endl;
- continue;
- }
+ if (domain.type != "primary" && domain.type != "secondary" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
+ cerr << " Warning! Skipping '" << domain.type << "' zone '" << domain.name << "'" << endl;
+ continue;
+ }
try {
startNewTransaction();
- emitDomain(domain.name, &(domain.masters));
+ emitDomain(domain.name, &(domain.primaries));
ZoneParserTNG zpt(domain.filename, domain.name, BP.getDirectory());
zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
while (zpt.get(dnsResourceRecord)) {
std::shared_ptr<DNSRecordContent> drc;
try {
- drc = DNSRecordContent::mastermake(dnsResourceRecord.qtype, QClass::IN, dnsResourceRecord.content);
+ drc = DNSRecordContent::make(dnsResourceRecord.qtype, QClass::IN, dnsResourceRecord.content);
}
catch (const PDNSException& pe) {
std::string err = "Bad record content in record for '" + dnsResourceRecord.qname.toStringNoDot() + "'|" + dnsResourceRecord.qtype.toString() + ": " + pe.reason;
void pdns::ZoneMD::readRecords(const vector<DNSRecord>& records)
{
- for (auto& record : records) {
+ for (const auto& record : records) {
readRecord(record);
}
}
-void pdns::ZoneMD::readRecord(const DNSRecord& record)
+void pdns::ZoneMD::processRecord(const DNSRecord& record)
{
- if (!record.d_name.isPartOf(d_zone) && record.d_name != d_zone) {
- return;
- }
- if (record.d_class == QClass::IN && record.d_type == QType::SOA && d_soaRecordContent) {
- return;
- }
-
if (record.d_class == QClass::IN && record.d_name == d_zone) {
switch (record.d_type) {
case QType::SOA: {
if (rrsig == nullptr) {
throw PDNSException("Invalid RRSIG record");
}
- d_rrsigs.emplace_back(rrsig);
+ d_rrsigs[rrsig->d_type].emplace_back(rrsig);
if (rrsig->d_type == QType::NSEC) {
d_nsecs.signatures.emplace_back(rrsig);
}
if (param == nullptr) {
throw PDNSException("Invalid NSEC3PARAM record");
}
- if (g_maxNSEC3Iterations && param->d_iterations > g_maxNSEC3Iterations) {
+ if (g_maxNSEC3Iterations > 0 && param->d_iterations > g_maxNSEC3Iterations) {
return;
}
d_nsec3params.emplace_back(param);
d_nsec3label = d_zone;
d_nsec3label.prependRawLabel(toBase32Hex(hashQNameWithSalt(param->d_salt, param->d_iterations, d_zone)));
// Zap the NSEC3 at labels that we now know are not relevant
- for (auto it = d_nsec3s.begin(); it != d_nsec3s.end();) {
- if (it->first != d_nsec3label) {
- it = d_nsec3s.erase(it);
+ for (auto item = d_nsec3s.begin(); item != d_nsec3s.end();) {
+ if (item->first != d_nsec3label) {
+ item = d_nsec3s.erase(item);
}
else {
- ++it;
+ ++item;
}
}
break;
}
}
}
+}
+
+void pdns::ZoneMD::readRecord(const DNSRecord& record)
+{
+ if (!record.d_name.isPartOf(d_zone) && record.d_name != d_zone) {
+ return;
+ }
+ if (record.d_class == QClass::IN && record.d_type == QType::SOA && d_soaRecordContent) {
+ return;
+ }
+
+ processRecord(record);
+
// Until we have seen the NSEC3PARAM record, we save all of them, as we do not know the label for the zone yet
if (record.d_class == QClass::IN && (d_nsec3label.empty() || record.d_name == d_nsec3label)) {
switch (record.d_type) {
// Get all records and remember RRSets and TTLs
// Determine which digests to compute based on accepted zonemd records present
- unique_ptr<pdns::SHADigest> sha384digest{nullptr}, sha512digest{nullptr};
+ unique_ptr<pdns::SHADigest> sha384digest{nullptr};
+ unique_ptr<pdns::SHADigest> sha512digest{nullptr};
- for (const auto& it : d_zonemdRecords) {
+ for (const auto& item : d_zonemdRecords) {
// The SOA Serial field MUST exactly match the ZONEMD Serial
// field. If the fields do not match, digest verification MUST
// NOT be considered successful with this ZONEMD RR.
// The Hash Algorithm field MUST be checked. If the verifier does
// not support the given hash algorithm, verification MUST NOT be
// considered successful with this ZONEMD RR.
- const auto duplicate = it.second.duplicate;
- const auto& r = it.second.record;
- if (!duplicate && r->d_serial == d_soaRecordContent->d_st.serial && r->d_scheme == 1 && (r->d_hashalgo == 1 || r->d_hashalgo == 2)) {
+ const auto duplicate = item.second.duplicate;
+ const auto& record = item.second.record;
+ if (!duplicate && record->d_serial == d_soaRecordContent->d_st.serial && record->d_scheme == 1 && (record->d_hashalgo == 1 || record->d_hashalgo == 2)) {
// A supported ZONEMD record
- if (r->d_hashalgo == 1) {
+ if (record->d_hashalgo == 1) {
sha384digest = make_unique<pdns::SHADigest>(384);
}
- else if (r->d_hashalgo == 2) {
+ else if (record->d_hashalgo == 2) {
sha512digest = make_unique<pdns::SHADigest>(512);
}
}
}
sortedRecords_t sorted;
- for (auto& rr : rrset.second) {
+ for (auto& resourceRecord : rrset.second) {
if (qtype == QType::RRSIG) {
- const auto rrsig = std::dynamic_pointer_cast<const RRSIGRecordContent>(rr);
+ const auto rrsig = std::dynamic_pointer_cast<const RRSIGRecordContent>(resourceRecord);
if (rrsig->d_type == QType::ZONEMD && qname == d_zone) {
continue;
}
}
- sorted.insert(rr);
+ sorted.insert(resourceRecord);
}
if (sorted.empty()) {
ValidationFailure
};
- ZoneMD(const DNSName& zone) :
- d_zone(zone)
+ ZoneMD(DNSName zone) :
+ d_zone(std::move(zone))
{}
void readRecords(ZoneParserTNG& zpt);
void readRecords(const std::vector<DNSRecord>& records);
void readRecord(const DNSRecord& record);
+ void processRecord(const DNSRecord& record);
void verify(bool& validationDone, bool& validationOK);
// Return the zone's apex DNSKEYs
- const std::set<shared_ptr<const DNSKEYRecordContent>>& getDNSKEYs() const
+ [[nodiscard]] const std::set<shared_ptr<const DNSKEYRecordContent>>& getDNSKEYs() const
{
return d_dnskeys;
}
// Return the zone's apex RRSIGs
- const std::vector<shared_ptr<const RRSIGRecordContent>>& getRRSIGs() const
+ [[nodiscard]] const std::vector<shared_ptr<const RRSIGRecordContent>>& getRRSIGs(QType requestedType)
{
- return d_rrsigs;
+ if (d_rrsigs.count(requestedType) == 0) {
+ d_rrsigs[requestedType] = {};
+ }
+ return d_rrsigs[requestedType];
}
// Return the zone's apex ZONEMDs
- std::vector<shared_ptr<const ZONEMDRecordContent>> getZONEMDs() const
+ [[nodiscard]] std::vector<shared_ptr<const ZONEMDRecordContent>> getZONEMDs() const
{
std::vector<shared_ptr<const ZONEMDRecordContent>> ret;
+ ret.reserve(d_zonemdRecords.size());
for (const auto& zonemd : d_zonemdRecords) {
ret.emplace_back(zonemd.second.record);
}
}
// Return the zone's apex NSECs with signatures
- const ContentSigPair& getNSECs() const
+ [[nodiscard]] const ContentSigPair& getNSECs() const
{
return d_nsecs;
}
// Return the zone's apex NSEC3s with signatures
- const ContentSigPair& getNSEC3s() const
+ [[nodiscard]] const ContentSigPair& getNSEC3s() const
{
- const auto it = d_nsec3s.find(d_nsec3label);
- return it == d_nsec3s.end() ? empty : d_nsec3s.at(d_nsec3label);
+ const auto item = d_nsec3s.find(d_nsec3label);
+ return item == d_nsec3s.end() ? empty : d_nsec3s.at(d_nsec3label);
}
- const DNSName& getNSEC3Label() const
+ [[nodiscard]] const DNSName& getNSEC3Label() const
{
return d_nsec3label;
}
- const std::vector<shared_ptr<const NSEC3PARAMRecordContent>>& getNSEC3Params() const
+ [[nodiscard]] const std::vector<shared_ptr<const NSEC3PARAMRecordContent>>& getNSEC3Params() const
{
return d_nsec3params;
}
struct CanonRRSetKeyCompare
{
- bool operator()(const RRSetKey_t& a, const RRSetKey_t& b) const
+ bool operator()(const RRSetKey_t& lhs, const RRSetKey_t& rhs) const
{
// FIXME surely we can be smarter here
- if (a.first.canonCompare(b.first)) {
+ if (lhs.first.canonCompare(rhs.first)) {
return true;
}
- if (b.first.canonCompare(a.first)) {
+ if (rhs.first.canonCompare(lhs.first)) {
return false;
}
- return a.second < b.second;
+ return lhs.second < rhs.second;
}
};
std::shared_ptr<const SOARecordContent> d_soaRecordContent;
std::set<shared_ptr<const DNSKEYRecordContent>> d_dnskeys;
- std::vector<shared_ptr<const RRSIGRecordContent>> d_rrsigs;
+ std::map<QType, std::vector<shared_ptr<const RRSIGRecordContent>>> d_rrsigs;
std::vector<shared_ptr<const NSEC3PARAMRecordContent>> d_nsec3params;
ContentSigPair d_nsecs;
map<DNSName, ContentSigPair> d_nsec3s;
d_templatecounter += d_templatestep;
}
- d_line = retline;
+ d_line = std::move(retline);
return true;
}
unsigned makeTTLFromZone(const std::string& str);
struct filestate {
- filestate(FILE* fp, string filename) : d_fp(fp), d_filename(filename), d_lineno(0){}
+ filestate(FILE* fp, string filename) : d_fp(fp), d_filename(std::move(filename)), d_lineno(0){}
FILE *d_fp;
string d_filename;
int d_lineno;
/recursor.conf
/rec-conf.d
/bindbackend.conf
+/acl-notify.list
+/acl-notify.list.yml
+/acl.list.yml
+/recursor.yml
launch+=bind
bind-config=bindbackend.conf
loglevel=5
+default-catalog-zone=default-catalog.example.com
"""
BINDBACKEND_CONF_TPL = """
ACL_LIST_TPL = """
# Generated by runtests.py
# local host
-127.0.0.1
-::1
+- 127.0.0.1
+- ::1
"""
ACL_NOTIFY_LIST_TPL = """
# Generated by runtests.py
# local host
-127.0.0.1
-::1
+- 127.0.0.1
+- ::1
"""
REC_EXAMPLE_COM_CONF_TPL = """
# Generated by runtests.py
-auth-zones+=example.com=../regression-tests/zones/example.com.rec
+recursor:
+ auth_zones:
+ - zone: example.com
+ file: ../regression-tests/zones/example.com.rec
"""
REC_CONF_TPL = """
# Generated by runtests.py
-auth-zones=
-forward-zones=
-forward-zones-recurse=
-allow-from-file=acl.list
-allow-notify-from-file=acl-notify.list
-api-config-dir=%(conf_dir)s
-include-dir=%(conf_dir)s
-devonly-regression-test-mode
+incoming:
+ allow_from_file: acl.list.yml
+ allow_notify_from_file: acl-notify.list.yml
+webservice:
+ api_dir: %(api_dir)s
+recursor:
+ include_dir: %(conf_dir)s
+ devonly_regression_test_mode: true
"""
else:
conf_dir = 'rec-conf.d'
ensure_empty_dir(conf_dir)
- with open('acl.list', 'w') as acl_list:
+ api_dir = 'rec-api.d'
+ ensure_empty_dir(api_dir)
+ with open('acl.list.yml', 'w') as acl_list:
acl_list.write(ACL_LIST_TPL)
- with open('acl-notify.list', 'w') as acl_notify_list:
+ with open('acl-notify.list.yml', 'w') as acl_notify_list:
acl_notify_list.write(ACL_NOTIFY_LIST_TPL)
- with open('recursor.conf', 'w') as recursor_conf:
+ with open('recursor.yml', 'w') as recursor_conf:
recursor_conf.write(REC_CONF_TPL % locals())
- with open(conf_dir+'/example.com..conf', 'w') as conf_file:
+ with open(conf_dir+'/example.com.yml', 'w') as conf_file:
conf_file.write(REC_EXAMPLE_COM_CONF_TPL)
servercmd = [pdns_recursor] + common_args
sys.exit(2)
print("Query for example.com/A to create statistic data...")
-run_check_call([sdig, "127.0.0.1", str(DNSPORT), "example.com", "A"])
+if daemon == 'authoritative':
+ run_check_call([sdig, "127.0.0.1", str(DNSPORT), "example.com", "A"])
+else:
+ run_check_call([sdig, "127.0.0.1", str(DNSPORT), "example.com", "A", "recurse"])
print("Running tests...")
returncode = 0
import requests
import socket
import time
-from test_helper import ApiTestCase
+from test_helper import ApiTestCase, is_auth
class TestBasics(ApiTestCase):
self.assertEqual(r.status_code, requests.codes.ok)
self.assertEqual(r.headers['access-control-allow-origin'], "*")
self.assertEqual(r.headers['access-control-allow-headers'], 'Content-Type, X-API-Key')
- self.assertEqual(r.headers['access-control-allow-methods'], 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
+ self.assertEqual(r.headers['access-control-allow-methods'], 'GET, OPTIONS')
+
+ print("response", repr(r.headers))
+
+ r = self.session.options(self.url("/api/v1/servers/localhost/zones/test"))
+ self.assertEqual(r.status_code, requests.codes.ok)
+ self.assertEqual(r.headers['access-control-allow-origin'], "*")
+ self.assertEqual(r.headers['access-control-allow-headers'], 'Content-Type, X-API-Key')
+ if is_auth():
+ self.assertEqual(r.headers['access-control-allow-methods'], 'GET, PATCH, PUT, DELETE, OPTIONS')
+ else:
+ self.assertEqual(r.headers['access-control-allow-methods'], 'GET, PUT, DELETE, OPTIONS')
+
+ print("response", repr(r.headers))
+
+ r = self.session.options(self.url("/api/v1/servers/localhost/invalid"))
+ self.assertEqual(r.status_code, requests.codes.not_found)
print("response", repr(r.headers))
self.assert_success_json(r)
data = r.json()
self.assertIn('count', data)
- self.assertEqual(1, data['count'])
+ self.assertEqual(2, data['count'])
@unittest.skipIf(not is_recursor(), "Not applicable")
def test_flush_subtree(self):
self.assert_success_json(r)
data = r.json()
self.assertIn('count', data)
- self.assertEqual(1, data['count'])
+ self.assertEqual(3, data['count'])
r = self.session.put(self.url("/api/v1/servers/localhost/cache/flush?domain=example.com.&subtree=true"))
self.assert_success_json(r)
data = r.json()
self.assertIn('count', data)
- self.assertEqual(2, data['count'])
+ self.assertEqual(4, data['count'])
def test_flush_root(self):
r = self.session.put(self.url("/api/v1/servers/localhost/cache/flush?domain=."))
import operator
import requests
import unittest
-import socket
from test_helper import ApiTestCase, is_auth, is_recursor, is_auth_lmdb
self.assertIn('daemon', data)
def test_read_statistics(self):
- # Use low-level API as we want to create an invalid request to test log line encoding
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
- sock.connect((self.server_address, self.server_port))
- sock.send(b'GET /binary\x00\x01\xeb HTTP/1.0\r\n')
- sock.close()
r = self.session.get(self.url("/api/v1/servers/localhost/statistics"))
self.assert_success_json(r)
data = r.json()
self.assertIn('uptime', [e['name'] for e in data])
print(data)
if is_auth():
- qtype_stats, respsize_stats, queries_stats, rcode_stats, logmessages = None, None, None, None, None
+ qtype_stats, respsize_stats, queries_stats, rcode_stats = None, None, None, None
for elem in data:
if elem['type'] == 'MapStatisticItem' and elem['name'] == 'response-by-qtype':
qtype_stats = elem['value']
queries_stats = elem['value']
elif elem['type'] == 'MapStatisticItem' and elem['name'] == 'response-by-rcode':
rcode_stats = elem['value']
- elif elem['type'] == 'RingStatisticItem' and elem['name'] == 'logmessages':
- logmessages = elem['value']
self.assertIn('A', [e['name'] for e in qtype_stats])
self.assertIn('80', [e['name'] for e in respsize_stats])
self.assertIn('example.com/A', [e['name'] for e in queries_stats])
self.assertIn('No Error', [e['name'] for e in rcode_stats])
- self.assertTrue(logmessages[0]['name'].startswith('[webserver]'))
else:
qtype_stats, respsize_stats, rcode_stats = None, None, None
for elem in data:
import operator
import time
import unittest
+import requests.exceptions
from copy import deepcopy
from parameterized import parameterized
from pprint import pprint
assert data_got == data_expected, "%r != %r" % (data_got, data_expected)
+def assert_eq_rrsets(rrsets, expected):
+ """Assert rrsets sets are equal, ignoring sort order."""
+ key = lambda rrset: (rrset['name'], rrset['type'])
+ assert sorted(rrsets, key=key) == sorted(expected, key=key)
+
+
+def templated_rrsets(rrsets: list, zonename: str):
+ """
+ Replace $NAME$ in `name` and `content` of given rrsets with `zonename`.
+ Will return a copy. Original rrsets should stay unmodified.
+ """
+ new_rrsets = []
+ for rrset in rrsets:
+ new_rrset = rrset | {"name": rrset["name"].replace('$NAME$', zonename)}
+
+ if "records" in rrset:
+ records = []
+ for record in rrset["records"]:
+ records.append(record | {"content": record["content"].replace('$NAME$', zonename)})
+ new_rrset["records"] = records
+
+ new_rrsets.append(new_rrset)
+
+ return new_rrsets
+
+
class Zones(ApiTestCase):
def _test_list_zones(self, dnssec=True):
print(example_com)
required_fields = ['id', 'url', 'name', 'kind']
if is_auth():
- required_fields = required_fields + ['masters', 'last_check', 'notified_serial', 'serial', 'account']
+ required_fields = required_fields + ['masters', 'last_check', 'notified_serial', 'serial', 'account', 'catalog']
if dnssec:
required_fields = required_fields = ['dnssec', 'edited_serial']
self.assertNotEqual(example_com['serial'], 0)
def test_list_zones_without_dnssec(self):
self._test_list_zones(False)
+
class AuthZonesHelperMixin(object):
- def create_zone(self, name=None, **kwargs):
+ def create_zone(self, name=None, expect_error=None, **kwargs):
if name is None:
name = unique_zone_name()
payload = {
- 'name': name,
- 'kind': 'Native',
- 'nameservers': ['ns1.example.com.', 'ns2.example.com.']
+ "name": name,
+ "kind": "Native",
+ "nameservers": ["ns1.example.com.", "ns2.example.com."]
}
for k, v in kwargs.items():
if v is None:
del payload[k]
else:
payload[k] = v
- print("sending", payload)
+ if "zone" in payload:
+ payload["zone"] = payload["zone"].replace("$NAME$", payload["name"])
+ if "rrsets" in payload:
+ payload["rrsets"] = templated_rrsets(payload["rrsets"], payload["name"])
+
+ print("Create zone", name, "with:", payload)
r = self.session.post(
self.url("/api/v1/servers/localhost/zones"),
data=json.dumps(payload),
- headers={'content-type': 'application/json'})
- self.assert_success_json(r)
- self.assertEqual(r.status_code, 201)
+ headers={"content-type": "application/json"})
+
+ if expect_error:
+ self.assertEqual(r.status_code, 422, r.content)
+ reply = r.json()
+ if expect_error is True:
+ pass
+ else:
+ self.assertIn(expect_error, reply["error"])
+ else:
+ # expect success
+ self.assertEqual(r.status_code, 201, r.content)
+ reply = r.json()
+
+ return name, payload, reply
+
+ def get_zone(self, api_zone_id, expect_error=None, **kwargs):
+ print("GET zone", api_zone_id, "args:", kwargs)
+ r = self.session.get(
+ self.url("/api/v1/servers/localhost/zones/" + api_zone_id),
+ params=kwargs
+ )
+
reply = r.json()
print("reply", reply)
- return name, payload, reply
+ if expect_error:
+ self.assertEqual(r.status_code, 422)
+ if expect_error is True:
+ pass
+ else:
+ self.assertIn(expect_error, r.json()['error'])
+ else:
+ # expect success
+ self.assert_success_json(r)
+ self.assertEqual(r.status_code, 200)
+
+ return reply
+
+ def put_zone(self, api_zone_id, payload, expect_error=None):
+ print("PUT zone", api_zone_id, "with:", payload)
+ r = self.session.put(
+ self.url("/api/v1/servers/localhost/zones/" + api_zone_id),
+ data=json.dumps(payload),
+ headers={'content-type': 'application/json'})
+
+ print("reply status code:", r.status_code)
+ if expect_error:
+ self.assertEqual(r.status_code, 422, r.content)
+ reply = r.json()
+ if expect_error is True:
+ pass
+ else:
+ self.assertIn(expect_error, reply['error'])
+ else:
+ # expect success (no content)
+ self.assertEqual(r.status_code, 204, r.content)
@unittest.skipIf(not is_auth(), "Not applicable")
class AuthZones(ApiTestCase, AuthZonesHelperMixin):
if k in payload:
self.assertEqual(data[k], payload[k])
+ # check that the catalog is reflected in the /zones output (#13633)
+ r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
+ self.assert_success_json(r)
+ domains = r.json()
+ domain = [domain for domain in domains if domain['name'] == name]
+ self.assertEqual(len(domain), 1)
+ domain = domain[0]
+ self.assertEqual(domain["catalog"], "catalog.invalid.")
+
def test_create_zone_with_account(self):
# soa_edit_api wins over serial
- name, payload, data = self.create_zone(account='anaccount', serial=10)
+ name, payload, data = self.create_zone(account='anaccount', serial=10, kind='Master')
print(data)
for k in ('account', ):
self.assertIn(k, data)
if k in payload:
self.assertEqual(data[k], payload[k])
+ # as we did not set a catalog in our request, check that the default catalog was applied
+ self.assertEqual(data['catalog'], "default-catalog.example.com.")
+
def test_create_zone_default_soa_edit_api(self):
name, payload, data = self.create_zone()
print(data)
self.url("/api/v1/servers/localhost/zones/" + data['id']),
data=json.dumps(payload),
headers={'content-type': 'application/json'})
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id']))
- data = r.json()
+ data = self.get_zone(data['id'])
soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
self.assertEqual(soa_serial[-2:], '02')
# check our record has appeared
self.assertEqual(get_rrset(data, rrset['name'], 'A')['records'], rrset['records'])
- @unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
def test_create_zone_with_comments(self):
name = unique_zone_name()
rrsets = [
"type": "soa", # test uppercasing of type, too.
"comments": [{
"account": "test1",
- "content": "blah blah",
+ "content": "blah blah and test a few non-ASCII chars: ö, €",
"modified_at": 11112,
}],
},
}],
},
]
- name, payload, data = self.create_zone(name=name, rrsets=rrsets)
+
+ if is_auth_lmdb():
+ # No comments in LMDB
+ self.create_zone(name=name, rrsets=rrsets, expect_error="Hosting backend does not support editing comments.")
+ return
+
+ name, _, data = self.create_zone(name=name, rrsets=rrsets)
# NS records have been created
self.assertEqual(len(data['rrsets']), len(rrsets) + 1)
# check our comment has appeared
name = unique_zone_name()
name, payload, data = self.create_zone(dnssec=True)
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+ self.get_zone(name)
for k in ('dnssec', ):
self.assertIn(k, data)
name = unique_zone_name()
name, payload, data = self.create_zone(dnssec=True)
- self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
- data=json.dumps({'dnssec': False}))
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+ self.put_zone(name, {'dnssec': False})
- zoneinfo = r.json()
-
- self.assertEqual(r.status_code, 200)
+ zoneinfo = self.get_zone(name)
self.assertEqual(zoneinfo['dnssec'], False)
r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/cryptokeys'))
nsec3param = '1 0 100 aabbccddeeff'
name, payload, data = self.create_zone(dnssec=True, nsec3param=nsec3param)
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+ self.get_zone(name)
for k in ('dnssec', 'nsec3param'):
self.assertIn(k, data)
name, payload, data = self.create_zone(dnssec=True, nsec3param=nsec3param,
nsec3narrow=True)
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+ self.get_zone(name)
for k in ('dnssec', 'nsec3param', 'nsec3narrow'):
self.assertIn(k, data)
"""
name, payload, data = self.create_zone(dnssec=True,
nsec3param='1 0 1 ab')
- self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
- data=json.dumps({'nsec3param': ''}))
- r = self.session.get(
- self.url("/api/v1/servers/localhost/zones/" + name))
- data = r.json()
+ self.put_zone(name, {'nsec3param': ''})
- self.assertEqual(r.status_code, 200)
+ data = self.get_zone(name)
self.assertEqual(data['nsec3param'], '')
def test_create_zone_without_dnssec_unset_nsec3parm(self):
Create a non dnssec zone and set an empty "nsec3param"
"""
name, payload, data = self.create_zone(dnssec=False)
- r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
- data=json.dumps({'nsec3param': ''}))
-
- self.assertEqual(r.status_code, 204)
+ self.put_zone(name, {'nsec3param': ''})
def test_create_zone_without_dnssec_set_nsec3parm(self):
"""
Create a non dnssec zone and set "nsec3param"
"""
name, payload, data = self.create_zone(dnssec=False)
- r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
- data=json.dumps({'nsec3param': '1 0 1 ab'}))
-
- self.assertEqual(r.status_code, 422)
+ self.put_zone(name, {'nsec3param': '1 0 1 ab'}, expect_error=True)
def test_create_zone_dnssec_serial(self):
"""
soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
self.assertEqual(soa_serial[-2:], '01')
- self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
- data=json.dumps({'dnssec': True}))
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+ self.put_zone(name, {'dnssec': True})
- data = r.json()
+ data = self.get_zone(name)
soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
-
- self.assertEqual(r.status_code, 200)
self.assertEqual(soa_serial[-2:], '02')
- self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
- data=json.dumps({'dnssec': False}))
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+ self.put_zone(name, {'dnssec': False})
- data = r.json()
+ data = self.get_zone(name)
soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
-
- self.assertEqual(r.status_code, 200)
self.assertEqual(soa_serial[-2:], '03')
def test_zone_absolute_url(self):
- name, payload, data = self.create_zone()
+ self.create_zone()
r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
rdata = r.json()
print(rdata[0])
def test_delete_zone_metadata(self):
r = self.session.delete(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE"))
- self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.status_code, 204)
r = self.session.get(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE"))
rdata = r.json()
self.assertEqual(r.status_code, 200)
print("zonelist:", zonelist)
self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
# Also test that fetching the zone works.
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id']))
- data = r.json()
+ data = self.get_zone(data['id'])
print("zone (fetched):", data)
for k in ('name', 'masters', 'kind'):
self.assertIn(k, data)
def test_create_consumer_zone(self):
# Test that nameservers can be absent for consumer zones.
- name, payload, data = self.create_zone(kind='Consumer', nameservers=None, masters=['127.0.0.2'])
- for k in ('name', 'masters', 'kind'):
- self.assertIn(k, data)
- self.assertEqual(data[k], payload[k])
+ _, payload, data = self.create_zone(kind='Consumer', nameservers=None, masters=['127.0.0.2'])
print("payload:", payload)
print("data:", data)
# Because consumer zones don't get a SOA, we need to test that they'll show up in the zone list.
self.assertEqual(data['serial'], 0)
self.assertEqual(data['rrsets'], [])
+ def test_create_consumer_zone_no_nameservers(self):
+ """nameservers must be absent for Consumer zones"""
+ self.create_zone(kind="Consumer", nameservers=["127.0.0.1"], expect_error="Nameservers MUST NOT be given for Consumer zones")
+
+ def test_create_consumer_zone_no_rrsets(self):
+ """rrsets must be absent for Consumer zones"""
+ rrsets = [{
+ "name": "$NAME$",
+ "type": "SOA",
+ "ttl": 3600,
+ "records": [{
+ "content": "ns1.example.net. testmaster@example.net. 10 10800 3600 604800 3600",
+ "disabled": False,
+ }],
+ }]
+ self.create_zone(kind="Consumer", nameservers=None, rrsets=rrsets, expect_error="Zone data MUST NOT be given for Consumer zones")
+
def test_find_zone_by_name(self):
name = 'foo/' + unique_zone_name()
name, payload, data = self.create_zone(name=name)
data = r.json()
print("status for axfr-retrieve:", data)
self.assertEqual(data['result'], u'Added retrieval request for \'' + payload['name'] +
- '\' from master 127.0.0.2')
+ '\' from primary 127.0.0.2')
def test_notify_master_zone(self):
name, payload, data = self.create_zone(kind='Master')
name, payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
name = payload['name']
zone_id = (name.replace('/', '=2F'))
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id))
- data = r.json()
+ data = self.get_zone(zone_id)
for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'dnssec'):
self.assertIn(k, data)
if k in payload:
r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
domains = r.json()
example_com = [domain for domain in domains if domain['name'] == u'example.com.'][0]
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + example_com['id']))
- self.assert_success_json(r)
- data = r.json()
+ data = self.get_zone(example_com['id'])
for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
self.assertIn(k, data)
self.assertEqual(data['name'], 'example.com.')
example_com = [domain for domain in domains if domain['name'] == u'example.com.'][0]
# verify single record from name that has a single record
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + example_com['id'] + "?rrset_name=host-18000.example.com."))
- self.assert_success_json(r)
- data = r.json()
+ data = self.get_zone(example_com['id'], rrset_name="host-18000.example.com.")
for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'rrsets'):
self.assertIn(k, data)
self.assertEqual(data['rrsets'],
# verify two RRsets from a name that has two types with one record each
powerdnssec_org = [domain for domain in domains if domain['name'] == u'powerdnssec.org.'][0]
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + powerdnssec_org['id'] + "?rrset_name=localhost.powerdnssec.org."))
- self.assert_success_json(r)
- data = r.json()
+ data = self.get_zone(powerdnssec_org['id'], rrset_name="localhost.powerdnssec.org.")
for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'rrsets'):
self.assertIn(k, data)
self.assertEqual(sorted(data['rrsets'], key=operator.itemgetter('type')),
)
# verify one RRset with one record from a name that has two, then filtered by type
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + powerdnssec_org['id'] + "?rrset_name=localhost.powerdnssec.org.&rrset_type=AAAA"))
- self.assert_success_json(r)
- data = r.json()
+ data = self.get_zone(powerdnssec_org['id'], rrset_name="localhost.powerdnssec.org.", rrset_type="AAAA")
for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'rrsets'):
self.assertIn(k, data)
self.assertEqual(data['rrsets'],
' 0 10800 3600 604800 3600']
self.assertCountEqual(data['zone'].strip().split('\n'), expected_data)
+ def test_import_zone_consumer(self):
+ zonestring = """
+$NAME$ 1D IN SOA ns1.example.org. hostmaster.example.org. (
+ 2002022401 ; serial
+ 3H ; refresh
+ 15 ; retry
+ 1w ; expire
+ 3h ; minimum
+ )
+ """
+ self.create_zone(kind="Consumer", nameservers=[], zone=zonestring, expect_error="Zone data MUST NOT be given for Consumer zones")
+
def test_export_zone_text(self):
name, payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='')
# export it
'soa_edit_api': 'EPOCH',
'soa_edit': 'EPOCH'
}
- r = self.session.put(
- self.url("/api/v1/servers/localhost/zones/" + name),
- data=json.dumps(payload),
- headers={'content-type': 'application/json'})
- self.assert_success(r)
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ self.put_zone(name, payload)
+ data = self.get_zone(name)
for k in payload.keys():
self.assertIn(k, data)
self.assertEqual(data[k], payload[k])
'soa_edit_api': '',
'soa_edit': ''
}
- r = self.session.put(
- self.url("/api/v1/servers/localhost/zones/" + name),
- data=json.dumps(payload),
- headers={'content-type': 'application/json'})
- self.assert_success(r)
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ self.put_zone(name, payload)
+ data = self.get_zone(name)
for k in payload.keys():
self.assertIn(k, data)
self.assertEqual(data[k], payload[k])
headers={'content-type': 'application/json'})
self.assert_success(r)
# verify that (only) the new record is there
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertCountEqual(get_rrset(data, name, 'NS')['records'], rrset['records'])
def test_zone_rr_update_mx(self):
headers={'content-type': 'application/json'})
self.assert_success(r)
# verify that (only) the new record is there
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertEqual(get_rrset(data, name, 'MX')['records'], rrset['records'])
def test_zone_rr_update_invalid_mx(self):
headers={'content-type': 'application/json'})
self.assertEqual(r.status_code, 422)
self.assertIn('non-hostname content', r.json()['error'])
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertIsNone(get_rrset(data, name, 'MX'))
def test_zone_rr_update_opt(self):
headers={'content-type': 'application/json'})
self.assert_success(r)
# verify that all rrsets have been updated
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertEqual(get_rrset(data, name, 'NS')['records'], rrset1['records'])
self.assertEqual(get_rrset(data, name, 'MX')['records'], rrset2['records'])
headers={'content-type': 'application/json'})
self.assert_success(r)
# verify that the records are gone
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertIsNone(get_rrset(data, name, 'NS'))
def test_zone_rr_update_rrset_combine_replace_and_delete(self):
headers={'content-type': 'application/json'})
self.assert_success(r)
# verify that (only) the new record is there
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertEqual(get_rrset(data, 'sub.' + name, 'CNAME')['records'], rrset2['records'])
def test_zone_disable_reenable(self):
headers={'content-type': 'application/json'})
self.assert_success(r)
# check SOA serial has been edited
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
soa_serial1 = get_first_rec(data, name, 'SOA')['content'].split()[2]
self.assertNotEqual(soa_serial1, '1')
# make sure domain is still in zone list (disabled SOA!)
headers={'content-type': 'application/json'})
self.assert_success(r)
# check SOA serial has been edited again
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
soa_serial2 = get_first_rec(data, name, 'SOA')['content'].split()[2]
self.assertNotEqual(soa_serial2, '1')
self.assertNotEqual(soa_serial2, soa_serial1)
headers={'content-type': 'application/json'})
self.assert_success(r)
# verify that the new record is there
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertEqual(get_rrset(data, name, qtype)['records'], rrset['records'])
rrset = {
headers={'content-type': 'application/json'})
self.assertEqual(r.status_code, 422)
self.assertIn('only allowed at apex', r.json()['error'])
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertIsNone(get_rrset(data, 'sub.' + name, qtype))
@parameterized.expand([
headers={'content-type': 'application/json'})
self.assertEqual(r.status_code, 422)
self.assertIn('not allowed at apex', r.json()['error'])
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertIsNone(get_rrset(data, 'sub.' + name, qtype))
rrset = {
headers={'content-type': 'application/json'})
self.assert_success(r)
# verify that the new record is there
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
self.assertEqual(get_rrset(data, 'sub.' + name, qtype)['records'], rrset['records'])
def test_rr_svcb(self):
self.assertEqual(r.status_code, 204)
self.assertNotIn('Content-Type', r.headers)
- @unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
def test_zone_comment_create(self):
name, payload, zone = self.create_zone()
rrset = {
self.url("/api/v1/servers/localhost/zones/" + name),
data=json.dumps(payload),
headers={'content-type': 'application/json'})
- self.assert_success(r)
+ if is_auth_lmdb():
+ self.assert_error_json(r) # No comments in LMDB
+ return
+ else:
+ self.assert_success(r)
# make sure the comments have been set, and that the NS
# records are still present
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
serverset = get_rrset(data, name, 'NS')
print(serverset)
self.assertNotEqual(serverset['records'], [])
# verify that TTL is correct (regression test)
self.assertEqual(serverset['ttl'], 3600)
- @unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
def test_zone_comment_delete(self):
# Test: Delete ONLY comments.
name, payload, zone = self.create_zone()
headers={'content-type': 'application/json'})
self.assert_success(r)
# make sure the NS records are still present
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
serverset = get_rrset(data, name, 'NS')
print(serverset)
self.assertNotEqual(serverset['records'], [])
data=json.dumps(payload),
headers={'content-type': 'application/json'})
self.assertEqual(r.status_code, 422)
- self.assertIn("Value for key 'modified_at' is out of range", r.json()['error'])
+ self.assertIn("Key 'modified_at' is out of range", r.json()['error'])
@unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
def test_zone_comment_stay_intact(self):
headers={'content-type': 'application/json'})
self.assert_success(r)
# make sure the comments still exist
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+ data = self.get_zone(name)
serverset = get_rrset(data, name, 'NS')
print(serverset)
self.assertEqual(serverset['records'], rrset2['records'])
def test_explicit_rectify_slave(self):
# Some users want to move a zone to kind=Slave and then rectify, without a re-transfer.
name, _, data = self.create_zone = self.create_zone(api_rectify=False, dnssec=True, nsec3param='1 0 1 ab')
- r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id']),
- data=json.dumps({'kind': 'Slave'}),
- headers={'content-type': 'application/json'})
- self.assertEqual(r.status_code, 204)
+ self.put_zone(data['id'], {'kind': 'Slave'})
r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/rectify"))
self.assertEqual(r.status_code, 200)
if not is_auth_lmdb():
def test_rrset_false_parameter(self):
name = unique_zone_name()
self.create_zone(name=name, kind='Native')
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=false"))
- self.assert_success_json(r)
- print(r.json())
- self.assertEqual(r.json().get('rrsets'), None)
+ data = self.get_zone(name, rrsets="false")
+ self.assertEqual(data.get('rrsets'), None)
def test_rrset_true_parameter(self):
name = unique_zone_name()
self.create_zone(name=name, kind='Native')
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=true"))
- self.assert_success_json(r)
- print(r.json())
- self.assertEqual(len(r.json().get('rrsets')), 2)
+ data = self.get_zone(name, rrsets="true")
+ self.assertEqual(len(data['rrsets']), 2)
def test_wrong_rrset_parameter(self):
name = unique_zone_name()
self.create_zone(name=name, kind='Native')
- r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=foobar"))
- self.assertEqual(r.status_code, 422)
- self.assertIn("'rrsets' request parameter value 'foobar' is not supported", r.json()['error'])
+ self.get_zone(
+ name, rrsets="foobar",
+ expect_error="'rrsets' request parameter value 'foobar' is not supported"
+ )
def test_put_master_tsig_key_ids_non_existent(self):
name = unique_zone_name()
payload = {
'master_tsig_key_ids': [keyname]
}
- r = self.session.put(self.url('/api/v1/servers/localhost/zones/' + name),
- data=json.dumps(payload),
- headers={'content-type': 'application/json'})
- self.assertEqual(r.status_code, 422)
- self.assertIn('A TSIG key with the name', r.json()['error'])
+ self.put_zone(name, payload, expect_error='A TSIG key with the name')
def test_put_slave_tsig_key_ids_non_existent(self):
name = unique_zone_name()
payload = {
'slave_tsig_key_ids': [keyname]
}
- r = self.session.put(self.url('/api/v1/servers/localhost/zones/' + name),
- data=json.dumps(payload),
- headers={'content-type': 'application/json'})
- self.assertEqual(r.status_code, 422)
- self.assertIn('A TSIG key with the name', r.json()['error'])
+ self.put_zone(name, payload, expect_error='A TSIG key with the name')
+
+ def test_zone_replace_rrsets_basic(self):
+ """Basic test: all automatic modification is off, on replace the new rrsets are ingested as is."""
+ name, _, _ = self.create_zone(dnssec=False, soa_edit='', soa_edit_api='')
+ rrsets = [
+ {'name': name, 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+ {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]},
+ {'name': 'www.' + name, 'type': 'A', 'ttl': 3600, 'records': [{'content': '192.0.2.1'}]},
+ {'name': 'sub.' + name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}]},
+ ]
+ self.put_zone(name, {'rrsets': rrsets})
+
+ data = self.get_zone(name)
+ for rrset in rrsets:
+ rrset.setdefault('comments', [])
+ for record in rrset['records']:
+ record.setdefault('disabled', False)
+ assert_eq_rrsets(data['rrsets'], rrsets)
+
+ def test_zone_replace_rrsets_dnssec(self):
+ """With dnssec: check automatic rectify is done"""
+ name, _, _ = self.create_zone(dnssec=True)
+ rrsets = [
+ {'name': name, 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+ {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]},
+ {'name': 'www.' + name, 'type': 'A', 'ttl': 3600, 'records': [{'content': '192.0.2.1'}]},
+ ]
+ self.put_zone(name, {'rrsets': rrsets})
+
+ if not is_auth_lmdb():
+ # lmdb: skip, no get_db_records implementations
+ dbrecs = get_db_records(name, 'A')
+ assert dbrecs[0]['ordername'] is not None # default = rectify enabled
+
+ def test_zone_replace_rrsets_with_soa_edit(self):
+ """SOA-EDIT was enabled before rrsets will be replaced"""
+ name, _, _ = self.create_zone(soa_edit='INCEPTION-INCREMENT', soa_edit_api='SOA-EDIT-INCREASE')
+ rrsets = [
+ {'name': name, 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+ {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]},
+ {'name': 'www.' + name, 'type': 'A', 'ttl': 3600, 'records': [{'content': '192.0.2.1'}]},
+ {'name': 'sub.' + name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}]},
+ ]
+ self.put_zone(name, {'rrsets': rrsets})
+
+ data = self.get_zone(name)
+ soa = [rrset['records'][0]['content'] for rrset in data['rrsets'] if rrset['type'] == 'SOA'][0]
+ assert int(soa.split()[2]) > 1 # serial is larger than what we sent
+
+ def test_zone_replace_rrsets_no_soa_primary(self):
+ """Replace all RRsets but supply no SOA. Should fail."""
+ name, _, _ = self.create_zone()
+ rrsets = [
+ {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]}
+ ]
+ self.put_zone(name, {'rrsets': rrsets}, expect_error='Must give SOA record for zone when replacing all RR sets')
+
+ @parameterized.expand([
+ (None, []),
+ (None, [
+ {'name': '$NAME$', 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+ ]),
+ ])
+ def test_zone_replace_rrsets_secondary(self, expected_error, rrsets):
+ """
+ Replace all RRsets in a SECONDARY zone.
+
+ If no SOA is given, this should still succeed, also setting zone stale (but cannot assert this here).
+ """
+ name, _, _ = self.create_zone(kind='Secondary', nameservers=None, masters=['127.0.0.2'])
+ self.put_zone(name, {'rrsets': templated_rrsets(rrsets, name)}, expect_error=expected_error)
+
+ @parameterized.expand([
+ (None, []),
+ ("Modifying RRsets in Consumer zones is unsupported", [
+ {'name': '$NAME$', 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+ ]),
+ ])
+ def test_zone_replace_rrsets_consumer(self, expected_error, rrsets):
+ name, _, _ = self.create_zone(kind='Consumer', nameservers=None, masters=['127.0.0.2'])
+ self.put_zone(name, {'rrsets': templated_rrsets(rrsets, name)}, expect_error=expected_error)
+
+ def test_zone_replace_rrsets_negative_ttl(self):
+ name, _, _ = self.create_zone(dnssec=False, soa_edit='', soa_edit_api='')
+ rrsets = [
+ {'name': name, 'type': 'SOA', 'ttl': -1, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+ ]
+ self.put_zone(name, {'rrsets': rrsets}, expect_error="Key 'ttl' is not a positive Integer")
+
+ @parameterized.expand([
+ ("IN MX: non-hostname content", [{'name': '$NAME$', 'type': 'MX', 'ttl': 3600, 'records': [{"content": "10 mail@mx.example.org."}]}]),
+ ("out of zone", [{'name': 'not-in-zone.', 'type': 'NS', 'ttl': 3600, 'records': [{"content": "ns1.example.org."}]}]),
+ ("contains unsupported characters", [{'name': 'test:.$NAME$', 'type': 'NS', 'ttl': 3600, 'records': [{"content": "ns1.example.org."}]}]),
+ ("unknown type", [{'name': '$NAME$', 'type': 'INVALID', 'ttl': 3600, 'records': [{"content": "192.0.2.1"}]}]),
+ ("Conflicts with another RRset", [{'name': '$NAME$', 'type': 'CNAME', 'ttl': 3600, 'records': [{"content": "example.org."}]}]),
+ ])
+ def test_zone_replace_rrsets_invalid(self, expected_error, invalid_rrsets):
+ """Test validation of RRsets before replacing them"""
+ name, _, _ = self.create_zone(dnssec=False, soa_edit='', soa_edit_api='')
+ base_rrsets = [
+ {'name': name, 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+ {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]},
+ ]
+ rrsets = base_rrsets + templated_rrsets(invalid_rrsets, name)
+ self.put_zone(name, {'rrsets': rrsets}, expect_error=expected_error)
@unittest.skipIf(not is_auth(), "Not applicable")
# Also test that fetching the zone works.
print("id:", data['id'])
self.assertEqual(data['id'], '=2E')
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id'])).json()
+ data = self.get_zone(data['id'])
print("zone (fetched):", data)
for k in ('name', 'kind'):
self.assertIn(k, data)
'soa_edit_api': 'EPOCH',
'soa_edit': 'EPOCH'
}
- r = self.session.put(
- self.url("/api/v1/servers/localhost/zones/" + zone_id),
- data=json.dumps(payload),
- headers={'content-type': 'application/json'})
- self.assert_success(r)
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json()
+ self.put_zone(zone_id, payload)
+ data = self.get_zone(zone_id)
for k in payload.keys():
self.assertIn(k, data)
self.assertEqual(data[k], payload[k])
'soa_edit_api': '',
'soa_edit': ''
}
- r = self.session.put(
- self.url("/api/v1/servers/localhost/zones/" + zone_id),
- data=json.dumps(payload),
- headers={'content-type': 'application/json'})
- self.assert_success(r)
- data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json()
+ self.put_zone(zone_id, payload)
+ data = self.get_zone(zone_id)
for k in payload.keys():
self.assertIn(k, data)
self.assertEqual(data[k], payload[k])
self.assertEqual(len(keydata), 4)
r = self.session.delete(self.url("/api/v1/servers/localhost/zones/powerdnssec.org./metadata/PUBLISH-CDS"))
- self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.status_code, 204)
pdnsutil('rectify-zone', zonename)
def sdig(*args):
- sdig_command_line = [SDIG, '127.0.0.1', str(DNSPORT)] + list(args)
+ if is_auth():
+ sdig_command_line = [SDIG, '127.0.0.1', str(DNSPORT)] + list(args)
+ else:
+ sdig_command_line = [SDIG, '127.0.0.1', str(DNSPORT)] + list(args) + ["recurse"]
try:
return subprocess.check_output(sdig_command_line).decode('utf-8')
except subprocess.CalledProcessError as except_inst:
namedconf.write("""
zone "%s" {
- type master;
+ type primary;
file "%s.zone";
};""" % (zone, zonename))
return False
if self.mask != other.mask:
return False
+ if self.scope != other.scope:
+ return False
if self.family != other.family:
return False
return True
[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
}
dnspython==2.1.0
-nose>=1.3.7
+pytest
Twisted>0.15.0
requests>=2.18.4
git+https://github.com/PowerDNS/xfrserver.git@0.3
set -x
fi
-ignore="-I test_GSSTSIG.py"
+ignore="--ignore=test_GSSTSIG.py"
if [ "${WITHKERBEROS}" = "YES" ]; then
ignore=""
- (cd kerberos-server && docker-compose up --detach --build)
+ (cd kerberos-server && sudo docker compose up --detach --build)
fi
-nosetests --with-xunit $ignore $@
+pytest --junitxml=pytest.xml $ignore $@
ret=$?
if [ "${WITHKERBEROS}" = "YES" ]; then
- (cd kerberos-server && docker-compose stop || exit 0)
+ (cd kerberos-server && sudo docker compose stop || exit 0)
fi
exit $ret
import threading
import unittest
+import clientsubnetoption
import dns
from twisted.internet.protocol import DatagramProtocol
resolver=%s.1:5301
any-to-tcp=no
launch=bind
+edns-subnet-processing=yes
"""
_config_params = ['_PREFIX']
noerror.example.org. 3600 IN ALIAS noerror.example.com.
nxd.example.org. 3600 IN ALIAS nxd.example.com.
-servfail.example.org. 3600 IN ALIAS servfail.example.com
+servfail.example.org. 3600 IN ALIAS servfail.example.com.
+subnet.example.org. 3600 IN ALIAS subnet.example.com.
+subnetwrong.example.org. 3600 IN ALIAS subnetwrong.example.com.
""",
}
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertAnyRRsetInAnswer(res, expected_a)
+ self.assertEqual(len(res.options), 0) # this checks that we don't invent ECS on non-ECS queries
query = dns.message.make_query('noerror.example.org', 'AAAA')
res = self.sendUDPQuery(query)
res = self.sendTCPQuery(query)
self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+ def testECS(self):
+ expected_a = [dns.rrset.from_text('subnet.example.org.',
+ 0, dns.rdataclass.IN, 'A',
+ '192.0.2.1')]
+ expected_aaaa = [dns.rrset.from_text('subnet.example.org.',
+ 0, dns.rdataclass.IN, 'AAAA',
+ '2001:DB8::1')]
+
+ ecso = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24)
+ ecso2 = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24, 22)
+ query = dns.message.make_query('subnet.example.org', 'A', use_edns=True, options=[ecso])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertAnyRRsetInAnswer(res, expected_a)
+ self.assertEqual(res.options[0], ecso2)
+
+ ecso = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64)
+ ecso2 = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64, 48)
+ query = dns.message.make_query('subnet.example.org', 'A', use_edns=True, options=[ecso])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertAnyRRsetInAnswer(res, expected_a)
+ self.assertEqual(res.options[0], ecso2)
+
+ def testECSWrong(self):
+ expected_a = [dns.rrset.from_text('subnetwrong.example.org.',
+ 0, dns.rdataclass.IN, 'A',
+ '192.0.2.1')]
+ expected_aaaa = [dns.rrset.from_text('subnetwrong.example.org.',
+ 0, dns.rdataclass.IN, 'AAAA',
+ '2001:DB8::1')]
+
+ ecso = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24) # FIXME change all IPs to documentation space in this file
+ ecso2 = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24, 22)
+ query = dns.message.make_query('subnetwrong.example.org', 'A', use_edns=True, options=[ecso])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertAnyRRsetInAnswer(res, expected_a)
+ self.assertEqual(res.options[0], ecso2)
+
+ ecso = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64)
+ ecso2 = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64, 48)
+ query = dns.message.make_query('subnetwrong.example.org', 'A', use_edns=True, options=[ecso])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertAnyRRsetInAnswer(res, expected_a)
+ self.assertEqual(res.options[0], ecso2)
+
+ def testECSNone(self):
+ expected_a = [dns.rrset.from_text('noerror.example.org.',
+ 0, dns.rdataclass.IN, 'A',
+ '192.0.2.1')]
+ expected_aaaa = [dns.rrset.from_text('noerror.example.org.',
+ 0, dns.rdataclass.IN, 'AAAA',
+ '2001:DB8::1')]
+
+ ecso = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24)
+ ecso2 = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24, 0)
+ query = dns.message.make_query('noerror.example.org', 'A', use_edns=True, options=[ecso])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertAnyRRsetInAnswer(res, expected_a)
+ self.assertEqual(res.options[0], ecso2)
+
+ ecso = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64)
+ ecso2 = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64, 0)
+ query = dns.message.make_query('noerror.example.org', 'A', use_edns=True, options=[ecso])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertAnyRRsetInAnswer(res, expected_a)
+ self.assertEqual(res.options[0], ecso2)
class AliasUDPResponder(DatagramProtocol):
def datagramReceived(self, datagram, address):
response.use_edns(edns=False)
response.flags |= dns.flags.RA
- if request.question[0].name == dns.name.from_text(
- 'noerror.example.com.'):
+ question = request.question[0]
+ name = question.name
+ name_text = name.to_text()
+
+ if name_text in ('noerror.example.com.', 'subnet.example.com.', 'subnetwrong.example.com.'):
+
+ do_ecs = False
+ do_ecs_wrong = False
+ if name_text == 'subnet.example.com.':
+ do_ecs = True
+ elif name_text == 'subnetwrong.example.com.':
+ do_ecs = True
+ do_ecs_wrong = True
+
response.set_rcode(dns.rcode.NOERROR)
- if request.question[0].rdtype in [dns.rdatatype.A,
+ if question.rdtype in [dns.rdatatype.A,
dns.rdatatype.ANY]:
response.answer.append(
dns.rrset.from_text(
- request.question[0].name,
+ name,
0, dns.rdataclass.IN, 'A', '192.0.2.1'))
- if request.question[0].rdtype in [dns.rdatatype.AAAA,
+ if question.rdtype in [dns.rdatatype.AAAA,
dns.rdatatype.ANY]:
response.answer.append(
- dns.rrset.from_text(request.question[0].name,
+ dns.rrset.from_text(name,
0, dns.rdataclass.IN, 'AAAA',
'2001:DB8::1'))
- if request.question[0].name == dns.name.from_text(
- 'nxd.example.com.'):
+
+ if do_ecs:
+ if request.options[0].family == clientsubnetoption.FAMILY_IPV4:
+ ecso = clientsubnetoption.ClientSubnetOption('5.6.7.0' if do_ecs_wrong else '1.2.3.0', 24, 22)
+ else:
+ ecso = clientsubnetoption.ClientSubnetOption('2600::' if do_ecs_wrong else '2001:db8:db6:db5::', 64, 48)
+ response.use_edns(edns=True, options=[ecso])
+
+ if name_text == 'nxd.example.com.':
response.set_rcode(dns.rcode.NXDOMAIN)
response.authority.append(
dns.rrset.from_text(
'example.com.',
0, dns.rdataclass.IN, 'SOA', 'ns1.example.com. hostmaster.example.com. 2018062101 1 2 3 4'))
- if request.question[0].name == dns.name.from_text(
- 'servfail.example.com.'):
+ if name_text == 'servfail.example.com.':
response.set_rcode(dns.rcode.SERVFAIL)
self.transport.write(response.to_wire(max_size=65535), address)
def testNoAcceptor(self):
self.kinit("testuser1")
self.nsupdate("add inserted3.noacceptor.net 10 A 1.2.3.3", 2)
- self.checkNotInDB('example.net', 'inserted3.example.net')
+ self.checkNotInDB('noacceptor.net', 'inserted3.noacceptor.net')
def testWrongAcceptor(self):
self.kinit("testuser1")
self.nsupdate("add inserted4.wrongacceptor.net 10 A 1.2.3.4", 2)
- self.checkNotInDB('example.net', 'inserted4.example.net')
+ self.checkNotInDB('wrongacceptor.net', 'inserted4.wrongacceptor.net')
class TestLuaGSSTSIG(GSSTSIGBase):
def testNoAcceptor(self):
self.kinit("testuser1")
self.nsupdate("add inserted12.noacceptor.net 10 A 1.2.3.12", 2)
- self.checkNotInDB('example.net', 'inserted12.example.net')
+ self.checkNotInDB('noacceptor.net', 'inserted12.noacceptor.net')
def testWrongAcceptor(self):
self.kinit("testuser1")
self.nsupdate("add inserted13.wrongacceptor.net 10 A 1.2.3.13", 2)
- self.checkNotInDB('example.net', 'inserted13.example.net')
+ self.checkNotInDB('wrongacceptor.net', 'inserted13.wrongacceptor.net')
launch=gsqlite3 bind
gsqlite3-database=configs/auth/powerdns.sqlite
gsqlite3-dnssec
-slave
-slave-cycle-interval=1
+secondary
+xfr-cycle-interval=1
query-cache-ttl=20
negquery-cache-ttl=60
"""
@classmethod
def setUpClass(cls):
super(TestIXFR, cls).setUpClass()
- os.system("$PDNSUTIL --config-dir=configs/auth create-slave-zone example. 127.0.0.1:%s" % (xfrServerPort,))
+ os.system("$PDNSUTIL --config-dir=configs/auth create-secondary-zone example. 127.0.0.1:%s" % (xfrServerPort,))
os.system("$PDNSUTIL --config-dir=configs/auth set-meta example. IXFR 1")
def waitUntilCorrectSerialIsLoaded(self, serial, timeout=10):
hashed-v6.example.org. 3600 IN LUA AAAA "pickhashed({{ '2001:db8:a0b:12f0::1', 'fe80::2a1:9bff:fe9b:f268' }})"
hashed-txt.example.org. 3600 IN LUA TXT "pickhashed({{ 'bob', 'alice' }})"
whashed.example.org. 3600 IN LUA A "pickwhashed({{ {{15, '1.2.3.4'}}, {{42, '4.3.2.1'}} }})"
+*.namehashed.example.org. 3600 IN LUA A "picknamehashed({{ {{15, '1.2.3.4'}}, {{42, '4.3.2.1'}} }})"
whashed-txt.example.org. 3600 IN LUA TXT "pickwhashed({{ {{15, 'bob'}}, {{42, 'alice'}} }})"
+chashed.example.org. 3600 IN LUA A "pickchashed({{ {{15, '1.2.3.4'}}, {{42, '4.3.2.1'}} }})"
+chashed-txt.example.org. 3600 IN LUA TXT "pickchashed({{ {{15, 'bob'}}, {{42, 'alice'}} }})"
rand.example.org. 3600 IN LUA A "pickrandom({{'{prefix}.101', '{prefix}.102'}})"
rand-txt.example.org. 3600 IN LUA TXT "pickrandom({{ 'bob', 'alice' }})"
randn-txt.example.org. 3600 IN LUA TXT "pickrandomsample( 2, {{ 'bob', 'alice', 'john' }} )"
resolve IN LUA A ";local r=resolve('localhost', 1) local t={{}} for _,v in ipairs(r) do table.insert(t, v:toString()) end return t"
+filterforwardempty IN LUA A "filterForward('192.0.2.1', newNMG{{'192.1.2.0/24'}}, '')"
+
*.createforward IN LUA A "filterForward(createForward(), newNMG{{'1.0.0.0/8', '64.0.0.0/8'}})"
*.createreverse IN LUA PTR "createReverse('%5%.example.com', {{['10.10.10.10'] = 'quad10.example.com.'}})"
*.createreverse6 IN LUA PTR "createReverse6('%33%.example.com', {{['2001:db8::1'] = 'example.example.com.'}})"
newcafromraw IN LUA AAAA "newCAFromRaw('ABCD020340506070'):toString()"
counter IN LUA TXT ";counter = counter or 0 counter=counter+1 return tostring(counter)"
+
+lookmeup IN A 192.0.2.5
+dblookup IN LUA A "dblookup('lookmeup.example.org', pdns.A)[1]"
""",
'createforward6.example.org': """
createforward6.example.org. 3600 IN SOA {soa}
self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
self.assertAnswerEmpty(res)
- def testWHashed(self):
+ def testCWHashed(self):
"""
- Basic pickwhashed() test with a set of A records
+ Basic pickwhashed() and pickchashed() test with a set of A records
As the `bestwho` is hashed, we should always get the same answer
"""
- expected = [dns.rrset.from_text('whashed.example.org.', 0, dns.rdataclass.IN, 'A', '1.2.3.4'),
- dns.rrset.from_text('whashed.example.org.', 0, dns.rdataclass.IN, 'A', '4.3.2.1')]
- query = dns.message.make_query('whashed.example.org', 'A')
+ for qname in ['whashed.example.org.', 'chashed.example.org.']:
+ expected = [dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '1.2.3.4'),
+ dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '4.3.2.1')]
+ query = dns.message.make_query(qname, 'A')
- first = self.sendUDPQuery(query)
- self.assertRcodeEqual(first, dns.rcode.NOERROR)
- self.assertAnyRRsetInAnswer(first, expected)
- for _ in range(5):
- res = self.sendUDPQuery(query)
+ first = self.sendUDPQuery(query)
+ self.assertRcodeEqual(first, dns.rcode.NOERROR)
+ self.assertAnyRRsetInAnswer(first, expected)
+ for _ in range(5):
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, first.answer[0])
+
+ def testNamehashed(self):
+ """
+ Basic picknamehashed() test with a set of A records
+ As the name is hashed, we should always get the same IP back for the same record name.
+ """
+
+ queries = [
+ {
+ 'query': dns.message.make_query('test.namehashed.example.org', 'A'),
+ 'expected': dns.rrset.from_text('test.namehashed.example.org.', 0,
+ dns.rdataclass.IN, 'A',
+ '1.2.3.4'),
+ },
+ {
+ 'query': dns.message.make_query('test2.namehashed.example.org', 'A'),
+ 'expected': dns.rrset.from_text('test2.namehashed.example.org.', 0,
+ dns.rdataclass.IN, 'A',
+ '4.3.2.1'),
+ }
+ ]
+ for query in queries :
+ res = self.sendUDPQuery(query['query'])
self.assertRcodeEqual(res, dns.rcode.NOERROR)
- self.assertRRsetInAnswer(res, first.answer[0])
+ self.assertRRsetInAnswer(res, query['expected'])
- def testWHashedTxt(self):
+ def testCWHashedTxt(self):
"""
Basic pickwhashed() test with a set of TXT records
As the `bestwho` is hashed, we should always get the same answer
"""
- expected = [dns.rrset.from_text('whashed-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'bob'),
- dns.rrset.from_text('whashed-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'alice')]
- query = dns.message.make_query('whashed-txt.example.org', 'TXT')
+ for qname in ['whashed-txt.example.org.', 'chashed-txt.example.org.']:
+ expected = [dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'TXT', 'bob'),
+ dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'TXT', 'alice')]
+ query = dns.message.make_query(qname,'TXT')
- first = self.sendUDPQuery(query)
- self.assertRcodeEqual(first, dns.rcode.NOERROR)
- self.assertAnyRRsetInAnswer(first, expected)
- for _ in range(5):
- res = self.sendUDPQuery(query)
- self.assertRcodeEqual(res, dns.rcode.NOERROR)
- self.assertRRsetInAnswer(res, first.answer[0])
+ first = self.sendUDPQuery(query)
+ self.assertRcodeEqual(first, dns.rcode.NOERROR)
+ self.assertAnyRRsetInAnswer(first, expected)
+ for _ in range(5):
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, first.answer[0])
def testHashed(self):
"""
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertEqual(res.answer, response.answer)
+ def testFilterForwardEmpty(self):
+ """
+ Test filterForward() function with empty fallback
+ """
+ name = 'filterforwardempty.example.org.'
+
+ query = dns.message.make_query(name, 'A')
+
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(res.answer, [])
+
def testCreateForwardAndReverse(self):
expected = {
".createforward.example.org." : (dns.rdatatype.A, {
"ip40414243": "64.65.66.67",
"ipp40414243": "64.65.66.67",
"ip4041424": "0.0.0.0",
+ "host64-22-33-44": "64.22.33.44",
"2.2.2.2": "0.0.0.0" # filtered
}),
".createreverse.example.org." : (dns.rdatatype.PTR, {
self.assertEqual(len(resUDP), 1)
self.assertEqual(len(resTCP), 1)
+ def testDblookup(self):
+ """
+ Test dblookup() function
+ """
+
+ name = 'dblookup.example.org.'
+
+ query = dns.message.make_query(name, 'A')
+
+ response = dns.message.make_response(query)
+
+ response.answer.append(dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.5'))
+
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(self.sortRRsets(res.answer), self.sortRRsets(response.answer))
+
+
class TestLuaRecordsShared(TestLuaRecords):
_config_template = """
geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
namedconf.write("""
zone "%s" {
- type slave;
+ type secondary;
file "%s.zone";
masters { %s; };
};""" % (zone, zonename, cls._zones[zone]))
launch=gsqlite3 bind
gsqlite3-database=configs/auth/powerdns.sqlite
gsqlite3-dnssec
-slave
+secondary
cache-ttl=0
query-cache-ttl=0
domain-metadata-cache-ttl=0
negquery-cache-ttl=0
-slave-cycle-interval=1
+xfr-cycle-interval=1
#loglevel=9
#axfr-fetch-timeout=20
"""
@classmethod
def setUpClass(cls):
super(XFRIncompleteAuthTest, cls).setUpClass()
- os.system("$PDNSUTIL --config-dir=configs/auth create-slave-zone zone.rpz. 127.0.0.1:%s" % (badxfrServerPort,))
+ os.system("$PDNSUTIL --config-dir=configs/auth create-secondary-zone zone.rpz. 127.0.0.1:%s" % (badxfrServerPort,))
os.system("$PDNSUTIL --config-dir=configs/auth set-meta zone.rpz. IXFR 1")
def waitUntilCorrectSerialIsLoaded(self, serial, timeout=20):
/server.csr
/server.key
/server.pem
-/server.ocsp
/server.p12
+/server-doq.*
+/server-doh3.*
+/server-ocsp.chain
+/server-ocsp.csr
+/server-ocsp.key
+/server-ocsp.pem
+/server-ocsp.p12
+/server-tls.*
+/server.ocsp
/configs
/dnsdist.log
/dnsdist_test.conf
--- /dev/null
+#!/usr/bin/env python
+import time
+import requests
+import dns
+from dnsdisttests import DNSDistTest, pickAvailablePort
+
+_maintenanceWaitTime = 2
+
+def waitForMaintenanceToRun():
+ time.sleep(_maintenanceWaitTime)
+
+class DynBlocksTest(DNSDistTest):
+
+ _webTimeout = 2.0
+ _webServerPort = pickAvailablePort()
+ _webServerBasicAuthPassword = 'secret'
+ _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
+ _webServerAPIKey = 'apisecret'
+ _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
+ _dynBlockQPS = 10
+ _dynBlockPeriod = 2
+ # this needs to be greater than maintenanceWaitTime
+ _dynBlockDuration = _maintenanceWaitTime + 2
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+
+ def doTestDynBlockViaAPI(self, ipRange, reason, minSeconds, maxSeconds, minBlocks, maxBlocks, ebpf=False):
+ headers = {'x-api-key': self._webServerAPIKey}
+ url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
+ r = requests.get(url, headers=headers, timeout=self._webTimeout)
+ self.assertTrue(r)
+ self.assertEqual(r.status_code, 200)
+
+ content = r.json()
+ self.assertIsNotNone(content)
+ self.assertIn(ipRange, content)
+
+ values = content[ipRange]
+ for key in ['reason', 'seconds', 'blocks', 'action', 'ebpf']:
+ self.assertIn(key, values)
+
+ self.assertEqual(values['reason'], reason)
+ self.assertGreaterEqual(values['seconds'], minSeconds)
+ self.assertLessEqual(values['seconds'], maxSeconds)
+ self.assertGreaterEqual(values['blocks'], minBlocks)
+ self.assertLessEqual(values['blocks'], maxBlocks)
+ self.assertEqual(values['ebpf'], True if ebpf else False)
+
+ def doTestQRate(self, name, testViaAPI=True, ebpf=False):
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=0.5)
+ self.assertEqual(receivedResponse, None)
+
+ if testViaAPI:
+ self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1, ebpf)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ # again, over TCP this time
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
+ self.assertEqual(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ def doTestQRateRCode(self, name, rcode):
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(rcode)
+
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, response)
+ allowed = allowed + 1
+ else:
+ self.assertEqual(receivedResponse, expectedResponse)
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ allowed = 0
+ sent = 0
+ # again, over TCP this time
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, response)
+ allowed = allowed + 1
+ else:
+ self.assertEqual(receivedResponse, expectedResponse)
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ def doTestResponseByteRate(self, name, dynBlockBytesPerSecond):
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ response.answer.append(dns.rrset.from_text_list(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4']))
+ response.answer.append(dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1'))
+
+ allowed = 0
+ sent = 0
+
+ print(time.time())
+
+ for _ in range(int(dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + len(response.to_wire())
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + len(response.to_wire())
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+ # and stop right there, otherwise we might
+ # wait for so long that the dynblock is gone
+ # by the time we finished
+ break
+
+ # we might be already blocked, but we should have been able to send
+ # at least dynBlockBytesPerSecond bytes
+ print(allowed)
+ print(sent)
+ print(time.time())
+ self.assertGreaterEqual(allowed, dynBlockBytesPerSecond)
+
+ print(self.sendConsoleCommand("showDynBlocks()"))
+ print(self.sendConsoleCommand("grepq(\"\")"))
+ print(time.time())
+
+ if allowed == sent:
+ print("Waiting for the maintenance function to run")
+ waitForMaintenanceToRun()
+
+ print(self.sendConsoleCommand("showDynBlocks()"))
+ print(self.sendConsoleCommand("grepq(\"\")"))
+ print(time.time())
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
+ self.assertEqual(receivedResponse, None)
+
+ print(self.sendConsoleCommand("showDynBlocks()"))
+ print(self.sendConsoleCommand("grepq(\"\")"))
+ print(time.time())
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ print(self.sendConsoleCommand("showDynBlocks()"))
+ print(self.sendConsoleCommand("grepq(\"\")"))
+ print(time.time())
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ # again, over TCP this time
+ allowed = 0
+ sent = 0
+ for _ in range(int(dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response, timeout=0.5)
+ sent = sent + len(response.to_wire())
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + len(response.to_wire())
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+ # and stop right there, otherwise we might
+ # wait for so long that the dynblock is gone
+ # by the time we finished
+ break
+
+ # we might be already blocked, but we should have been able to send
+ # at least dynBlockBytesPerSecond bytes
+ self.assertGreaterEqual(allowed, dynBlockBytesPerSecond)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ def doTestRCodeRate(self, name, rcode):
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(rcode)
+
+ # start with normal responses
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ waitForMaintenanceToRun()
+
+ # we should NOT be dropped!
+ (_, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertEqual(receivedResponse, response)
+
+ # now with rcode!
+ sent = 0
+ allowed = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(expectedResponse, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
+ self.assertEqual(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ # again, over TCP this time
+ # start with normal responses
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ waitForMaintenanceToRun()
+
+ # we should NOT be dropped!
+ (_, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertEqual(receivedResponse, response)
+
+ # now with rcode!
+ sent = 0
+ allowed = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(expectedResponse, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
+ self.assertEqual(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount):
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(rcode)
+
+ # start with normal responses
+ for _ in range(noerrorcount-1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ waitForMaintenanceToRun()
+
+ # we should NOT be dropped!
+ (_, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertEqual(receivedResponse, response)
+
+ # now with rcode!
+ sent = 0
+ allowed = 0
+ for _ in range(rcodecount):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(expectedResponse, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
+ self.assertGreaterEqual(allowed, rcodecount)
+
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
+ self.assertEqual(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ # again, over TCP this time
+ # start with normal responses
+ for _ in range(noerrorcount-1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ waitForMaintenanceToRun()
+
+ # we should NOT be dropped!
+ (_, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertEqual(receivedResponse, response)
+
+ # now with rcode!
+ sent = 0
+ allowed = 0
+ for _ in range(rcodecount):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(expectedResponse, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
+ self.assertGreaterEqual(allowed, rcodecount)
+
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
+ self.assertEqual(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
import pycurl
from io import BytesIO
+from doqclient import quic_query
+from doh3client import doh3_query
+
from eqdnsmessage import AssertEqualDNSMessageMixin
from proxyprotocol import ProxyProtocol
except NameError:
pass
+def getWorkerID():
+ if not 'PYTEST_XDIST_WORKER' in os.environ:
+ return 0
+ workerName = os.environ['PYTEST_XDIST_WORKER']
+ return int(workerName[2:])
+
+workerPorts = {}
+
+def pickAvailablePort():
+ global workerPorts
+ workerID = getWorkerID()
+ if workerID in workerPorts:
+ port = workerPorts[workerID] + 1
+ else:
+ port = 11000 + (workerID * 1000)
+ workerPorts[workerID] = port
+ return port
class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
"""
from dnsdist on a separate queue, allowing the tests to check
that the queries sent from dnsdist were as expected.
"""
- _dnsDistPort = 5340
_dnsDistListeningAddr = "127.0.0.1"
- _testServerPort = 5350
_toResponderQueue = Queue()
_fromResponderQueue = Queue()
_queueTimeout = 1
"""
_config_params = ['_testServerPort']
_acl = ['127.0.0.1/32']
- _consolePort = 5199
_consoleKey = None
_healthCheckName = 'a.root-servers.net.'
_healthCheckCounter = 0
_answerUnexpected = True
_checkConfigExpectedOutput = None
_verboseMode = False
+ _sudoMode = False
_skipListeningOnCL = False
_alternateListeningAddr = None
_alternateListeningPort = None
_UDPResponder = None
_TCPResponder = None
_extraStartupSleep = 0
+ _dnsDistPort = pickAvailablePort()
+ _consolePort = pickAvailablePort()
+ _testServerPort = pickAvailablePort()
@classmethod
def waitForTCPSocket(cls, ipaddress, port):
@classmethod
def startResponders(cls):
print("Launching responders..")
+ cls._testServerPort = pickAvailablePort()
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls.waitForTCPSocket("127.0.0.1", cls._testServerPort);
@classmethod
def startDNSDist(cls):
+ cls._dnsDistPort = pickAvailablePort()
+ cls._consolePort = pickAvailablePort()
+
print("Launching dnsdist..")
confFile = os.path.join('configs', 'dnsdist_%s.conf' % (cls.__name__))
params = tuple([getattr(cls, param) for param in cls._config_params])
print(params)
with open(confFile, 'w') as conf:
conf.write("-- Autogenerated by dnsdisttests.py\n")
+ conf.write(f"-- dnsdist will listen on {cls._dnsDistPort}")
conf.write(cls._config_template % params)
conf.write("setSecurityPollSuffix('')")
if cls._verboseMode:
dnsdistcmd.append('-v')
+ if cls._sudoMode:
+ preserve_env_values = ['LD_LIBRARY_PATH', 'LLVM_PROFILE_FILE']
+ for value in preserve_env_values:
+ if value in os.environ:
+ dnsdistcmd.insert(0, value + '=' + os.environ[value])
+ dnsdistcmd.insert(0, 'sudo')
for acl in cls._acl:
dnsdistcmd.extend(['--acl', acl])
else:
expectedOutput = ('Configuration \'%s\' OK!\n' % (confFile)).encode()
if not cls._verboseMode and output != expectedOutput:
- raise AssertionError('dnsdist --check-config failed: %s' % output)
+ raise AssertionError('dnsdist --check-config failed: %s (expected %s)' % (output, expectedOutput))
logFile = os.path.join('configs', 'dnsdist_%s.log' % (cls.__name__))
with open(logFile, 'w') as fdLog:
@classmethod
def _ResponderIncrementCounter(cls):
- if threading.currentThread().name in cls._responsesCounter:
- cls._responsesCounter[threading.currentThread().name] += 1
+ if threading.current_thread().name in cls._responsesCounter:
+ cls._responsesCounter[threading.current_thread().name] += 1
else:
- cls._responsesCounter[threading.currentThread().name] = 1
+ cls._responsesCounter[threading.current_thread().name] = 1
@classmethod
def _getResponse(cls, request, fromQueue, toQueue, synthesize=None):
sock.close()
@classmethod
- def handleTCPConnection(cls, conn, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None):
+ def handleTCPConnection(cls, conn, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, partialWrite=False):
ignoreTrailing = trailingDataResponse is True
- data = conn.recv(2)
+ try:
+ data = conn.recv(2)
+ except Exception as err:
+ data = None
+ print(f'Error while reading query size in TCP responder thread {err=}, {type(err)=}')
if not data:
conn.close()
return
conn.close()
return
- conn.send(struct.pack("!H", len(wire)))
+ wireLen = struct.pack("!H", len(wire))
+ if partialWrite:
+ for b in wireLen:
+ conn.send(bytes([b]))
+ time.sleep(0.5)
+ else:
+ conn.send(wireLen)
conn.send(wire)
while multipleResponses:
conn.close()
@classmethod
- def TCPResponder(cls, port, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, tlsContext=None, multipleConnections=False, listeningAddr='127.0.0.1'):
+ def TCPResponder(cls, port, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, tlsContext=None, multipleConnections=False, listeningAddr='127.0.0.1', partialWrite=False):
cls._backgroundThreads[threading.get_native_id()] = True
# trailingDataResponse=True means "ignore trailing data".
# Other values are either False (meaning "raise an exception")
if multipleConnections:
thread = threading.Thread(name='TCP Connection Handler',
target=cls.handleTCPConnection,
- args=[conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback])
- thread.setDaemon(True)
+ args=[conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback, partialWrite])
+ thread.daemon = True
thread.start()
else:
- cls.handleTCPConnection(conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback)
+ cls.handleTCPConnection(conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback, partialWrite)
sock.close()
except ssl.SSLEOFError as e:
print("Unexpected EOF: %s" % (e))
return
+ except Exception as err:
+ print(f'Unexpected exception in DoH responder thread (connection init) {err=}, {type(err)=}')
+ return
dnsData = {}
# be careful, HTTP/2 headers and data might be in different recv() results
requestHeaders = None
while True:
- data = conn.recv(65535)
+ try:
+ data = conn.recv(65535)
+ except Exception as err:
+ data = None
+ print(f'Unexpected exception in DoH responder thread {err=}, {type(err)=}')
if not data:
break
thread = threading.Thread(name='DoH Connection Handler',
target=cls.handleDoHConnection,
args=[config, conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback, tlsContext, useProxyProtocol])
- thread.setDaemon(True)
+ thread.daemon = True
thread.start()
sock.close()
return sock
@classmethod
- def openTLSConnection(cls, port, serverName, caCert=None, timeout=None):
+ def openTLSConnection(cls, port, serverName, caCert=None, timeout=None, alpn=[]):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
if timeout:
# 2.7.9+
if hasattr(ssl, 'create_default_context'):
sslctx = ssl.create_default_context(cafile=caCert)
+ if len(alpn)> 0 and hasattr(sslctx, 'set_alpn_protocols'):
+ sslctx.set_alpn_protocols(alpn)
sslsock = sslctx.wrap_socket(sock, server_hostname=serverName)
else:
sslsock = ssl.wrap_socket(sock, ca_certs=caCert, cert_reqs=ssl.CERT_REQUIRED)
@classmethod
def recvTCPResponseOverConnection(cls, sock, useQueue=False, timeout=2.0):
- print("reading data")
message = None
data = sock.recv(2)
if data:
print(useQueue)
if useQueue and not cls._fromResponderQueue.empty():
receivedQuery = cls._fromResponderQueue.get(True, timeout)
- print("Got from queue")
print(receivedQuery)
return (receivedQuery, message)
else:
if useQueue:
cls._toResponderQueue.put(response, True, timeout)
- sock = cls.openTCPConnection(timeout)
+ try:
+ sock = cls.openTCPConnection(timeout)
+ except socket.timeout as e:
+ print("Timeout while opening TCP connection: %s" % (str(e)))
+ return (None, None)
try:
- cls.sendTCPQueryOverConnection(sock, query, rawQuery)
- message = cls.recvTCPResponseOverConnection(sock)
+ cls.sendTCPQueryOverConnection(sock, query, rawQuery, timeout=timeout)
+ message = cls.recvTCPResponseOverConnection(sock, timeout=timeout)
except socket.timeout as e:
print("Timeout while sending or receiving TCP data: %s" % (str(e)))
except socket.error as e:
receivedQuery = None
print(useQueue)
if useQueue and not cls._fromResponderQueue.empty():
- print("Got from queue")
print(receivedQuery)
receivedQuery = cls._fromResponderQueue.get(True, timeout)
else:
return result.decode('UTF-8')
@classmethod
- def sendConsoleCommand(cls, command, timeout=1.0):
+ def sendConsoleCommand(cls, command, timeout=5.0, IPv6=False):
ourNonce = libnacl.utils.rand_nonce()
theirNonce = None
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock = socket.socket(socket.AF_INET if not IPv6 else socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
if timeout:
sock.settimeout(timeout)
- sock.connect(("127.0.0.1", cls._consolePort))
+ sock.connect(("::1", cls._consolePort, 0, 0) if IPv6 else ("127.0.0.1", cls._consolePort))
sock.send(ourNonce)
theirNonce = sock.recv(len(ourNonce))
if len(theirNonce) != len(ourNonce):
def checkResponseNoEDNS(self, expected, received):
self.checkMessageNoEDNS(expected, received)
- def generateNewCertificateAndKey(self):
+ @staticmethod
+ def generateNewCertificateAndKey(filePrefix):
# generate and sign a new cert
- cmd = ['openssl', 'req', '-new', '-newkey', 'rsa:2048', '-nodes', '-keyout', 'server.key', '-out', 'server.csr', '-config', 'configServer.conf']
+ cmd = ['openssl', 'req', '-new', '-newkey', 'rsa:2048', '-nodes', '-keyout', filePrefix + '.key', '-out', filePrefix + '.csr', '-config', 'configServer.conf']
output = None
try:
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
output = process.communicate(input='')
except subprocess.CalledProcessError as exc:
raise AssertionError('openssl req failed (%d): %s' % (exc.returncode, exc.output))
- cmd = ['openssl', 'x509', '-req', '-days', '1', '-CA', 'ca.pem', '-CAkey', 'ca.key', '-CAcreateserial', '-in', 'server.csr', '-out', 'server.pem', '-extfile', 'configServer.conf', '-extensions', 'v3_req']
+ cmd = ['openssl', 'x509', '-req', '-days', '1', '-CA', 'ca.pem', '-CAkey', 'ca.key', '-CAcreateserial', '-in', filePrefix + '.csr', '-out', filePrefix + '.pem', '-extfile', 'configServer.conf', '-extensions', 'v3_req']
output = None
try:
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
except subprocess.CalledProcessError as exc:
raise AssertionError('openssl x509 failed (%d): %s' % (exc.returncode, exc.output))
- with open('server.chain', 'w') as outFile:
- for inFileName in ['server.pem', 'ca.pem']:
+ with open(filePrefix + '.chain', 'w') as outFile:
+ for inFileName in [filePrefix + '.pem', 'ca.pem']:
with open(inFileName) as inFile:
outFile.write(inFile.read())
- cmd = ['openssl', 'pkcs12', '-export', '-passout', 'pass:passw0rd', '-clcerts', '-in', 'server.pem', '-CAfile', 'ca.pem', '-inkey', 'server.key', '-out', 'server.p12']
+ cmd = ['openssl', 'pkcs12', '-export', '-passout', 'pass:passw0rd', '-clcerts', '-in', filePrefix + '.pem', '-CAfile', 'ca.pem', '-inkey', filePrefix + '.key', '-out', filePrefix + '.p12']
output = None
try:
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
return conn
@classmethod
- def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[], useHTTPS=True, fromQueue=None, toQueue=None):
+ def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[], useHTTPS=True, fromQueue=None, toQueue=None, conn=None):
url = cls.getDOHGetURL(baseurl, query, rawQuery)
- conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
- response_headers = BytesIO()
- #conn.setopt(pycurl.VERBOSE, True)
- conn.setopt(pycurl.URL, url)
- conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
+
+ if not conn:
+ conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
+ # this means "really do HTTP/2, not HTTP/1 with Upgrade headers"
+ conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE)
+
if useHTTPS:
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.SSL_VERIFYHOST, 2)
if caFile:
conn.setopt(pycurl.CAINFO, caFile)
+ response_headers = BytesIO()
+ #conn.setopt(pycurl.VERBOSE, True)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
+
conn.setopt(pycurl.HTTPHEADER, customHeaders)
conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
#conn.setopt(pycurl.VERBOSE, True)
conn.setopt(pycurl.URL, url)
conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
+ # this means "really do HTTP/2, not HTTP/1 with Upgrade headers"
+ conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE)
if useHTTPS:
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.SSL_VERIFYHOST, 2)
def sendDOHQueryWrapper(self, query, response, useQueue=True):
return self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue)
+ def sendDOHWithNGHTTP2QueryWrapper(self, query, response, useQueue=True):
+ return self.sendDOHQuery(self._dohWithNGHTTP2ServerPort, self._serverName, self._dohWithNGHTTP2BaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue)
+
+ def sendDOHWithH2OQueryWrapper(self, query, response, useQueue=True):
+ return self.sendDOHQuery(self._dohWithH2OServerPort, self._serverName, self._dohWithH2OBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue)
+
def sendDOTQueryWrapper(self, query, response, useQueue=True):
return self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response, self._caCert, useQueue=useQueue)
+
+ def sendDOQQueryWrapper(self, query, response, useQueue=True):
+ return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName)
+
+ def sendDOH3QueryWrapper(self, query, response, useQueue=True):
+ return self.sendDOH3Query(self._doh3ServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName)
+ @classmethod
+ def getDOQConnection(cls, port, caFile=None, source=None, source_port=0):
+
+ manager = dns.quic.SyncQuicManager(
+ verify_mode=caFile
+ )
+
+ return manager.connect('127.0.0.1', port, source, source_port)
+
+ @classmethod
+ def sendDOQQuery(cls, port, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, fromQueue=None, toQueue=None, connection=None, serverName=None):
+
+ if response:
+ if toQueue:
+ toQueue.put(response, True, timeout)
+ else:
+ cls._toResponderQueue.put(response, True, timeout)
+
+ (message, _) = quic_query(query, '127.0.0.1', timeout, port, verify=caFile, server_hostname=serverName)
+
+ receivedQuery = None
+
+ if useQueue:
+ if fromQueue:
+ if not fromQueue.empty():
+ receivedQuery = fromQueue.get(True, timeout)
+ else:
+ if not cls._fromResponderQueue.empty():
+ receivedQuery = cls._fromResponderQueue.get(True, timeout)
+
+ return (receivedQuery, message)
+
+ @classmethod
+ def sendDOH3Query(cls, port, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, fromQueue=None, toQueue=None, connection=None, serverName=None, post=False):
+
+ if response:
+ if toQueue:
+ toQueue.put(response, True, timeout)
+ else:
+ cls._toResponderQueue.put(response, True, timeout)
+
+ message = doh3_query(query, baseurl, timeout, port, verify=caFile, server_hostname=serverName, post=post)
+
+ receivedQuery = None
+
+ if useQueue:
+ if fromQueue:
+ if not fromQueue.empty():
+ receivedQuery = fromQueue.get(True, timeout)
+ else:
+ if not cls._fromResponderQueue.empty():
+ receivedQuery = cls._fromResponderQueue.get(True, timeout)
+
+ return (receivedQuery, message)
--- /dev/null
+import base64
+import asyncio
+import pickle
+import ssl
+import struct
+import dns
+import time
+import async_timeout
+
+from collections import deque
+from typing import BinaryIO, Callable, Deque, Dict, List, Optional, Union, cast
+from urllib.parse import urlparse
+
+import aioquic
+from aioquic.asyncio.client import connect
+from aioquic.asyncio.protocol import QuicConnectionProtocol
+from aioquic.h0.connection import H0_ALPN, H0Connection
+from aioquic.h3.connection import H3_ALPN, ErrorCode, H3Connection
+from aioquic.h3.events import (
+ DataReceived,
+ H3Event,
+ HeadersReceived,
+ PushPromiseReceived,
+)
+from aioquic.quic.configuration import QuicConfiguration
+from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
+from aioquic.tls import CipherSuite, SessionTicket
+
+from doqclient import StreamResetError
+
+HttpConnection = Union[H0Connection, H3Connection]
+
+class URL:
+ def __init__(self, url: str) -> None:
+ parsed = urlparse(url)
+
+ self.authority = parsed.netloc
+ self.full_path = parsed.path or "/"
+ if parsed.query:
+ self.full_path += "?" + parsed.query
+ self.scheme = parsed.scheme
+
+
+class HttpRequest:
+ def __init__(
+ self,
+ method: str,
+ url: URL,
+ content: bytes = b"",
+ headers: Optional[Dict] = None,
+ ) -> None:
+ if headers is None:
+ headers = {}
+
+ self.content = content
+ self.headers = headers
+ self.method = method
+ self.url = url
+
+class HttpClient(QuicConnectionProtocol):
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+
+ self.pushes: Dict[int, Deque[H3Event]] = {}
+ self._http: Optional[HttpConnection] = None
+ self._request_events: Dict[int, Deque[H3Event]] = {}
+ self._request_waiter: Dict[int, asyncio.Future[Deque[H3Event]]] = {}
+
+ if self._quic.configuration.alpn_protocols[0].startswith("hq-"):
+ self._http = H0Connection(self._quic)
+ else:
+ self._http = H3Connection(self._quic)
+
+ async def get(self, url: str, headers: Optional[Dict] = None) -> Deque[H3Event]:
+ """
+ Perform a GET request.
+ """
+ return await self._request(
+ HttpRequest(method="GET", url=URL(url), headers=headers)
+ )
+
+ async def post(
+ self, url: str, data: bytes, headers: Optional[Dict] = None
+ ) -> Deque[H3Event]:
+ """
+ Perform a POST request.
+ """
+ return await self._request(
+ HttpRequest(method="POST", url=URL(url), content=data, headers=headers)
+ )
+
+
+ def http_event_received(self, event: H3Event) -> None:
+ if isinstance(event, (HeadersReceived, DataReceived)):
+ stream_id = event.stream_id
+ if stream_id in self._request_events:
+ # http
+ self._request_events[event.stream_id].append(event)
+ if event.stream_ended:
+ request_waiter = self._request_waiter.pop(stream_id)
+ request_waiter.set_result(self._request_events.pop(stream_id))
+
+ elif stream_id in self._websockets:
+ # websocket
+ websocket = self._websockets[stream_id]
+ websocket.http_event_received(event)
+
+ elif event.push_id in self.pushes:
+ # push
+ self.pushes[event.push_id].append(event)
+
+ elif isinstance(event, PushPromiseReceived):
+ self.pushes[event.push_id] = deque()
+ self.pushes[event.push_id].append(event)
+
+ def quic_event_received(self, event: QuicEvent) -> None:
+ if isinstance(event, StreamReset):
+ waiter = self._request_waiter.pop(event.stream_id)
+ waiter.set_result([event])
+
+ # pass event to the HTTP layer
+ if self._http is not None:
+ for http_event in self._http.handle_event(event):
+ self.http_event_received(http_event)
+
+ async def _request(self, request: HttpRequest) -> Deque[H3Event]:
+ stream_id = self._quic.get_next_available_stream_id()
+ self._http.send_headers(
+ stream_id=stream_id,
+ headers=[
+ (b":method", request.method.encode()),
+ (b":scheme", request.url.scheme.encode()),
+ (b":authority", request.url.authority.encode()),
+ (b":path", request.url.full_path.encode()),
+ ]
+ + [(k.encode(), v.encode()) for (k, v) in request.headers.items()],
+ end_stream=not request.content,
+ )
+ if request.content:
+ self._http.send_data(
+ stream_id=stream_id, data=request.content, end_stream=True
+ )
+
+ waiter = self._loop.create_future()
+ self._request_events[stream_id] = deque()
+ self._request_waiter[stream_id] = waiter
+ self.transmit()
+
+ return await asyncio.shield(waiter)
+
+
+async def perform_http_request(
+ client: HttpClient,
+ url: str,
+ data: Optional[bytes],
+ include: bool,
+ output_dir: Optional[str],
+) -> None:
+ # perform request
+ start = time.time()
+ if data is not None:
+ http_events = await client.post(
+ url,
+ data=data,
+ headers={
+ "content-length": str(len(data)),
+ "content-type": "application/dns-message",
+ },
+ )
+ method = "POST"
+ else:
+ http_events = await client.get(url)
+ method = "GET"
+ elapsed = time.time() - start
+
+ result = bytes()
+ for http_event in http_events:
+ if isinstance(http_event, DataReceived):
+ result += http_event.data
+ if isinstance(http_event, StreamReset):
+ result = http_event
+ return result
+
+
+async def async_h3_query(
+ configuration: QuicConfiguration,
+ baseurl: str,
+ port: int,
+ query: dns.message,
+ timeout: float,
+ post: bool,
+ create_protocol=HttpClient,
+) -> None:
+
+ url = baseurl
+ if not post:
+ url = "{}?dns={}".format(baseurl, base64.urlsafe_b64encode(query.to_wire()).decode('UTF8').rstrip('='))
+ async with connect(
+ "127.0.0.1",
+ port,
+ configuration=configuration,
+ create_protocol=create_protocol,
+ ) as client:
+ client = cast(HttpClient, client)
+
+ try:
+ async with async_timeout.timeout(timeout):
+
+ answer = await perform_http_request(
+ client=client,
+ url=url,
+ data=query.to_wire() if post else None,
+ include=False,
+ output_dir=None,
+ )
+
+ return answer
+ except asyncio.TimeoutError as e:
+ return e
+
+
+def doh3_query(query, baseurl, timeout=2, port=853, verify=None, server_hostname=None, post=False):
+ configuration = QuicConfiguration(alpn_protocols=H3_ALPN, is_client=True)
+ if verify:
+ configuration.load_verify_locations(verify)
+
+ result = asyncio.run(
+ async_h3_query(
+ configuration=configuration,
+ baseurl=baseurl,
+ port=port,
+ query=query,
+ timeout=timeout,
+ create_protocol=HttpClient,
+ post=post
+ )
+ )
+
+ if (isinstance(result, StreamReset)):
+ raise StreamResetError(result.error_code)
+ if (isinstance(result, asyncio.TimeoutError)):
+ raise TimeoutError()
+ return dns.message.from_wire(result)
--- /dev/null
+import asyncio
+import pickle
+import ssl
+import struct
+from typing import Any, Optional, cast
+import dns
+import dns.message
+import async_timeout
+
+from aioquic.quic.configuration import QuicConfiguration
+from aioquic.asyncio.client import connect
+from aioquic.asyncio.protocol import QuicConnectionProtocol
+from aioquic.quic.configuration import QuicConfiguration
+from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
+from aioquic.quic.logger import QuicFileLogger
+
+class DnsClientProtocol(QuicConnectionProtocol):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._ack_waiter: Any = None
+
+ def pack(self, data):
+ # serialize query
+ data = bytes(data)
+ data = struct.pack("!H", len(data)) + data
+ return data
+
+ async def query(self, query: dns.message) -> None:
+ data = self.pack(query.to_wire())
+ # send query and wait for answer
+ stream_id = self._quic.get_next_available_stream_id()
+ self._quic.send_stream_data(stream_id, data, end_stream=True)
+ waiter = self._loop.create_future()
+ self._ack_waiter = waiter
+ self.transmit()
+
+ return await asyncio.shield(waiter)
+
+ def quic_event_received(self, event: QuicEvent) -> None:
+ if self._ack_waiter is not None:
+ if isinstance(event, StreamDataReceived):
+ length = struct.unpack("!H", bytes(event.data[:2]))[0]
+ answer = dns.message.from_wire(event.data[2 : 2 + length], ignore_trailing=True)
+
+ waiter = self._ack_waiter
+ self._ack_waiter = None
+ waiter.set_result(answer)
+ if isinstance(event, StreamReset):
+ waiter = self._ack_waiter
+ self._ack_waiter = None
+ waiter.set_result(event)
+
+class BogusDnsClientProtocol(DnsClientProtocol):
+ def pack(self, data):
+ # serialize query
+ data = bytes(data)
+ data = struct.pack("!H", len(data) * 2) + data
+ return data
+
+
+async def async_quic_query(
+ configuration: QuicConfiguration,
+ host: str,
+ port: int,
+ query: dns.message,
+ timeout: float,
+ create_protocol=DnsClientProtocol
+) -> None:
+ print("Connecting to {}:{}".format(host, port))
+ async with connect(
+ host,
+ port,
+ configuration=configuration,
+ create_protocol=create_protocol,
+ ) as client:
+ client = cast(DnsClientProtocol, client)
+ print("Sending DNS query")
+ try:
+ async with async_timeout.timeout(timeout):
+ answer = await client.query(query)
+ return (answer, client._quic.tls._peer_certificate.serial_number)
+ except asyncio.TimeoutError as e:
+ return (e, None)
+
+class StreamResetError(Exception):
+ def __init__(self, error, message="Stream reset by peer"):
+ self.error = error
+ super().__init__(message)
+
+def quic_query(query, host='127.0.0.1', timeout=2, port=853, verify=None, server_hostname=None):
+ configuration = QuicConfiguration(alpn_protocols=["doq"], is_client=True)
+ if verify:
+ configuration.load_verify_locations(verify)
+ (result, serial) = asyncio.run(
+ async_quic_query(
+ configuration=configuration,
+ host=host,
+ port=port,
+ query=query,
+ timeout=timeout,
+ create_protocol=DnsClientProtocol
+ )
+ )
+ if (isinstance(result, StreamReset)):
+ raise StreamResetError(result.error_code)
+ if (isinstance(result, asyncio.TimeoutError)):
+ raise TimeoutError()
+ return (result, serial)
+
+def quic_bogus_query(query, host='127.0.0.1', timeout=2, port=853, verify=None, server_hostname=None):
+ configuration = QuicConfiguration(alpn_protocols=["doq"], is_client=True)
+ if verify:
+ configuration.load_verify_locations(verify)
+ (result, _) = asyncio.run(
+ async_quic_query(
+ configuration=configuration,
+ host=host,
+ port=port,
+ query=query,
+ timeout=timeout,
+ create_protocol=BogusDnsClientProtocol
+ )
+ )
+ if (isinstance(result, StreamReset)):
+ raise StreamResetError(result.error_code)
+ if (isinstance(result, asyncio.TimeoutError)):
+ raise TimeoutError()
+ return result
--- /dev/null
+../regression-tests.recursor-dnssec/extendederrors.py
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python
+import copy
+import dns
+import socket
+import struct
+import sys
+
+from proxyprotocol import ProxyProtocol
+
+def ProxyProtocolUDPResponder(port, fromQueue, toQueue):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ try:
+ sock.bind(("127.0.0.1", port))
+ except socket.error as e:
+ print("Error binding in the Proxy Protocol UDP responder: %s" % str(e))
+ sys.exit(1)
+
+ while True:
+ data, addr = sock.recvfrom(4096)
+
+ proxy = ProxyProtocol()
+ if len(data) < proxy.HEADER_SIZE:
+ continue
+
+ if not proxy.parseHeader(data):
+ continue
+
+ if proxy.local:
+ # likely a healthcheck
+ data = data[proxy.HEADER_SIZE:]
+ request = dns.message.from_wire(data)
+ response = dns.message.make_response(request)
+ wire = response.to_wire()
+ sock.settimeout(2.0)
+ sock.sendto(wire, addr)
+ sock.settimeout(None)
+
+ continue
+
+ payload = data[:(proxy.HEADER_SIZE + proxy.contentLen)]
+ dnsData = data[(proxy.HEADER_SIZE + proxy.contentLen):]
+ toQueue.put([payload, dnsData], True, 2.0)
+ # computing the correct ID for the response
+ request = dns.message.from_wire(dnsData)
+ response = fromQueue.get(True, 2.0)
+ response.id = request.id
+
+ sock.settimeout(2.0)
+ sock.sendto(response.to_wire(), addr)
+ sock.settimeout(None)
+
+ sock.close()
+
+def ProxyProtocolTCPResponder(port, fromQueue, toQueue):
+ # be aware that this responder will not accept a new connection
+ # until the last one has been closed. This is done on purpose to
+ # to check for connection reuse, making sure that a lot of connections
+ # are not opened in parallel.
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ try:
+ sock.bind(("127.0.0.1", port))
+ except socket.error as e:
+ print("Error binding in the TCP responder: %s" % str(e))
+ sys.exit(1)
+
+ sock.listen(100)
+ while True:
+ (conn, _) = sock.accept()
+ conn.settimeout(5.0)
+ # try to read the entire Proxy Protocol header
+ proxy = ProxyProtocol()
+ header = conn.recv(proxy.HEADER_SIZE)
+ if not header:
+ conn.close()
+ continue
+
+ if not proxy.parseHeader(header):
+ conn.close()
+ continue
+
+ proxyContent = conn.recv(proxy.contentLen)
+ if not proxyContent:
+ conn.close()
+ continue
+
+ payload = header + proxyContent
+ while True:
+ try:
+ data = conn.recv(2)
+ except socket.timeout:
+ data = None
+
+ if not data:
+ conn.close()
+ break
+
+ (datalen,) = struct.unpack("!H", data)
+ data = conn.recv(datalen)
+
+ toQueue.put([payload, data], True, 2.0)
+
+ response = copy.deepcopy(fromQueue.get(True, 2.0))
+ if not response:
+ conn.close()
+ break
+
+ # computing the correct ID for the response
+ request = dns.message.from_wire(data)
+ response.id = request.id
+
+ wire = response.to_wire()
+ conn.send(struct.pack("!H", len(wire)))
+ conn.send(wire)
+
+ conn.close()
+
+ sock.close()
--- /dev/null
+#!/usr/bin/env python
+
+import dns
+from doqclient import StreamResetError
+
+class QUICTests(object):
+
+ def testQUICSimple(self):
+ """
+ QUIC: Simple query
+ """
+ name = 'simple.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ (receivedQuery, receivedResponse) = self.sendQUICQuery(query, response=response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.assertEqual(receivedResponse, response)
+
+ def testQUICMultipleStreams(self):
+ """
+ QUIC: Test multiple queries using the same connection
+ """
+ name = 'simple.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ connection = self.getQUICConnection()
+
+ (receivedQuery, receivedResponse) = self.sendQUICQuery(query, response=response, connection=connection)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+
+ (receivedQuery, receivedResponse) = self.sendQUICQuery(query, response=response, connection=connection)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+
+ def testDropped(self):
+ """
+ QUIC: Dropped query
+ """
+ name = 'drop.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ dropped = False
+ try:
+ (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+ self.assertTrue(False)
+ except StreamResetError as e:
+ self.assertEqual(e.error, 5);
+
+ def testRefused(self):
+ """
+ QUIC: Refused
+ """
+ name = 'refused.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.id = 0
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ def testSpoof(self):
+ """
+ QUIC: Spoofed
+ """
+ name = 'spoof.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.id = 0
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '1.2.3.4')
+ expectedResponse.answer.append(rrset)
+
+ (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ def testQUICNoBackend(self):
+ """
+ QUIC: No backend
+ """
+ name = 'no-backend.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ dropped = False
+ try:
+ (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+ self.assertTrue(False)
+ except StreamResetError as e :
+ self.assertEqual(e.error, 5);
+
+class QUICACLTests(object):
+
+ def testDropped(self):
+ """
+ QUIC: Dropped query because of ACL
+ """
+ name = 'acl.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ dropped = False
+ try:
+ (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+ self.assertTrue(False)
+ except StreamResetError as e:
+ self.assertEqual(e.error, 5);
+ dropped = True
+ self.assertTrue(dropped)
+
+class QUICWithCacheTests(object):
+ def testCached(self):
+ """
+ QUIC Cache: Served from cache
+ """
+ numberOfQueries = 10
+ name = 'cached.quic.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ query.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendQUICQuery(query, response=response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, response)
+
+ for _ in range(numberOfQueries):
+ (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, response)
+
+ total = 0
+ for key in self._responsesCounter:
+ total += self._responsesCounter[key]
+
+ self.assertEqual(total, 1)
dnspython>=2.2.0
-nose>=1.3.7
+pytest
+pytest-xdist
libnacl>=1.4.3,<1.7
requests>=2.1.0
protobuf>=3.0
lmdb>=0.95
cdbx==0.1.2
h2>=4.0.0
+aioquic
+async_timeout
out=$(mktemp)
set -o pipefail
-if ! nosetests --with-xunit $@ 2>&1 | tee "${out}" ; then
+if ! pytest --junitxml=pytest.xml --dist=loadfile -n auto $@ 2>&1 | tee "${out}" ; then
for log in configs/*.log; do
echo "=== ${log} ==="
cat "${log}"
echo
done
- echo "=== nosetests log ==="
+ echo "=== pytest log ==="
cat "${out}"
- echo "=== end of nosetests log ==="
+ echo "=== end of pytest log ==="
false
fi
rm -f "${out}"
-addAction(makeRule("includedir.advanced.tests.powerdns.com."), AllowAction())
+addAction(SuffixMatchNodeRule("includedir.advanced.tests.powerdns.com."), AllowAction())
import os.path
import base64
+import dns
import json
import requests
import socket
import time
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class APITestsBase(DNSDistTest):
__test__ = False
- _webTimeout = 2.0
- _webServerPort = 8083
+ _webTimeout = 5.0
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
_webServerAPIKey = 'apisecret'
'latency-avg10000', 'latency-avg1000000', 'latency-tcp-avg100', 'latency-tcp-avg1000',
'latency-tcp-avg10000', 'latency-tcp-avg1000000', 'latency-dot-avg100', 'latency-dot-avg1000',
'latency-dot-avg10000', 'latency-dot-avg1000000', 'latency-doh-avg100', 'latency-doh-avg1000',
- 'latency-doh-avg10000', 'latency-doh-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
+ 'latency-doh-avg10000', 'latency-doh-avg1000000', 'latency-doq-avg100', 'latency-doq-avg1000',
+ 'latency-doq-avg10000', 'latency-doq-avg1000000', 'latency-doh3-avg100', 'latency-doh3-avg1000',
+ 'latency-doh3-avg10000', 'latency-doh3-avg1000000','uptime', 'real-memory-usage', 'noncompliant-queries',
'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
'cache-misses', 'cpu-iowait', 'cpu-steal', 'cpu-sys-msec', 'cpu-user-msec', 'fd-usage', 'dyn-blocked',
'dyn-block-nmg-size', 'rule-servfail', 'rule-truncated', 'security-status',
'udp-in-csum-errors', 'udp-in-errors', 'udp-noport-errors', 'udp-recvbuf-errors', 'udp-sndbuf-errors',
'udp6-in-errors', 'udp6-recvbuf-errors', 'udp6-sndbuf-errors', 'udp6-noport-errors', 'udp6-in-csum-errors',
- 'doh-query-pipe-full', 'doh-response-pipe-full', 'proxy-protocol-invalid', 'tcp-listen-overflows',
+ 'doh-query-pipe-full', 'doh-response-pipe-full', 'doq-response-pipe-full', 'doh3-response-pipe-full', 'proxy-protocol-invalid', 'tcp-listen-overflows',
'outgoing-doh-query-pipe-full', 'tcp-query-pipe-full', 'tcp-cross-protocol-query-pipe-full',
'tcp-cross-protocol-response-pipe-full']
_verboseMode = True
'dropRate', 'responses', 'nonCompliantResponses', 'tcpDiedSendingQuery', 'tcpDiedReadingResponse',
'tcpGaveUp', 'tcpReadTimeouts', 'tcpWriteTimeouts', 'tcpCurrentConnections',
'tcpNewConnections', 'tcpReusedConnections', 'tlsResumptions', 'tcpAvgQueriesPerConnection',
- 'tcpAvgConnectionDuration', 'tcpLatency', 'protocol']:
+ 'tcpAvgConnectionDuration', 'tcpLatency', 'protocol', 'healthCheckFailures', 'healthCheckFailuresParsing', 'healthCheckFailuresTimeout', 'healthCheckFailuresNetwork', 'healthCheckFailuresMismatch', 'healthCheckFailuresInvalid']:
self.assertIn(key, server)
for key in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds',
for key in ['blocks']:
self.assertTrue(content[key] >= 0)
+ def testServersLocalhostRings(self):
+ """
+ API: /api/v1/servers/localhost/rings
+ """
+ headers = {'x-api-key': self._webServerAPIKey}
+ url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/rings'
+ expectedValues = ['age', 'id', 'name', 'requestor', 'size', 'qtype', 'protocol', 'rd']
+ expectedResponseValues = expectedValues + ['latency', 'rcode', 'tc', 'aa', 'answers', 'backend']
+ r = requests.get(url, headers=headers, timeout=self._webTimeout)
+ self.assertTrue(r)
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue(r.json())
+ content = r.json()
+ self.assertIn('queries', content)
+ self.assertIn('responses', content)
+ self.assertEqual(len(content['queries']), 0)
+ self.assertEqual(len(content['responses']), 0)
+
+ name = 'simple.api.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ r = requests.get(url, headers=headers, timeout=self._webTimeout)
+ self.assertTrue(r)
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue(r.json())
+ content = r.json()
+ self.assertIn('queries', content)
+ self.assertIn('responses', content)
+ self.assertEqual(len(content['queries']), 2)
+ self.assertEqual(len(content['responses']), 2)
+ for entry in content['queries']:
+ for value in expectedValues:
+ self.assertIn(value, entry)
+ for entry in content['responses']:
+ for value in expectedResponseValues:
+ self.assertIn(value, entry)
+
class TestAPIServerDown(APITestsBase):
__test__ = True
_config_template = """
import threading
import time
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class TestAXFR(DNSDistTest):
# because, contrary to the other ones, its
# TCP responder allows multiple responses and we don't want
# to mix things up.
- _testServerPort = 5370
+ _testServerPort = pickAvailablePort()
_config_template = """
newServer{address="127.0.0.1:%s"}
"""
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, True, None, None, True])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
def setUp(self):
_config_params = ['_testServerPort', '_dnsDistPort', '_dnsDistPort']
_acl = ['127.0.0.1/32', '::1/128']
_skipListeningOnCL = True
+ _verboseMode = True
def testAdvancedGetLocalAddressOnAnyBind(self):
"""
'address-was-127-0-0-2.local-address-any.advanced.tests.powerdns.com.')
response.answer.append(rrset)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.settimeout(1.0)
+ sock.settimeout(2.0)
sock.connect(('127.0.0.2', self._dnsDistPort))
try:
query = query.to_wire()
sock.send(query)
(data, remote) = sock.recvfrom(4096)
- self.assertEquals(remote[0], '127.0.0.2')
+ self.assertEqual(remote[0], '127.0.0.2')
except socket.timeout:
data = None
# a bit more tricky, UDP-only IPv4
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.settimeout(1.0)
+ sock.settimeout(2.0)
sock.connect(('127.0.0.2', self._dnsDistPort))
self._toResponderQueue.put(response, True, 1.0)
try:
data = query.to_wire()
sock.send(data)
(data, remote) = sock.recvfrom(4096)
- self.assertEquals(remote[0], '127.0.0.2')
+ self.assertEqual(remote[0], '127.0.0.2')
except socket.timeout:
data = None
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)
+ sock.settimeout(2.0)
sock.connect(('::1', self._dnsDistPort))
self._toResponderQueue.put(response, True, 1.0)
try:
data = query.to_wire()
sock.send(data)
(data, remote) = sock.recvfrom(4096)
- self.assertEquals(remote[0], '::1')
+ self.assertEqual(remote[0], '::1')
except socket.timeout:
data = None
# a bit more tricky, UDP-only IPv4
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.settimeout(1.0)
+ sock.settimeout(2.0)
sock.connect(('127.0.1.19', self._dnsDistPort))
self._toResponderQueue.put(response, True, 1.0)
try:
data = query.to_wire()
sock.send(data)
(data, remote) = sock.recvfrom(4096)
- self.assertEquals(remote[0], '127.0.1.19')
+ self.assertEqual(remote[0], '127.0.1.19')
except socket.timeout:
data = None
_config_template = """
function custommetrics(dq)
initialCounter = getMetric("my-custom-counter")
- initialGauge = getMetric("my-custom-counter")
+ initialGauge = getMetric("my-custom-gauge")
incMetric("my-custom-counter")
+ incMetric("my-custom-counter", 41)
setMetric("my-custom-gauge", initialGauge + 1.3)
- if getMetric("my-custom-counter") ~= (initialCounter + 1) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
+ if getMetric("my-custom-counter") ~= (initialCounter + 42) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
return DNSAction.Spoof, '1.2.3.5'
end
return DNSAction.Spoof, '4.3.2.1'
end
function declareNewMetric(dq)
- if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should fail") then
- return DNSAction.Spoof, '1.2.3.4'
+ if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should work fine") then
+ return DNSAction.None
end
- return DNSAction.None
+ return DNSAction.Spoof, '1.2.3.4'
end
declareMetric("my-custom-counter", "counter", "Number of tests run")
self.assertEqual(len(timeouts), 2)
self.assertEqual(len(queries), 2)
+
+class TestTruncatedUDPLargeAnswers(DNSDistTest):
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+ """
+ def testVeryLargeAnswer(self):
+ """
+ Advanced: Check that UDP responses that are too large for our buffer are dismissed
+ """
+ name = 'very-large-answer-dismissed.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'TXT', 'IN')
+ response = dns.message.make_response(query)
+ # we prepare a large answer
+ content = ''
+ for i in range(31):
+ if len(content) > 0:
+ content = content + ' '
+ content = content + 'A' * 255
+ # pad up to 8192
+ content = content + ' ' + 'B' * 170
+
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.TXT,
+ content)
+ response.answer.append(rrset)
+ self.assertEqual(len(response.to_wire()), 8192)
+
+ # TCP should be OK
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, response)
+
+ # UDP should never get an answer, because dnsdist will not be able to get it from the backend
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertFalse(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
import os
import socket
+import sys
import threading
import unittest
import dns
-from dnsdisttests import DNSDistTest
+import dns.message
+import doqclient
+
+from dnsdisttests import DNSDistTest, pickAvailablePort
def AsyncResponder(listenPath, responsePath):
# Make sure the socket does not already exist
asyncResponderSocketPath = '/tmp/async-responder.sock'
dnsdistSocketPath = '/tmp/dnsdist.sock'
asyncResponder = threading.Thread(name='Asynchronous Responder', target=AsyncResponder, args=[asyncResponderSocketPath, dnsdistSocketPath])
-asyncResponder.setDaemon(True)
+asyncResponder.daemon = True
asyncResponder.start()
class AsyncTests(object):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _tlsServerPort = pickAvailablePort()
+ _dohWithNGHTTP2ServerPort = pickAvailablePort()
+ _dohWithH2OServerPort = pickAvailablePort()
+ _dohWithNGHTTP2BaseURL = ("https://%s:%d/" % (_serverName, _dohWithNGHTTP2ServerPort))
+ _dohWithH2OBaseURL = ("https://%s:%d/" % (_serverName, _dohWithH2OServerPort))
+ _doqServerPort = pickAvailablePort()
+
def testPass(self):
"""
Async: Accept
'192.0.2.1')
response.answer.append(rrset)
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
(receivedQuery, receivedResponse) = sender(query, response)
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
+ if method == 'sendDOQQueryWrapper':
+ # dnspython sets the ID to 0
+ receivedResponse.id = response.id
self.assertEqual(response, receivedResponse)
- (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
def testPassCached(self):
"""
Async: Accept (cached)
'192.0.2.1')
response.answer.append(rrset)
- for method in ("sendUDPQuery", "sendTCPQuery"):
- # first time to fill the cache
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
- (receivedQuery, receivedResponse) = sender(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
+ if method != 'sendDOTQueryWrapper' and method != 'sendDOHWithH2OQueryWrapper' and method != 'sendDOQQueryWrapper':
+ # first time to fill the cache
+ # disabled for DoT since it was already filled via TCP
+ (receivedQuery, receivedResponse) = sender(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
# second time from the cache
sender = getattr(self, method)
(_, receivedResponse) = sender(query, response=None, useQueue=False)
+ if method == 'sendDOQQueryWrapper':
+ # dnspython sets the ID to 0
+ receivedResponse.id = response.id
self.assertEqual(response, receivedResponse)
- (_, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=None, useQueue=False, caFile=self._caCert)
- self.assertEqual(response, receivedResponse)
-
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=None, useQueue=False, caFile=self._caCert)
- self.assertEqual(response, receivedResponse)
-
def testTimeoutThenAccept(self):
"""
Async: Timeout then accept
'192.0.2.1')
response.answer.append(rrset)
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
(receivedQuery, receivedResponse) = sender(query, response)
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
+ if method == 'sendDOQQueryWrapper':
+ # dnspython sets the ID to 0
+ receivedResponse.id = response.id
self.assertEqual(response, receivedResponse)
- (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
def testAcceptThenTimeout(self):
"""
Async: Accept then timeout
'192.0.2.1')
response.answer.append(rrset)
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
(receivedQuery, receivedResponse) = sender(query, response)
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
+ if method == 'sendDOQQueryWrapper':
+ # dnspython sets the ID to 0
+ receivedResponse.id = response.id
self.assertEqual(response, receivedResponse)
- (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
def testAcceptThenRefuse(self):
"""
Async: Accept then refuse
expectedResponse.flags |= dns.flags.RA
expectedResponse.set_rcode(dns.rcode.REFUSED)
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
(receivedQuery, receivedResponse) = sender(query, response)
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
+ if method == 'sendDOQQueryWrapper':
+ # dnspython sets the ID to 0
+ receivedResponse.id = expectedResponse.id
self.assertEqual(expectedResponse, receivedResponse)
- (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
-
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
-
def testAcceptThenCustom(self):
"""
Async: Accept then custom
'192.0.2.1')
response.answer.append(rrset)
- # easier to get the same custom response to everyone, sorry!
- expectedQuery = dns.message.make_query('custom.async.tests.powerdns.com.', 'A', 'IN')
+ expectedQuery = dns.message.make_query(name, 'A', 'IN')
expectedQuery.id = query.id
expectedResponse = dns.message.make_response(expectedQuery)
expectedResponse.flags |= dns.flags.RA
expectedResponse.set_rcode(dns.rcode.FORMERR)
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
(receivedQuery, receivedResponse) = sender(query, response)
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
+ if method == 'sendDOQQueryWrapper':
+ # dnspython sets the ID to 0
+ receivedResponse.id = expectedResponse.id
self.assertEqual(expectedResponse, receivedResponse)
- (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
-
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
-
def testAcceptThenDrop(self):
"""
Async: Accept then drop
'192.0.2.1')
response.answer.append(rrset)
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
- (receivedQuery, receivedResponse) = sender(query, response)
+ try:
+ (receivedQuery, receivedResponse) = sender(query, response)
+ except doqclient.StreamResetError:
+ if not self._fromResponderQueue.empty():
+ receivedQuery = self._fromResponderQueue.get(True, 1.0)
+ receivedResponse = None
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
self.assertEqual(receivedResponse, None)
- (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, None)
-
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, None)
-
def testRefused(self):
"""
Async: Refused
expectedResponse.flags |= dns.flags.RA
expectedResponse.set_rcode(dns.rcode.REFUSED)
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertTrue(receivedResponse)
+ if method == 'sendDOQQueryWrapper':
+ # dnspython sets the ID to 0
+ receivedResponse.id = expectedResponse.id
self.assertEqual(expectedResponse, receivedResponse)
- (_, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=None, caFile=self._caCert, useQueue=False)
- self.assertEqual(expectedResponse, receivedResponse)
-
- (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=None, caFile=self._caCert, useQueue=False)
- self.assertEqual(expectedResponse, receivedResponse)
-
def testDrop(self):
"""
Async: Drop
name = 'drop.async.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
- (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ try:
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ except doqclient.StreamResetError:
+ receivedResponse = None
self.assertEqual(receivedResponse, None)
- (_, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=None, caFile=self._caCert, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=None, caFile=self._caCert, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
def testCustom(self):
"""
Async: Custom answer
expectedResponse.flags |= dns.flags.RA
expectedResponse.set_rcode(dns.rcode.FORMERR)
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
sender = getattr(self, method)
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertTrue(receivedResponse)
+ if method == 'sendDOQQueryWrapper':
+ # dnspython sets the ID to 0
+ receivedResponse.id = expectedResponse.id
self.assertEqual(expectedResponse, receivedResponse)
- (_, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=None, caFile=self._caCert, useQueue=False)
- self.assertEqual(expectedResponse, receivedResponse)
-
- (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=None, caFile=self._caCert, useQueue=False)
- self.assertEqual(expectedResponse, receivedResponse)
-
def testTruncation(self):
"""
Async: DoH query, timeout then truncated answer over UDP, then valid over TCP and accept
"""
# the query is first forwarded over UDP, leading to a TC=1 answer from the
# backend, then over TCP
- name = 'timeout-then-accept.tc.async.tests.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- query.id = 42
- expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
- expectedQuery.id = 42
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 3600,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '127.0.0.1')
- response.answer.append(rrset)
- # first response is a TC=1
- tcResponse = dns.message.make_response(query)
- tcResponse.flags |= dns.flags.TC
- self._toResponderQueue.put(tcResponse, True, 2.0)
-
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, response=response)
- # first query, received by the responder over UDP
- self.assertTrue(receivedQuery)
- receivedQuery.id = expectedQuery.id
- self.assertEqual(expectedQuery, receivedQuery)
- self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
-
- # check the response
- self.assertTrue(receivedResponse)
- self.assertEqual(response, receivedResponse)
-
- # check the second query, received by the responder over TCP
- receivedQuery = self._fromResponderQueue.get(True, 2.0)
- self.assertTrue(receivedQuery)
- receivedQuery.id = expectedQuery.id
- self.assertEqual(expectedQuery, receivedQuery)
- self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ for method in ("sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"):
+ sender = getattr(self, method)
+ name = 'timeout-then-accept.' + method + '.tc.async.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.id = 42
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 42
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
-@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
-class TestAsyncFFI(DNSDistTest, AsyncTests):
+ # first response is a TC=1
+ tcResponse = dns.message.make_response(query)
+ tcResponse.flags |= dns.flags.TC
+ self._toResponderQueue.put(tcResponse, True, 2.0)
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
- _serverName = 'tls.tests.dnsdist.org'
- _caCert = 'ca.pem'
- _tlsServerPort = 8453
- _dohServerPort = 8443
- _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+ # first query, received by the responder over UDP
+ (receivedQuery, receivedResponse) = sender(query, response=response)
+ self.assertTrue(receivedQuery)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+
+ # check the response
+ self.assertTrue(receivedResponse)
+ self.assertEqual(response, receivedResponse)
+ # check the second query, received by the responder over TCP
+ receivedQuery = self._fromResponderQueue.get(True, 2.0)
+ self.assertTrue(receivedQuery)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+
+@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
+class TestAsyncFFI(DNSDistTest, AsyncTests):
_config_template = """
- newServer{address="127.0.0.1:%s", pool={'', 'cache'}}
- newServer{address="127.0.0.1:%s", pool="tcp-only", tcpOnly=true }
+ newServer{address="127.0.0.1:%d", pool={'', 'cache'}}
+ newServer{address="127.0.0.1:%d", pool="tcp-only", tcpOnly=true }
- addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/"})
+ addTLSLocal("127.0.0.1:%d", "%s", "%s", { provider="openssl" })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="h2o"})
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="nghttp2"})
+ addDOQLocal("127.0.0.1:%d", "%s", "%s")
local ffi = require("ffi")
local C = ffi.C
pc = newPacketCache(100)
getPool('cache'):setCache(pc)
+ local asyncObjectsMap = {}
+
function gotAsyncResponse(endpointID, message, from)
print('Got async response '..message)
return
end
local queryID = tonumber(parts[1])
+ local qname = asyncObjectsMap[queryID]
if parts[2] == 'accept' then
print('accepting')
C.dnsdist_ffi_resume_from_async(asyncID, queryID, filteringTagName, #filteringTagName, filteringTagValue, #filteringTagValue, true)
end
if parts[2] == 'custom' then
print('sending a custom response')
- local raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\006custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+ local raw = nil
+ if qname == string.char(6)..'custom'..string.char(5)..'async'..string.char(5)..'tests'..string.char(8)..'powerdns'..string.char(3)..'com' then
+ raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\006custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+ elseif qname == string.char(18)..'accept-then-custom'..string.char(5)..'async'..string.char(5)..'tests'..string.char(8)..'powerdns'..string.char(3)..'com' then
+ raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\018accept-then-custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+ elseif qname == string.char(18)..'accept-then-custom'..string.char(8)..'tcp-only'..string.char(5)..'async'..string.char(5)..'tests'..string.char(8)..'powerdns'..string.char(3)..'com' then
+ raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\018accept-then-custom\\008tcp-only\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+ end
+
C.dnsdist_ffi_set_answer_from_async(asyncID, queryID, raw, #raw)
return
end
end
- local asyncResponderEndpoint = newNetworkEndpoint('%s')
- local listener = newNetworkListener()
+ asyncResponderEndpoint = newNetworkEndpoint('%s')
+ listener = newNetworkListener()
listener:addUnixListeningEndpoint('%s', 0, gotAsyncResponse)
listener:start()
+ function getQNameRaw(dq)
+ local ret_ptr = ffi.new("char *[1]")
+ local ret_ptr_param = ffi.cast("const char **", ret_ptr)
+ local ret_size = ffi.new("size_t[1]")
+ local ret_size_param = ffi.cast("size_t*", ret_size)
+ C.dnsdist_ffi_dnsquestion_get_qname_raw(dq, ret_ptr_param, ret_size_param)
+ return ffi.string(ret_ptr[0])
+ end
+
function passQueryToAsyncFilter(dq)
print('in passQueryToAsyncFilter')
local timeout = 500 -- 500 ms
-- we need to take a copy, as we can no longer touch that data after calling set_async
local buffer = ffi.string(queryPtr, querySize)
- print(C.dnsdist_ffi_dnsquestion_set_async(dq, asyncID, C.dnsdist_ffi_dnsquestion_get_id(dq), timeout))
+ asyncObjectsMap[C.dnsdist_ffi_dnsquestion_get_id(dq)] = getQNameRaw(dq)
+
+ C.dnsdist_ffi_dnsquestion_set_async(dq, asyncID, C.dnsdist_ffi_dnsquestion_get_id(dq), timeout)
asyncResponderEndpoint:send(buffer)
return DNSAction.Allow
-- we need to take a copy, as we can no longer touch that data after calling set_async
local buffer = ffi.string(responsePtr, responseSize)
- print(C.dnsdist_ffi_dnsresponse_set_async(dr, asyncID, C.dnsdist_ffi_dnsquestion_get_id(dr), timeout))
+ asyncObjectsMap[C.dnsdist_ffi_dnsquestion_get_id(dr)] = getQNameRaw(dr)
+
+ C.dnsdist_ffi_dnsresponse_set_async(dr, asyncID, C.dnsdist_ffi_dnsquestion_get_id(dr), timeout)
asyncResponderEndpoint:send(buffer)
return DNSResponseAction.Allow
"""
_asyncResponderSocketPath = asyncResponderSocketPath
_dnsdistSocketPath = dnsdistSocketPath
- _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
+ _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_doqServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
_verboseMode = True
@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
class TestAsyncLua(DNSDistTest, AsyncTests):
-
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
- _serverName = 'tls.tests.dnsdist.org'
- _caCert = 'ca.pem'
- _tlsServerPort = 8453
- _dohServerPort = 8443
- _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
-
_config_template = """
- newServer{address="127.0.0.1:%s", pool={'', 'cache'}}
- newServer{address="127.0.0.1:%s", pool="tcp-only", tcpOnly=true }
+ newServer{address="127.0.0.1:%d", pool={'', 'cache'}}
+ newServer{address="127.0.0.1:%d", pool="tcp-only", tcpOnly=true }
- addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/"})
+ addTLSLocal("127.0.0.1:%d", "%s", "%s", { provider="openssl" })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="h2o"})
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="nghttp2"})
+ addDOQLocal("127.0.0.1:%d", "%s", "%s")
local filteringTagName = 'filtering'
local filteringTagValue = 'pass'
end
if parts[2] == 'custom' then
print('sending a custom response')
- local raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\006custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
local dq = asyncObject:getDQ()
+ local raw
+ if tostring(dq.qname) == 'custom.async.tests.powerdns.com.' then
+ raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\006custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+ elseif tostring(dq.qname) == 'accept-then-custom.async.tests.powerdns.com.' then
+ raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\018accept-then-custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+ elseif tostring(dq.qname) == 'accept-then-custom.tcp-only.async.tests.powerdns.com.' then
+ raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\018accept-then-custom\\008tcp-only\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+ end
dq:setContent(raw)
asyncObject:resume()
return
end
end
- local asyncResponderEndpoint = newNetworkEndpoint('%s')
- local listener = newNetworkListener()
+ asyncResponderEndpoint = newNetworkEndpoint('%s')
+ listener = newNetworkListener()
listener:addUnixListeningEndpoint('%s', 0, gotAsyncResponse)
listener:start()
"""
_asyncResponderSocketPath = asyncResponderSocketPath
_dnsdistSocketPath = dnsdistSocketPath
- _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
+ _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_doqServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
_verboseMode = True
from dnsdisttests import DNSDistTest
class TestBackendDiscovery(DNSDistTest):
+ # these ports are hardcoded for now, sorry about that!
_noSVCBackendPort = 10600
_svcNoUpgradeBackendPort = 10601
_svcUpgradeDoTBackendPort = 10602
tlsContext.load_cert_chain('server.chain', 'server.key')
TCPNoSVCResponder = threading.Thread(name='TCP no SVC Responder', target=cls.TCPResponder, args=[cls._noSVCBackendPort, cls._toResponderQueue, cls._fromResponderQueue, True, False, cls.NoSVCCallback])
- TCPNoSVCResponder.setDaemon(True)
+ TCPNoSVCResponder.daemon = True
TCPNoSVCResponder.start()
TCPNoUpgradeResponder = threading.Thread(name='TCP no upgrade Responder', target=cls.TCPResponder, args=[cls._svcNoUpgradeBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.NoUpgradePathCallback])
- TCPNoUpgradeResponder.setDaemon(True)
+ TCPNoUpgradeResponder.daemon = True
TCPNoUpgradeResponder.start()
- TCPUpgradeToDoTResponder = threading.Thread(name='TCP upgrade to DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTCallback])
- TCPUpgradeToDoTResponder.setDaemon(True)
+ # this one is special, does partial writes!
+ TCPUpgradeToDoTResponder = threading.Thread(name='TCP upgrade to DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTCallback, None, False, '127.0.0.1', True])
+ TCPUpgradeToDoTResponder.daemon = True
TCPUpgradeToDoTResponder.start()
# and the corresponding DoT responder
UpgradedDoTResponder = threading.Thread(name='DoT upgraded Responder', target=cls.TCPResponder, args=[10652, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- UpgradedDoTResponder.setDaemon(True)
+ UpgradedDoTResponder.daemon = True
UpgradedDoTResponder.start()
TCPUpgradeToDoHResponder = threading.Thread(name='TCP upgrade to DoH Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHCallback])
- TCPUpgradeToDoHResponder.setDaemon(True)
+ TCPUpgradeToDoHResponder.daemon = True
TCPUpgradeToDoHResponder.start()
# and the corresponding DoH responder
UpgradedDOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[10653, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- UpgradedDOHResponder.setDaemon(True)
+ UpgradedDOHResponder.daemon = True
UpgradedDOHResponder.start()
TCPUpgradeToDoTDifferentAddrResponder = threading.Thread(name='TCP upgrade to DoT different addr 1 Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendDifferentAddrPort1, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTDifferentAddr1Callback])
- TCPUpgradeToDoTDifferentAddrResponder.setDaemon(True)
+ TCPUpgradeToDoTDifferentAddrResponder.daemon = True
TCPUpgradeToDoTDifferentAddrResponder.start()
# and the corresponding DoT responder
UpgradedDoTResponder = threading.Thread(name='DoT upgraded different addr 1 Responder', target=cls.TCPResponder, args=[10654, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, False, '127.0.0.2'])
- UpgradedDoTResponder.setDaemon(True)
+ UpgradedDoTResponder.daemon = True
UpgradedDoTResponder.start()
TCPUpgradeToDoTDifferentAddrResponder = threading.Thread(name='TCP upgrade to DoT different addr 2 Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendDifferentAddrPort2, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTDifferentAddr2Callback, None, False, '127.0.0.2'])
- TCPUpgradeToDoTDifferentAddrResponder.setDaemon(True)
+ TCPUpgradeToDoTDifferentAddrResponder.daemon = True
TCPUpgradeToDoTDifferentAddrResponder.start()
# and the corresponding DoT responder
UpgradedDoTResponder = threading.Thread(name='DoT upgraded different addr 2 Responder', target=cls.TCPResponder, args=[10655, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, False])
- UpgradedDoTResponder.setDaemon(True)
+ UpgradedDoTResponder.daemon = True
UpgradedDoTResponder.start()
TCPUpgradeToUnreachableDoTResponder = threading.Thread(name='TCP upgrade to unreachable DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTUnreachableBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTUnreachableCallback])
- TCPUpgradeToUnreachableDoTResponder.setDaemon(True)
+ TCPUpgradeToUnreachableDoTResponder.daemon = True
TCPUpgradeToUnreachableDoTResponder.start()
# and NO corresponding DoT responder
# this is not a mistake!
BrokenResponseResponder = threading.Thread(name='Broken response Responder', target=cls.TCPResponder, args=[cls._svcBrokenDNSResponseBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.BrokenResponseCallback])
- BrokenResponseResponder.setDaemon(True)
+ BrokenResponseResponder.daemon = True
BrokenResponseResponder.start()
DOHMissingPathResponder = threading.Thread(name='DoH missing path Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHBackendWithoutPathPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHMissingPathCallback])
- DOHMissingPathResponder.setDaemon(True)
+ DOHMissingPathResponder.daemon = True
DOHMissingPathResponder.start()
EOFResponder = threading.Thread(name='EOF Responder', target=cls.TCPResponder, args=[cls._eofBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.EOFCallback])
- EOFResponder.setDaemon(True)
+ EOFResponder.daemon = True
EOFResponder.start()
ServFailResponder = threading.Thread(name='ServFail Responder', target=cls.TCPResponder, args=[cls._servfailBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.ServFailCallback])
- ServFailResponder.setDaemon(True)
+ ServFailResponder.daemon = True
ServFailResponder.start()
WrongNameResponder = threading.Thread(name='Wrong Name Responder', target=cls.TCPResponder, args=[cls._wrongNameBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.WrongNameCallback])
- WrongNameResponder.setDaemon(True)
+ WrongNameResponder.daemon = True
WrongNameResponder.start()
WrongIDResponder = threading.Thread(name='Wrong ID Responder', target=cls.TCPResponder, args=[cls._wrongIDBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.WrongIDCallback])
- WrongIDResponder.setDaemon(True)
+ WrongIDResponder.daemon = True
WrongIDResponder.start()
TooManyQuestionsResponder = threading.Thread(name='Too many questions Responder', target=cls.TCPResponder, args=[cls._tooManyQuestionsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.TooManyQuestionsCallback])
- TooManyQuestionsResponder.setDaemon(True)
+ TooManyQuestionsResponder.daemon = True
TooManyQuestionsResponder.start()
badQNameResponder = threading.Thread(name='Bad QName Responder', target=cls.TCPResponder, args=[cls._badQNameBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.BadQNameCallback])
- badQNameResponder.setDaemon(True)
+ badQNameResponder.daemon = True
badQNameResponder.start()
TCPUpgradeToDoTNoPortResponder = threading.Thread(name='TCP upgrade to DoT (no port) Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTNoPortBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTNoPortCallback])
- TCPUpgradeToDoTNoPortResponder.setDaemon(True)
+ TCPUpgradeToDoTNoPortResponder.daemon = True
TCPUpgradeToDoTNoPortResponder.start()
TCPUpgradeToDoHNoPortResponder = threading.Thread(name='TCP upgrade to DoH (no port) Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHNoPortBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHNoPortCallback])
- TCPUpgradeToDoHNoPortResponder.setDaemon(True)
+ TCPUpgradeToDoHNoPortResponder.daemon = True
TCPUpgradeToDoHNoPortResponder.start()
# in this particular case, the upgraded backend
# does not replace the existing one and thus
# the health-check is forced to auto (or lazy auto)
- self.assertEquals(tokens[2], 'up')
+ self.assertEqual(tokens[2], 'up')
else:
- self.assertEquals(tokens[2], 'UP')
+ self.assertEqual(tokens[2], 'UP')
pool = ''
if len(tokens) == 14:
pool = tokens[13]
# let's wait a bit longer
time.sleep(5)
self.assertTrue(self.checkBackendsUpgraded())
+
+class TestBackendDiscoveryByHostname(DNSDistTest):
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+ _config_params = ['_consoleKeyB64', '_consolePort']
+ _config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%d")
+
+ function resolveCB(hostname, ips)
+ print('Got response for '..hostname)
+ for _, ip in ipairs(ips) do
+ print(ip)
+ newServer(ip:toString())
+ end
+ end
+
+ getAddressInfo('dns.quad9.net.', resolveCB)
+ """
+ def checkBackends(self):
+ output = self.sendConsoleCommand('showServers()')
+ print(output)
+ backends = {}
+ for line in output.splitlines(False):
+ if line.startswith('#') or line.startswith('All'):
+ continue
+ tokens = line.split()
+ self.assertTrue(len(tokens) == 13 or len(tokens) == 14)
+ backends[tokens[1]] = tokens[2]
+
+ if len(backends) != 4:
+ return False
+
+ for expected in ['9.9.9.9:53', '149.112.112.112:53', '[2620:fe::9]:53', '[2620:fe::fe]:53']:
+ self.assertIn(expected, backends)
+ for backend in backends:
+ self.assertTrue(backends[backend])
+ return True
+
+ def testBackendFromHostname(self):
+ """
+ Backend Discovery: From hostname
+ """
+ # enough time for resolution to happen
+ time.sleep(4)
+ if not self.checkBackends():
+ time.sleep(4)
+ self.assertTrue(self.checkBackends())
mySMN = newSuffixMatchNode()
mySMN:add(newDNSName("nameAndQtype.tests.powerdns.com."))
addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(DNSRCode.NOTIMP))
- addAction(makeRule("drop.test.powerdns.com."), DropAction())
+ addAction(QNameSuffixRule({"drop.test.powerdns.com.", "drop2.test.powerdns.com."}), DropAction())
addAction(AndRule({QTypeRule(DNSQType.A),QNameRule("ds9a.nl")}), SpoofAction("1.2.3.4"))
addAction(newDNSName("dnsname.addaction.powerdns.com."), RCodeAction(DNSRCode.REFUSED))
addAction({newDNSName("dnsname-table1.addaction.powerdns.com."), newDNSName("dnsname-table2.addaction.powerdns.com.")}, RCodeAction(DNSRCode.REFUSED))
which is dropped by configuration. We expect
no response.
"""
- name = 'drop.test.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- for method in ("sendUDPQuery", "sendTCPQuery"):
- sender = getattr(self, method)
- (_, receivedResponse) = sender(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
+ for name in ['drop.test.powerdns.com.', 'drop2.test.powerdns.com.']:
+ query = dns.message.make_query(name, 'A', 'IN')
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, None)
def testAWithECS(self):
"""
import threading
import clientsubnetoption
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
def responseCallback(request):
if len(request.question) != 1:
# this test suite uses a different responder port
# because, contrary to the other ones, its
# responders send raw, broken data
- _testServerPort = 5400
+ _testServerPort = pickAvailablePort()
_config_template = """
setECSSourcePrefixV4(32)
newServer{address="127.0.0.1:%s", useClientSubnet=true}
# Returns broken data for non-healthcheck queries
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, responseCallback])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
# Returns broken data for non-healthcheck queries
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, responseCallback])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
def testUDPWithInvalidAnswer(self):
_config_template = """
pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
getPool(""):setCache(pc)
- addCacheHitResponseAction(makeRule("dropwhencached.cachehitresponses.tests.powerdns.com."), DropResponseAction())
+ addCacheHitResponseAction(SuffixMatchNodeRule("dropwhencached.cachehitresponses.tests.powerdns.com."), DropResponseAction())
newServer{address="127.0.0.1:%s"}
"""
_config_template = """
pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
getPool(""):setCache(pc)
- addCacheInsertedResponseAction(makeRule("cacheinsertedresponses.tests.powerdns.com."), LimitTTLResponseAction(%d, %d))
+ addCacheInsertedResponseAction(SuffixMatchNodeRule("cacheinsertedresponses.tests.powerdns.com."), LimitTTLResponseAction(%d, %d))
newServer{address="127.0.0.1:%s"}
"""
_config_params = ['capTTLMax', 'capTTLMin', '_testServerPort']
import clientsubnetoption
import cookiesoption
import requests
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class TestCaching(DNSDistTest):
_config_template = """
pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
getPool(""):setCache(pc)
- addAction(makeRule("nocache.cache.tests.powerdns.com."), SetSkipCacheAction())
- addResponseAction(makeRule("nocache-response.cache.tests.powerdns.com."), SetSkipCacheResponseAction())
+ addAction(SuffixMatchNodeRule("nocache.cache.tests.powerdns.com."), SetSkipCacheAction())
+ addResponseAction(SuffixMatchNodeRule("nocache-response.cache.tests.powerdns.com."), SetSkipCacheResponseAction())
function skipViaLua(dq)
dq.skipCache = true
return DNSAction.None, ""
class TestAPICache(DNSDistTest):
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
_webServerAPIKey = 'apisecret'
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
self.assertEqual(receivedResponse, response)
+
+class TestCachingOfVeryLargeAnswers(DNSDistTest):
+
+ _config_template = """
+ pc = newPacketCache(100, {maxTTL=86400, minTTL=1, maximumEntrySize=8192})
+ getPool(""):setCache(pc)
+ newServer{address="127.0.0.1:%d"}
+ """
+
+ def testVeryLargeAnswer(self):
+ """
+ Cache: Check that we can cache (and retrieve) VERY large answers
+
+ We should be able to get answers as large as 8192 bytes this time
+ """
+ numberOfQueries = 10
+ name = 'very-large-answer.cache.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'TXT', 'IN')
+ response = dns.message.make_response(query)
+ # we prepare a large answer
+ content = ''
+ for i in range(31):
+ if len(content) > 0:
+ content = content + ' '
+ content = content + 'A' * 255
+ # pad up to 8192
+ content = content + ' ' + 'B' * 183
+
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.TXT,
+ content)
+ response.answer.append(rrset)
+ self.assertEqual(len(response.to_wire()), 8192)
+
+ # # first query to fill the cache, over TCP
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, response)
+
+ for _ in range(numberOfQueries):
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, response)
+
+ total = 0
+ for key in self._responsesCounter:
+ total += self._responsesCounter[key]
+ TestCachingOfVeryLargeAnswers._responsesCounter[key] = 0
+
+ self.assertEqual(total, 1)
+
+ # UDP should not be cached, dnsdist has a hard limit to 4096 bytes for UDP
+ # actually we will never get an answer, because dnsdist will not be able to get it from the backend
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertFalse(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
import socket
import sys
import time
-from dnsdisttests import DNSDistTest, Queue
+from dnsdisttests import DNSDistTest, Queue, pickAvailablePort
class TestCarbon(DNSDistTest):
- _carbonServer1Port = 8000
+ _carbonServer1Port = pickAvailablePort()
_carbonServer1Name = "carbonname1"
- _carbonServer2Port = 8001
+ _carbonServer2Port = pickAvailablePort()
_carbonServer2Name = "carbonname2"
_carbonQueue1 = Queue()
_carbonQueue2 = Queue()
cls._carbonQueue1.put(lines, True, timeout=2.0)
else:
cls._carbonQueue2.put(lines, True, timeout=2.0)
- if threading.currentThread().name in cls._carbonCounters:
- cls._carbonCounters[threading.currentThread().name] += 1
+ if threading.current_thread().name in cls._carbonCounters:
+ cls._carbonCounters[threading.current_thread().name] += 1
else:
- cls._carbonCounters[threading.currentThread().name] = 1
+ cls._carbonCounters[threading.current_thread().name] = 1
conn.close()
sock.close()
@classmethod
def startResponders(cls):
cls._CarbonResponder1 = threading.Thread(name='Carbon Responder 1', target=cls.CarbonResponder, args=[cls._carbonServer1Port])
- cls._CarbonResponder1.setDaemon(True)
+ cls._CarbonResponder1.daemon = True
cls._CarbonResponder1.start()
cls._CarbonResponder2 = threading.Thread(name='Carbon Responder 2', target=cls.CarbonResponder, args=[cls._carbonServer2Port])
- cls._CarbonResponder2.setDaemon(True)
+ cls._CarbonResponder2.daemon = True
cls._CarbonResponder2.start()
def isfloat(self, num):
mySMN:add({"string-one.smn.tests.powerdns.com", "string-two.smn.tests.powerdns.com"})
mySMN:add({newDNSName("dnsname-one.smn.tests.powerdns.com"), newDNSName("dnsname-two.smn.tests.powerdns.com")})
addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(DNSRCode.NOTIMP))
- addAction(makeRule("drop.test.powerdns.com."), DropAction())
+ addAction(SuffixMatchNodeRule("drop.test.powerdns.com."), DropAction())
"""
self.tryDNSDist(configTemplate)
#!/usr/bin/env python
import base64
import dns
+import os
import socket
import time
from dnsdisttests import DNSDistTest
version = self.sendConsoleCommand('showVersion()')
self.assertTrue(version.startswith('dnsdist '))
+class TestConsoleAllowedV6(DNSDistTest):
+
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+
+ _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
+ _config_template = """
+ setKey("%s")
+ controlSocket("[::1]:%s")
+ newServer{address="127.0.0.1:%d"}
+ """
+
+ def testConsoleAllowed(self):
+ """
+ Console: Allowed IPv6
+ """
+ if 'SKIP_IPV6_TESTS' in os.environ:
+ raise unittest.SkipTest('IPv6 tests are disabled')
+ version = self.sendConsoleCommand('showVersion()', IPv6=True)
+ self.assertTrue(version.startswith('dnsdist '))
+
class TestConsoleNotAllowed(DNSDistTest):
_consoleKey = DNSDistTest.generateConsoleKey()
import time
import dns
import dns.message
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
import dnscrypt
class DNSCryptTest(DNSDistTest):
Be careful to change the _providerFingerprint below if you want to regenerate the keys.
"""
- _dnsDistPort = 5340
- _dnsDistPortDNSCrypt = 8443
+ _dnsDistPortDNSCrypt = pickAvailablePort()
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
"""
DNSCrypt: encrypted A query
"""
- client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
name = 'a.dnscrypt.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
response = dns.message.make_response(query)
the padding into account) and check that the response
is truncated.
"""
- client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
name = 'smallquerylargeresponse.dnscrypt.tests.powerdns.com.'
query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096)
response = dns.message.make_response(query)
"""
DNSCrypt: certificate rotation
"""
- client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
client.refreshResolverCertificates()
cert = client.getResolverCertificate()
"""
DNSCrypt: Test DNSQuestion.Protocol over UDP
"""
- client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
name = 'udp.protocols.dnscrypt.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
response = dns.message.make_response(query)
"""
DNSCrypt: Test DNSQuestion.Protocol over TCP
"""
- client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
name = 'tcp.protocols.dnscrypt.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
response = dns.message.make_response(query)
DNSCrypt: encrypted A query served from cache
"""
misses = 0
- client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
name = 'cacheda.dnscrypt.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
response = dns.message.make_response(query)
local last = 0
serial = %d
- function maintenance()
+ function reloadCallback()
local now = os.time()
if ((now - last) > 2) then
serial = serial + 1
last = now
end
end
+ addMaintenanceCallback(reloadCallback)
"""
_config_params = ['_consoleKeyB64', '_consolePort', '_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort', '_resolverCertificateSerial']
"""
DNSCrypt: automatic certificate rotation
"""
- client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
client.refreshResolverCertificates()
cert = client.getResolverCertificate()
#!/usr/bin/env python
+import base64
import dns
import os
import time
import clientsubnetoption
from dnsdistdohtests import DNSDistDOHTest
+from dnsdisttests import pickAvailablePort
import pycurl
from io import BytesIO
-class TestDOH(DNSDistDOHTest):
-
+class DOHTests(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_customResponseHeader1 = 'access-control-allow-origin: *'
_customResponseHeader2 = 'user-agent: derp'
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
- newServer{address="127.0.0.1:%s"}
-
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/", "/coffee", "/PowerDNS", "/PowerDNS2", "/PowerDNS-999" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true})
- dohFE = getDOHFrontend(0)
- dohFE:setResponsesMap({newDOHResponseMapEntry('^/coffee$', 418, 'C0FFEE', {['FoO']='bar'})})
+ newServer{address="127.0.0.1:%d"}
addAction("drop.doh.tests.powerdns.com.", DropAction())
addAction("refused.doh.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
addAction(HTTPPathRegexRule("^/PowerDNS-[0-9]"), SpoofAction("6.7.8.9"))
addAction("http-status-action.doh.tests.powerdns.com.", HTTPStatusAction(200, "Plaintext answer", "text/plain"))
addAction("http-status-action-redirect.doh.tests.powerdns.com.", HTTPStatusAction(307, "https://doh.powerdns.org"))
+ addAction("no-backend.doh.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
function dohHandler(dq)
if dq:getHTTPScheme() == 'https' and dq:getHTTPHost() == '%s:%d' and dq:getHTTPPath() == '/' and dq:getHTTPQueryString() == '' then
return DNSAction.None
end
addAction("http-lua.doh.tests.powerdns.com.", LuaAction(dohHandler))
+
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/", "/coffee", "/PowerDNS", "/PowerDNS2", "/PowerDNS-999" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
+ dohFE = getDOHFrontend(0)
+ dohFE:setResponsesMap({newDOHResponseMapEntry('^/coffee$', 418, 'C0FFEE', {['FoO']='bar'})})
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_serverName', '_dohServerPort']
+ _config_params = ['_testServerPort', '_serverName', '_dohServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
+ _verboseMode = True
def testDOHSimple(self):
"""
(_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False)
self.assertEqual(receivedResponse, expectedResponse)
+ def testDOHWithoutQuery(self):
+ """
+ DOH: Empty GET query
+ """
+ name = 'empty-get.doh.tests.powerdns.com.'
+ url = self._dohBaseURL
+ conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.CAINFO, self._caCert)
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ self.assertEqual(rcode, 400)
+
+ def testDOHZeroQDCount(self):
+ """
+ DOH: qdcount == 0
+ """
+ if self._dohLibrary == 'h2o':
+ raise unittest.SkipTest('h2o tries to parse the qname early, so this check will fail')
+ name = 'zero-qdcount.doh.tests.powerdns.com.'
+ query = dns.message.Message()
+ query.id = 0
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.NOTIMP)
+
+ (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ def testDOHShortPath(self):
+ """
+ DOH: Short path in GET query
+ """
+ name = 'short-path-get.doh.tests.powerdns.com.'
+ url = self._dohBaseURL + '/AA'
+ conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.CAINFO, self._caCert)
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ self.assertEqual(rcode, 404)
+
+ def testDOHQueryNoParameter(self):
+ """
+ DOH: No parameter GET query
+ """
+ name = 'no-parameter-get.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ wire = query.to_wire()
+ b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+ url = self._dohBaseURL + '?not-dns=' + b64
+ conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.CAINFO, self._caCert)
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ self.assertEqual(rcode, 400)
+
+ def testDOHQueryInvalidBase64(self):
+ """
+ DOH: Invalid Base64 GET query
+ """
+ name = 'invalid-b64-get.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ wire = query.to_wire()
+ url = self._dohBaseURL + '?dns=' + '_-~~~~-_'
+ conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.CAINFO, self._caCert)
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ self.assertEqual(rcode, 400)
+
+ def testDOHInvalidDNSHeaders(self):
+ """
+ DOH: Invalid DNS headers
+ """
+ name = 'invalid-dns-headers.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.flags |= dns.flags.QR
+ wire = query.to_wire()
+ b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+ url = self._dohBaseURL + '?dns=' + b64
+ conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.CAINFO, self._caCert)
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ self.assertEqual(rcode, 400)
+
+ def testDOHQueryInvalidMethod(self):
+ """
+ DOH: Invalid method
+ """
+ if self._dohLibrary == 'h2o':
+ raise unittest.SkipTest('h2o does not check the HTTP method')
+ name = 'invalid-method.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ wire = query.to_wire()
+ b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+ url = self._dohBaseURL + '?dns=' + b64
+ conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.CAINFO, self._caCert)
+ conn.setopt(pycurl.CUSTOMREQUEST, 'PATCH')
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ self.assertEqual(rcode, 400)
+
+ def testDOHQueryInvalidALPN(self):
+ """
+ DOH: Invalid ALPN
+ """
+ alpn = ['bogus-alpn']
+ conn = self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn)
+ try:
+ conn.send('AAAA')
+ response = conn.recv(65535)
+ self.assertFalse(response)
+ except:
+ pass
+
+ def testDOHHTTP1(self):
+ """
+ DOH: HTTP/1.1
+ """
+ if self._dohLibrary == 'h2o':
+ raise unittest.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2')
+ name = 'http11.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ wire = query.to_wire()
+ b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+ url = self._dohBaseURL + '?dns=' + b64
+ conn = pycurl.Curl()
+ conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_1)
+ conn.setopt(pycurl.HTTPHEADER, ["Content-type: application/dns-message",
+ "Accept: application/dns-message"])
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.CAINFO, self._caCert)
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ self.assertEqual(rcode, 400)
+ self.assertEqual(data, b'<html><body>This server implements RFC 8484 - DNS Queries over HTTP, and requires HTTP/2 in accordance with section 5.2 of the RFC.</body></html>\r\n')
+
+ def testDOHHTTP1NotSelectedOverH2(self):
+ """
+ DOH: Check that HTTP/1.1 is not selected over H2 when offered in the wrong order by the client
+ """
+ if self._dohLibrary == 'h2o':
+ raise unittest.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2')
+ alpn = ['http/1.1', 'h2']
+ conn = self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn)
+ if not hasattr(conn, 'selected_alpn_protocol'):
+ raise unittest.SkipTest('Unable to check the selected ALPN, Python version is too old to support selected_alpn_protocol')
+ self.assertEqual(conn.selected_alpn_protocol(), 'h2')
+
def testDOHInvalid(self):
"""
- DOH: Invalid query
+ DOH: Invalid DNS query
"""
name = 'invalid.doh.tests.powerdns.com.'
invalidQuery = dns.message.make_query(name, 'A', 'IN', use_edns=False)
self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
self.assertEqual(response, receivedResponse)
- def testDOHWithoutQuery(self):
+ def testDOHInvalidHeaderName(self):
"""
- DOH: Empty GET query
+ DOH: Invalid HTTP header name query
"""
- name = 'empty-get.doh.tests.powerdns.com.'
- url = self._dohBaseURL
- conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+ name = 'invalid-header-name.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ # this header is invalid, see rfc9113 section 8.2.1. Field Validity
+ customHeaders = ['{}: test']
+ try:
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, customHeaders=customHeaders)
+ self.assertFalse(receivedQuery)
+ self.assertFalse(receivedResponse)
+ except pycurl.error:
+ pass
+
+ def testDOHNoBackend(self):
+ """
+ DOH: No backend
+ """
+ if self._dohLibrary == 'h2o':
+ raise unittest.SkipTest('h2o does not check the HTTP method')
+ name = 'no-backend.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ wire = query.to_wire()
+ b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+ url = self._dohBaseURL + '?dns=' + b64
+ conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2)
conn.setopt(pycurl.URL, url)
conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.CAINFO, self._caCert)
data = conn.perform_rb()
rcode = conn.getinfo(pycurl.RESPONSE_CODE)
- self.assertEqual(rcode, 400)
+ self.assertEqual(rcode, 403)
def testDOHEmptyPOST(self):
"""
self.assertIn('foo: bar', headers)
self.assertNotIn(self._customResponseHeader2, headers)
-class TestDOHSubPaths(DNSDistDOHTest):
+class TestDoHNGHTTP2(DOHTests, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDoHH2O(DOHTests, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+class DOHSubPathsTests(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
addAction(AllRule(), SpoofAction("3.4.5.6"))
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false})
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false, library='%s'})
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
def testSubPath(self):
"""
# this path is not in the URLs map and should lead to a 404
(_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + "NotPowerDNS", query, caFile=self._caCert, useQueue=False, rawResponse=True)
self.assertTrue(receivedResponse)
- self.assertEqual(receivedResponse, b'not found')
+ self.assertIn(receivedResponse, [b'there is no endpoint configured for this path', b'not found'])
self.assertEqual(self._rcode, 404)
# this path is below one in the URLs map and exactPathMatching is false, so we should be good
(_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + 'PowerDNS/something', caFile=self._caCert, query=query, response=None, useQueue=False)
self.assertEqual(receivedResponse, expectedResponse)
-class TestDOHAddingECS(DNSDistDOHTest):
+class TestDoHSubPathsNGHTTP2(DOHSubPathsTests, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDoHSubPathsH2O(DOHSubPathsTests, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+
+class DOHAddingECSTests(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s", useClientSubnet=true}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
setECSOverride(true)
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
def testDOHSimple(self):
"""
self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
self.checkResponseEDNSWithECS(response, receivedResponse)
-class TestDOHOverHTTP(DNSDistDOHTest):
+class TestDoHAddingECSNGHTTP2(DOHAddingECSTests, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDoHAddingECSH2O(DOHAddingECSTests, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
- _dohServerPort = 8480
+class DOHOverHTTP(object):
+ _dohServerPort = pickAvailablePort()
_serverName = 'tls.tests.dnsdist.org'
_dohBaseURL = ("http://%s:%d/dns-query" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s")
+ addDOHLocal("127.0.0.1:%s", nil, nil, '/dns-query', {library='%s'})
"""
- _config_params = ['_testServerPort', '_dohServerPort']
- _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:8480, running in DNS over HTTP mode instead of DNS over HTTPS
-Configuration 'configs/dnsdist_TestDOHOverHTTP.conf' OK!
-"""
+ _config_params = ['_testServerPort', '_dohServerPort', '_dohLibrary']
def testDOHSimple(self):
"""
self.assertEqual(response, receivedResponse)
self.checkResponseNoEDNS(response, receivedResponse)
-class TestDOHWithCache(DNSDistDOHTest):
+class TestDOHOverHTTPNGHTTP2(DOHOverHTTP, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+ _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
+Configuration 'configs/dnsdist_TestDOHOverHTTPNGHTTP2.conf' OK!
+""" % (DOHOverHTTP._dohServerPort)
+
+class TestDOHOverHTTPH2O(DOHOverHTTP, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+ _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
+Configuration 'configs/dnsdist_TestDOHOverHTTPH2O.conf' OK!
+""" % (DOHOverHTTP._dohServerPort)
+
+class DOHWithCache(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s")
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", '/dns-query', {library='%s'})
pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
getPool(""):setCache(pc)
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
def testDOHCacheLargeAnswer(self):
"""
(_, receivedResponse) = self.sendUDPQuery(expectedQuery, response=None, useQueue=False)
self.assertEqual(response, receivedResponse)
-class TestDOHWithoutCacheControl(DNSDistDOHTest):
+class TestDOHWithCacheNGHTTP2(DOHWithCache, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+ _verboseMode = True
+
+class TestDOHWithCacheH2O(DOHWithCache, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+
+class DOHWithoutCacheControl(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false})
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false, library='%s'})
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
def testDOHSimple(self):
"""
self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
self.assertEqual(response, receivedResponse)
-class TestDOHFFI(DNSDistDOHTest):
+class TestDOHWithoutCacheControlNGHTTP2(DOHWithoutCacheControl, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDOHWithoutCacheControlH2O(DOHWithoutCacheControl, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+class DOHFFI(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_customResponseHeader1 = 'access-control-allow-origin: *'
_customResponseHeader2 = 'user-agent: derp'
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true})
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
local ffi = require("ffi")
end
addAction("http-lua-ffi.doh.tests.powerdns.com.", LuaFFIAction(dohHandler))
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_serverName', '_dohServerPort']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_serverName', '_dohServerPort']
def testHTTPLuaFFIResponse(self):
"""
self.assertEqual(self._rcode, 200)
self.assertTrue('content-type: text/plain' in self._response_headers.decode())
-class TestDOHForwardedFor(DNSDistDOHTest):
+class TestDOHFFINGHTTP2(DOHFFI, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+class TestDOHFFIH2O(DOHFFI, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+
+class DOHForwardedFor(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
setACL('192.0.2.1/32')
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true})
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true, library='%s'})
+ -- Set a maximum number of TCP connections per client, to exercise
+ -- that code along with X-Forwarded-For support
+ setMaxTCPConnectionsPerClient(2)
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
def testDOHAllowedForwarded(self):
"""
(receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=False, rawResponse=True, customHeaders=['x-forwarded-for: 127.0.0.1:42, 127.0.0.1'])
self.assertEqual(self._rcode, 403)
- self.assertEqual(receivedResponse, b'dns query not allowed because of ACL')
+ self.assertEqual(receivedResponse, b'DoH query not allowed because of ACL')
+
+class TestDOHForwardedForNGHTTP2(DOHForwardedFor, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDOHForwardedForH2O(DOHForwardedFor, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
-class TestDOHForwardedForNoTrusted(DNSDistDOHTest):
+class DOHForwardedForNoTrusted(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
setACL('192.0.2.1/32')
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {earlyACLDrop=true, library='%s'})
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
def testDOHForwardedUntrusted(self):
"""
'127.0.0.1')
response.answer.append(rrset)
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=False, rawResponse=True, customHeaders=['x-forwarded-for: 192.0.2.1:4200'])
+ dropped = False
+ try:
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=False, rawResponse=True, customHeaders=['x-forwarded-for: 192.0.2.1:4200'])
+ self.assertEqual(self._rcode, 403)
+ self.assertEqual(receivedResponse, b'DoH query not allowed because of ACL')
+ except pycurl.error as e:
+ dropped = True
- self.assertEqual(self._rcode, 403)
- self.assertEqual(receivedResponse, b'dns query not allowed because of ACL')
+ self.assertTrue(dropped)
+
+class TestDOHForwardedForNoTrustedNGHTTP2(DOHForwardedForNoTrusted, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
-class TestDOHFrontendLimits(DNSDistDOHTest):
+class TestDOHForwardedForNoTrustedH2O(DOHForwardedForNoTrusted, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+
+class DOHFrontendLimits(object):
# this test suite uses a different responder port
# because it uses a different health check configuration
- _testServerPort = 5395
+ _testServerPort = pickAvailablePort()
_answerUnexpected = True
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
-
_skipListeningOnCL = True
_maxTCPConnsPerDOHFrontend = 5
_config_template = """
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d })
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d, library='%s' })
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend', '_dohLibrary']
_alternateListeningAddr = '127.0.0.1'
_alternateListeningPort = _dohServerPort
for idx in range(self._maxTCPConnsPerDOHFrontend + 1):
try:
- conns.append(self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert))
+ alpn = []
+ if self._dohLibrary != 'h2o':
+ alpn.append('h2')
+ conns.append(self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn))
except:
conns.append(None)
self.assertEqual(count, self._maxTCPConnsPerDOHFrontend)
self.assertEqual(failed, 1)
-class TestProtocols(DNSDistDOHTest):
+class TestDOHFrontendLimitsNGHTTP2(DOHFrontendLimits, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDOHFrontendLimitsH2O(DOHFrontendLimits, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+
+class Protocols(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_customResponseHeader1 = 'access-control-allow-origin: *'
_customResponseHeader2 = 'user-agent: derp'
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
addAction("protocols.doh.tests.powerdns.com.", LuaAction(checkDOH))
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
def testProtocolDOH(self):
"""
self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
self.assertEqual(response, receivedResponse)
-class TestDOHWithPKCS12Cert(DNSDistDOHTest):
+class TestProtocolsNGHTTP2(Protocols, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestProtocolsH2O(Protocols, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+
+class DOHWithPKCS12Cert(object):
_serverCert = 'server.p12'
_pkcs12Password = 'passw0rd'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s"}
cert=newTLSCertificate("%s", {password="%s"})
- addDOHLocal("127.0.0.1:%s", cert, "", { "/" })
+ addDOHLocal("127.0.0.1:%s", cert, "", { "/" }, {library='%s'})
"""
- _config_params = ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort']
+ _config_params = ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort', '_dohLibrary']
- def testProtocolDOH(self):
+ def testPKCS12DOH(self):
"""
DoH: Test Simple DOH Query with a password protected PKCS12 file configured
"""
receivedQuery.id = expectedQuery.id
self.assertEqual(expectedQuery, receivedQuery)
-class TestDOHForwardedToTCPOnly(DNSDistDOHTest):
+class TestDOHWithPKCS12CertNGHTTP2(DOHWithPKCS12Cert, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDOHWithPKCS12CertH2O(DOHWithPKCS12Cert, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+
+class DOHForwardedToTCPOnly(object):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
newServer{address="127.0.0.1:%s", tcpOnly=true}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
def testDOHTCPOnly(self):
"""
self.assertEqual(receivedQuery, query)
self.assertEqual(receivedResponse, response)
-class TestDOHLimits(DNSDistDOHTest):
+class TestDOHForwardedToTCPOnlyNGHTTP2(DOHForwardedToTCPOnly, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDOHForwardedToTCPOnlyH2O(DOHForwardedToTCPOnly, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
+
+class DOHLimits(object):
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_serverKey = 'server.key'
_serverCert = 'server.chain'
_maxTCPConnsPerClient = 3
_config_template = """
- newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
- setMaxTCPConnectionsPerClient(%s)
+ newServer{address="127.0.0.1:%d"}
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {library='%s'})
+ setMaxTCPConnectionsPerClient(%d)
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerClient']
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_maxTCPConnsPerClient']
def testConnsPerClient(self):
"""
self.assertEqual(count, self._maxTCPConnsPerClient)
self.assertEqual(failed, 1)
+
+class TestDOHLimitsNGHTTP2(DOHLimits, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDOHLimitsH2O(DOHLimits, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
--- /dev/null
+#!/usr/bin/env python
+import dns
+import clientsubnetoption
+
+from dnsdisttests import DNSDistTest
+from dnsdisttests import pickAvailablePort
+from quictests import QUICTests, QUICWithCacheTests, QUICACLTests
+import doh3client
+
+class TestDOH3(QUICTests, DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+
+ addAction("drop.doq.tests.powerdns.com.", DropAction())
+ addAction("refused.doq.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
+ addAction("spoof.doq.tests.powerdns.com.", SpoofAction("1.2.3.4"))
+ addAction("no-backend.doq.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
+
+ addDOH3Local("127.0.0.1:%d", "%s", "%s", {keyLogFile='/tmp/keys'})
+ """
+ _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+ _verboseMode = True
+
+ def getQUICConnection(self):
+ return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+ def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+ return self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOH3ACL(QUICACLTests, DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+
+ setACL("192.0.2.1/32")
+ addDOH3Local("127.0.0.1:%d", "%s", "%s", {keyLogFile='/tmp/keys'})
+ """
+ _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+ _verboseMode = True
+
+ def getQUICConnection(self):
+ return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+ def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+ return self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOH3Specifics(DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+
+ addDOH3Local("127.0.0.1:%d", "%s", "%s", {keyLogFile='/tmp/keys'})
+ """
+ _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+ _verboseMode = True
+
+ def testDOH3Post(self):
+ """
+ QUIC: Simple POST query
+ """
+ name = 'simple.post.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ (receivedQuery, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, serverName=self._serverName, post=True)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.assertEqual(receivedResponse, response)
--- /dev/null
+#!/usr/bin/env python
+import base64
+import dns
+import clientsubnetoption
+
+from dnsdisttests import DNSDistTest
+from dnsdisttests import pickAvailablePort
+from doqclient import quic_bogus_query
+from quictests import QUICTests, QUICWithCacheTests, QUICACLTests
+import doqclient
+from doqclient import quic_query
+
+class TestDOQBogus(DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+
+ addDOQLocal("127.0.0.1:%d", "%s", "%s")
+ """
+ _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+ def testDOQBogus(self):
+ """
+ DOQ: Test a bogus query (wrong packed length)
+ """
+ name = 'bogus.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+
+ try:
+ message = quic_bogus_query(query, '127.0.0.1', 2.0, self._doqServerPort, verify=self._caCert, server_hostname=self._serverName)
+ self.assertFalse(True)
+ except doqclient.StreamResetError as e :
+ self.assertEqual(e.error, 2);
+
+class TestDOQ(QUICTests, DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+
+ addAction("drop.doq.tests.powerdns.com.", DropAction())
+ addAction("refused.doq.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
+ addAction("spoof.doq.tests.powerdns.com.", SpoofAction("1.2.3.4"))
+ addAction("no-backend.doq.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
+
+ addDOQLocal("127.0.0.1:%d", "%s", "%s")
+ """
+ _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+ def getQUICConnection(self):
+ return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+ def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+ return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOQWithCache(QUICWithCacheTests, DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+
+ addDOQLocal("127.0.0.1:%d", "%s", "%s")
+
+ pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
+ getPool(""):setCache(pc)
+ """
+ _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+ def getQUICConnection(self):
+ return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+ def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+ return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOQWithACL(QUICACLTests, DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+
+ setACL("192.0.2.1/32")
+ addDOQLocal("127.0.0.1:%d", "%s", "%s")
+ """
+ _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+ def getQUICConnection(self):
+ return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+ def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+ return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOQCertificateReloading(DNSDistTest):
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+ _serverKey = 'server-doq.key'
+ _serverCert = 'server-doq.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%s")
+
+ newServer{address="127.0.0.1:%d"}
+
+ addDOQLocal("127.0.0.1:%d", "%s", "%s")
+ """
+ _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+ @classmethod
+ def setUpClass(cls):
+ cls.generateNewCertificateAndKey('server-doq')
+ cls.startResponders()
+ cls.startDNSDist()
+ cls.setUpSockets()
+
+ def testCertificateReloaded(self):
+ name = 'certificate-reload.doq.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ (_, serial) = quic_query(query, '127.0.0.1', 0.5, self._doqServerPort, verify=self._caCert, server_hostname=self._serverName)
+
+ self.generateNewCertificateAndKey('server-doq')
+ self.sendConsoleCommand("reloadAllCertificates()")
+
+ (_, secondSerial) = quic_query(query, '127.0.0.1', 0.5, self._doqServerPort, verify=self._caCert, server_hostname=self._serverName)
+ # check that the serial is different
+ self.assertNotEqual(serial, secondSerial)
--- /dev/null
+#!/usr/bin/env python
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestDeprecatedMakeRule(DNSDistTest):
+
+ _config_template = """
+ addAction(makeRule("make-rule-suffix.deprecated.tests.powerdns.com."), SpoofAction("192.0.2.1"))
+ addAction("string-suffix.deprecated.tests.powerdns.com.", SpoofAction("192.0.2.2"))
+ addAction({"list-of-string-suffixes.deprecated.tests.powerdns.com."}, SpoofAction("192.0.2.3"))
+
+ newServer{address="127.0.0.1:%d"}
+ """
+
+ def testDeprecatedMakeRule(self):
+ """
+ Deprecated: makeRule
+ """
+ name = 'prefix.make-rule-suffix.deprecated.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(expectedResponse, receivedResponse)
+
+ def testDeprecatedAddActionStringSuffix(self):
+ """
+ Deprecated: addAction string suffix
+ """
+ name = 'another.prefix.string-suffix.deprecated.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.2')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(expectedResponse, receivedResponse)
+
+ def testDeprecatedAddActionListOfStringSuffixes(self):
+ """
+ Deprecated: addAction list of string suffixes
+ """
+ name = 'yet.another.prefix.list-of-string-suffixes.deprecated.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.3')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(expectedResponse, receivedResponse)
import struct
import sys
import time
-from dnsdisttests import DNSDistTest, Queue
+from dnsdisttests import DNSDistTest, Queue, pickAvailablePort
import dns
import dnstap_pb2
class TestDnstapOverRemoteLogger(DNSDistTest):
- _remoteLoggerServerPort = 4243
+ _remoteLoggerServerPort = pickAvailablePort()
_remoteLoggerQueue = Queue()
_remoteLoggerCounter = 0
_config_params = ['_testServerPort', '_remoteLoggerServerPort']
DNSDistTest.startResponders()
cls._remoteLoggerListener = threading.Thread(name='RemoteLogger Listener', target=cls.RemoteLoggerListener, args=[cls._remoteLoggerServerPort])
- cls._remoteLoggerListener.setDaemon(True)
+ cls._remoteLoggerListener.daemon = True
cls._remoteLoggerListener.start()
def getFirstDnstap(self):
DNSDistTest.startResponders()
cls._fstrmLoggerListener = threading.Thread(name='FrameStreamUnixListener', target=cls.FrameStreamUnixListener, args=[cls._fstrmLoggerAddress])
- cls._fstrmLoggerListener.setDaemon(True)
+ cls._fstrmLoggerListener.daemon = True
cls._fstrmLoggerListener.start()
def getFirstDnstap(self):
class TestDnstapOverFrameStreamTcpLogger(DNSDistTest):
- _fstrmLoggerPort = 4000
+ _fstrmLoggerPort = pickAvailablePort()
_fstrmLoggerQueue = Queue()
_fstrmLoggerCounter = 0
_config_params = ['_testServerPort', '_fstrmLoggerPort']
DNSDistTest.startResponders()
cls._fstrmLoggerListener = threading.Thread(name='FrameStreamUnixListener', target=cls.FrameStreamUnixListener, args=[cls._fstrmLoggerPort])
- cls._fstrmLoggerListener.setDaemon(True)
+ cls._fstrmLoggerListener.daemon = True
cls._fstrmLoggerListener.start()
def getFirstDnstap(self):
#!/usr/bin/env python
import base64
-import json
-import requests
import socket
import time
import dns
from dnsdisttests import DNSDistTest
-try:
- range = xrange
-except NameError:
- pass
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
-class DynBlocksTest(DNSDistTest):
-
- _webTimeout = 2.0
- _webServerPort = 8083
- _webServerBasicAuthPassword = 'secret'
- _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
- _webServerAPIKey = 'apisecret'
- _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
-
- def doTestDynBlockViaAPI(self, range, reason, minSeconds, maxSeconds, minBlocks, maxBlocks):
- headers = {'x-api-key': self._webServerAPIKey}
- url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
- r = requests.get(url, headers=headers, timeout=self._webTimeout)
- self.assertTrue(r)
- self.assertEqual(r.status_code, 200)
-
- content = r.json()
- self.assertIsNotNone(content)
- self.assertIn(range, content)
-
- values = content[range]
- for key in ['reason', 'seconds', 'blocks', 'action']:
- self.assertIn(key, values)
-
- self.assertEqual(values['reason'], reason)
- self.assertGreaterEqual(values['seconds'], minSeconds)
- self.assertLessEqual(values['seconds'], maxSeconds)
- self.assertGreaterEqual(values['blocks'], minBlocks)
- self.assertLessEqual(values['blocks'], maxBlocks)
-
- def doTestQRate(self, name, testViaAPI=True):
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
-
- allowed = 0
- sent = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- if testViaAPI:
- self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- # again, over TCP this time
- allowed = 0
- sent = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- def doTestQRateRCode(self, name, rcode):
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
- expectedResponse = dns.message.make_response(query)
- expectedResponse.set_rcode(rcode)
-
- allowed = 0
- sent = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, response)
- allowed = allowed + 1
- else:
- self.assertEqual(receivedResponse, expectedResponse)
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, expectedResponse)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- allowed = 0
- sent = 0
- # again, over TCP this time
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, response)
- allowed = allowed + 1
- else:
- self.assertEqual(receivedResponse, expectedResponse)
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, expectedResponse)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- def doTestResponseByteRate(self, name):
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- response.answer.append(dns.rrset.from_text_list(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4']))
- response.answer.append(dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.AAAA,
- '2001:DB8::1'))
-
- allowed = 0
- sent = 0
-
- print(time.time())
-
- for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- sent = sent + len(response.to_wire())
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
- allowed = allowed + len(response.to_wire())
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
- # and stop right there, otherwise we might
- # wait for so long that the dynblock is gone
- # by the time we finished
- break
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockBytesPerSecond bytes
- print(allowed)
- print(sent)
- print(time.time())
- self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
-
- print(self.sendConsoleCommand("showDynBlocks()"))
- print(self.sendConsoleCommand("grepq(\"\")"))
- print(time.time())
-
- if allowed == sent:
- # wait for the maintenance function to run
- print("Waiting for the maintenance function to run")
- time.sleep(2)
-
- print(self.sendConsoleCommand("showDynBlocks()"))
- print(self.sendConsoleCommand("grepq(\"\")"))
- print(time.time())
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- print(self.sendConsoleCommand("showDynBlocks()"))
- print(self.sendConsoleCommand("grepq(\"\")"))
- print(time.time())
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- print(self.sendConsoleCommand("showDynBlocks()"))
- print(self.sendConsoleCommand("grepq(\"\")"))
- print(time.time())
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- # again, over TCP this time
- allowed = 0
- sent = 0
- for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- sent = sent + len(response.to_wire())
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
- allowed = allowed + len(response.to_wire())
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
- # and stop right there, otherwise we might
- # wait for so long that the dynblock is gone
- # by the time we finished
- break
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockBytesPerSecond bytes
- self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- def doTestRCodeRate(self, name, rcode):
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
- expectedResponse = dns.message.make_response(query)
- expectedResponse.set_rcode(rcode)
-
- # start with normal responses
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should NOT be dropped!
- (_, receivedResponse) = self.sendUDPQuery(query, response)
- self.assertEqual(receivedResponse, response)
-
- # now with rcode!
- sent = 0
- allowed = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- # again, over TCP this time
- # start with normal responses
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should NOT be dropped!
- (_, receivedResponse) = self.sendUDPQuery(query, response)
- self.assertEqual(receivedResponse, response)
-
- # now with rcode!
- sent = 0
- allowed = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount):
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
- expectedResponse = dns.message.make_response(query)
- expectedResponse.set_rcode(rcode)
-
- # start with normal responses
- for _ in range(noerrorcount-1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should NOT be dropped!
- (_, receivedResponse) = self.sendUDPQuery(query, response)
- self.assertEqual(receivedResponse, response)
-
- # now with rcode!
- sent = 0
- allowed = 0
- for _ in range(rcodecount):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
- self.assertGreaterEqual(allowed, rcodecount)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- # again, over TCP this time
- # start with normal responses
- for _ in range(noerrorcount-1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should NOT be dropped!
- (_, receivedResponse) = self.sendUDPQuery(query, response)
- self.assertEqual(receivedResponse, response)
-
- # now with rcode!
- sent = 0
- allowed = 0
- for _ in range(rcodecount):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
- self.assertGreaterEqual(allowed, rcodecount)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
-class TestDynBlockQPS(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_template = """
- function maintenance()
- addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
- end
- newServer{address="127.0.0.1:%s"}
- webserver("127.0.0.1:%s")
- setWebserverConfig({password="%s", apiKey="%s"})
- """
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
-
- def testDynBlocksQRate(self):
- """
- Dyn Blocks: QRate
- """
- name = 'qrate.dynblocks.tests.powerdns.com.'
- self.doTestQRate(name)
-
-class TestDynBlockGroupQPS(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
-
- function maintenance()
- dbr:apply()
- end
- newServer{address="127.0.0.1:%s"}
- webserver("127.0.0.1:%s")
- setWebserverConfig({password="%s", apiKey="%s"})
- """
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
-
- def testDynBlocksQRate(self):
- """
- Dyn Blocks (Group): QRate
- """
- name = 'qrate.group.dynblocks.tests.powerdns.com.'
- self.doTestQRate(name)
-
-
-class TestDynBlockQPSRefused(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- function maintenance()
- addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
- end
- setDynBlocksAction(DNSAction.Refused)
- newServer{address="127.0.0.1:%s"}
- """
-
- def testDynBlocksQRate(self):
- """
- Dyn Blocks: QRate refused
- """
- name = 'qraterefused.dynblocks.tests.powerdns.com.'
- self.doTestQRateRCode(name, dns.rcode.REFUSED)
-
-class TestDynBlockGroupQPSRefused(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
-
- function maintenance()
- dbr:apply()
- end
- setDynBlocksAction(DNSAction.Refused)
- newServer{address="127.0.0.1:%s"}
- """
-
- def testDynBlocksQRate(self):
- """
- Dyn Blocks (Group): QRate refused
- """
- name = 'qraterefused.group.dynblocks.tests.powerdns.com.'
- self.doTestQRateRCode(name, dns.rcode.REFUSED)
-
-class TestDynBlockQPSActionRefused(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- function maintenance()
- addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
- end
- setDynBlocksAction(DNSAction.Drop)
- newServer{address="127.0.0.1:%s"}
- """
-
- def testDynBlocksQRate(self):
- """
- Dyn Blocks: QRate refused (action)
- """
- name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
- self.doTestQRateRCode(name, dns.rcode.REFUSED)
-
-class TestDynBlockQPSActionNXD(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- function maintenance()
- addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain)
- end
- setDynBlocksAction(DNSAction.Drop)
- newServer{address="127.0.0.1:%s"}
- """
-
- def testDynBlocksQRate(self):
- """
- Dyn Blocks: QRate NXD (action)
- """
- name = 'qrateactionnxd.dynblocks.tests.powerdns.com.'
- self.doTestQRateRCode(name, dns.rcode.NXDOMAIN)
-
-class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused)
-
- function maintenance()
- dbr:apply()
- end
- setDynBlocksAction(DNSAction.Drop)
- newServer{address="127.0.0.1:%s"}
- """
-
- def testDynBlocksQRate(self):
- """
- Dyn Blocks (group): QRate refused (action)
- """
- name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
- self.doTestQRateRCode(name, dns.rcode.REFUSED)
-
-class TestDynBlockQPSActionTruncated(DNSDistTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- function maintenance()
- addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
- end
- setDynBlocksAction(DNSAction.Drop)
- newServer{address="127.0.0.1:%s"}
- """
-
- def testDynBlocksQRate(self):
- """
- Dyn Blocks: QRate truncated (action)
- """
- name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- # dnsdist sets RA = RD for TC responses
- query.flags &= ~dns.flags.RD
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
- truncatedResponse = dns.message.make_response(query)
- truncatedResponse.flags |= dns.flags.TC
-
- allowed = 0
- sent = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, response)
- allowed = allowed + 1
- else:
- self.assertEqual(receivedResponse, truncatedResponse)
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we might be already truncated, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, truncatedResponse)
-
- # check over TCP, which should not be truncated
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, response)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
-
- allowed = 0
- sent = 0
- # again, over TCP this time, we should never get truncated!
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- sent = sent + 1
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, response)
- receivedQuery.id = query.id
- allowed = allowed + 1
-
- self.assertEqual(allowed, sent)
-
-class TestDynBlockServFails(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- function maintenance()
- addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
- end
- newServer{address="127.0.0.1:%s"}
- """
-
- def testDynBlocksServFailRate(self):
- """
- Dyn Blocks: Server Failure Rate
- """
- name = 'servfailrate.dynblocks.tests.powerdns.com.'
- self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
-
-class TestDynBlockServFailsCached(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false})
- getPool(""):setCache(pc)
- function maintenance()
- addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
- end
- newServer{address="127.0.0.1:%s"}
- """
-
- def testDynBlocksServFailRateCached(self):
- """
- Dyn Blocks: Make sure cache hit responses also gets inserted into rings
- """
- name = 'servfailrate.dynblocks.tests.powerdns.com.'
- rcode = dns.rcode.SERVFAIL
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
- expectedResponse = dns.message.make_response(query)
- expectedResponse.set_rcode(rcode)
-
-
- for method in ("sendUDPQuery", "sendTCPQuery"):
- print(method, "()")
- sender = getattr(self, method)
-
- # fill the cache
- (receivedQuery, receivedResponse) = sender(query, expectedResponse)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(expectedResponse, receivedResponse)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should NOT be dropped!
- (_, receivedResponse) = sender(query, response=None)
- self.assertEqual(receivedResponse, expectedResponse)
-
- # now with rcode!
- sent = 0
- allowed = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (_, receivedResponse) = sender(query, expectedResponse)
- sent = sent + 1
- self.assertEqual(expectedResponse, receivedResponse)
- allowed = allowed + 1
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = sender(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- # wait until we are not blocked anymore
- time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
- # this one should succeed
- (receivedQuery, receivedResponse) = sender(query, response=None)
- self.assertEqual(expectedResponse, receivedResponse)
-
-class TestDynBlockAllowlist(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- allowlisted = false
- function maintenance()
- toBlock = exceedQRate(%d, %d)
- for addr, count in pairs(toBlock) do
- if tostring(addr) == "127.0.0.1" then
- allowlisted = true
- toBlock[addr] = nil
- end
- end
- addDynBlocks(toBlock, "Exceeded query rate", %d)
- end
-
- function spoofrule(dq)
- if (allowlisted)
- then
- return DNSAction.Spoof, "192.0.2.42"
- else
- return DNSAction.None, ""
- end
- end
- addAction("allowlisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
-
- newServer{address="127.0.0.1:%s"}
- """
-
- def testAllowlisted(self):
- """
- Dyn Blocks: Allowlisted from the dynamic blocks
- """
- name = 'allowlisted.dynblocks.tests.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
-
- allowed = 0
- sent = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we should not have been blocked
- self.assertEqual(allowed, sent)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should still not be blocked
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, receivedResponse)
-
- # check that we would have been blocked without the allowlisting
- name = 'allowlisted-test.dynblocks.tests.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- # dnsdist set RA = RD for spoofed responses
- query.flags &= ~dns.flags.RD
- expectedResponse = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.42')
- expectedResponse.answer.append(rrset)
- (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, expectedResponse)
-
-class TestDynBlockGroupServFails(DynBlocksTest):
+class TestDynBlockQPS(DynBlocksTest):
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
_config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setRCodeRate(DNSRCode.SERVFAIL, %d, %d, "Exceeded query rate", %d)
-
function maintenance()
- dbr:apply()
+ addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
end
-
newServer{address="127.0.0.1:%s"}
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({password="%s", apiKey="%s"})
"""
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
- def testDynBlocksServFailRate(self):
+ def testDynBlocksQRate(self):
"""
- Dyn Blocks (group): Server Failure Rate
+ Dyn Blocks: QRate
"""
- name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
- self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
+ name = 'qrate.dynblocks.tests.powerdns.com.'
+ self.doTestQRate(name)
-class TestDynBlockGroupServFailsRatio(DynBlocksTest):
+class TestDynBlockQPSRefused(DynBlocksTest):
- # we need this period to be quite long because we request the valid
- # queries to be still looked at to reach the 20 queries count!
- _dynBlockPeriod = 6
- _dynBlockDuration = 5
- _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
_config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20)
-
function maintenance()
- dbr:apply()
+ addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
end
-
+ setDynBlocksAction(DNSAction.Refused)
newServer{address="127.0.0.1:%s"}
"""
- def testDynBlocksServFailRatio(self):
+ def testDynBlocksQRate(self):
"""
- Dyn Blocks (group): Server Failure Ratio
+ Dyn Blocks: QRate refused
"""
- name = 'servfailratio.group.dynblocks.tests.powerdns.com.'
- self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10)
+ name = 'qraterefused.dynblocks.tests.powerdns.com.'
+ self.doTestQRateRCode(name, dns.rcode.REFUSED)
-class TestDynBlockResponseBytes(DynBlocksTest):
+class TestDynBlockQPSActionRefused(DynBlocksTest):
- _dynBlockBytesPerSecond = 200
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _consoleKey = DNSDistTest.generateConsoleKey()
- _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
_config_template = """
- setKey("%s")
- controlSocket("127.0.0.1:%s")
function maintenance()
- addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
+ addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
end
+ setDynBlocksAction(DNSAction.Drop)
newServer{address="127.0.0.1:%s"}
"""
- def testDynBlocksResponseByteRate(self):
+ def testDynBlocksQRate(self):
"""
- Dyn Blocks: Response Byte Rate
+ Dyn Blocks: QRate refused (action)
"""
- name = 'responsebyterate.dynblocks.tests.powerdns.com.'
- self.doTestResponseByteRate(name)
+ name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
+ self.doTestQRateRCode(name, dns.rcode.REFUSED)
-class TestDynBlockGroupResponseBytes(DynBlocksTest):
+class TestDynBlockQPSActionNXD(DynBlocksTest):
- _dynBlockBytesPerSecond = 200
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _consoleKey = DNSDistTest.generateConsoleKey()
- _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
_config_template = """
- setKey("%s")
- controlSocket("127.0.0.1:%s")
- local dbr = dynBlockRulesGroup()
- dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
-
function maintenance()
- dbr:apply()
+ addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain)
end
-
+ setDynBlocksAction(DNSAction.Drop)
newServer{address="127.0.0.1:%s"}
"""
- def testDynBlocksResponseByteRate(self):
+ def testDynBlocksQRate(self):
"""
- Dyn Blocks (group) : Response Byte Rate
+ Dyn Blocks: QRate NXD (action)
"""
- name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
- self.doTestResponseByteRate(name)
+ name = 'qrateactionnxd.dynblocks.tests.powerdns.com.'
+ self.doTestQRateRCode(name, dns.rcode.NXDOMAIN)
-class TestDynBlockGroupExcluded(DynBlocksTest):
+class TestDynBlockQPSActionTruncated(DNSDistTest):
_dynBlockQPS = 10
_dynBlockPeriod = 2
- _dynBlockDuration = 5
+ # this needs to be greater than maintenanceWaitTime
+ _dynBlockDuration = _maintenanceWaitTime + 1
_config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
_config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
- dbr:excludeRange("127.0.0.1/32")
-
function maintenance()
- dbr:apply()
+ addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
end
-
+ setDynBlocksAction(DNSAction.Drop)
newServer{address="127.0.0.1:%s"}
"""
- def testExcluded(self):
+ def testDynBlocksQRate(self):
"""
- Dyn Blocks (group) : Excluded from the dynamic block rules
+ Dyn Blocks: QRate truncated (action)
"""
- name = 'excluded.group.dynblocks.tests.powerdns.com.'
+ name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
60,
dns.rdatatype.A,
'192.0.2.1')
response.answer.append(rrset)
+ truncatedResponse = dns.message.make_response(query)
+ truncatedResponse.flags |= dns.flags.TC
allowed = 0
sent = 0
if receivedQuery:
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
+ self.assertEqual(receivedResponse, response)
allowed = allowed + 1
else:
+ self.assertEqual(receivedResponse, truncatedResponse)
# the query has not reached the responder,
# let's clear the response queue
self.clearToResponderQueue()
- # we should not have been blocked
- self.assertEqual(allowed, sent)
-
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should still not be blocked
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, receivedResponse)
-
-class TestDynBlockGroupExcludedViaNMG(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- _config_template = """
- local nmg = newNMG()
- nmg:addMask("127.0.0.1/32")
-
- local dbr = dynBlockRulesGroup()
- dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
- dbr:excludeRange(nmg)
-
- function maintenance()
- dbr:apply()
- end
+ # we might be already truncated, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
- newServer{address="127.0.0.1:%s"}
- """
+ if allowed == sent:
+ waitForMaintenanceToRun()
- def testExcluded(self):
- """
- Dyn Blocks (group) : Excluded (via NMG) from the dynamic block rules
- """
- name = 'excluded-nmg.group.dynblocks.tests.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
+ # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, truncatedResponse)
- allowed = 0
- sent = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
+ # check over TCP, which should not be truncated
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- # we should not have been blocked
- self.assertEqual(allowed, sent)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, response)
- # wait for the maintenance function to run
- time.sleep(2)
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
- # we should still not be blocked
+ # this one should succeed
(receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, receivedResponse)
-
-class TestDynBlockGroupNoOp(DynBlocksTest):
-
- _dynBlockQPS = 10
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
-
- function maintenance()
- dbr:apply()
- end
-
- newServer{address="127.0.0.1:%s"}
- webserver("127.0.0.1:%s")
- setWebserverConfig({password="%s", apiKey="%s"})
- """
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
-
- def testNoOp(self):
- """
- Dyn Blocks (group) : NoOp
- """
- name = 'noop.group.dynblocks.tests.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
+ self.assertEqual(response, receivedResponse)
allowed = 0
sent = 0
+ # again, over TCP this time, we should never get truncated!
for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, response)
+ receivedQuery.id = query.id
+ allowed = allowed + 1
- # a dynamic rule should have been inserted, but the queries should still go on
self.assertEqual(allowed, sent)
- # wait for the maintenance function to run
- time.sleep(2)
-
- # the rule should still be present, but the queries pass through anyway
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(receivedResponse, receivedResponse)
-
- # check that the rule has been inserted
- self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
-
-class TestDynBlockGroupWarning(DynBlocksTest):
+class TestDynBlockAllowlist(DynBlocksTest):
- _dynBlockWarningQPS = 5
- _dynBlockQPS = 20
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
_config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
-
+ allowlisted = false
function maintenance()
- dbr:apply()
+ toBlock = exceedQRate(%d, %d)
+ for addr, count in pairs(toBlock) do
+ if tostring(addr) == "127.0.0.1" then
+ allowlisted = true
+ toBlock[addr] = nil
+ end
+ end
+ addDynBlocks(toBlock, "Exceeded query rate", %d)
+ end
+
+ function spoofrule(dq)
+ if (allowlisted)
+ then
+ return DNSAction.Spoof, "192.0.2.42"
+ else
+ return DNSAction.None, ""
+ end
end
+ addAction("allowlisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
newServer{address="127.0.0.1:%s"}
- webserver("127.0.0.1:%s")
- setWebserverConfig({password="%s", apiKey="%s"})
"""
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
- def testWarning(self):
+ def testAllowlisted(self):
"""
- Dyn Blocks (group) : Warning
+ Dyn Blocks: Allowlisted from the dynamic blocks
"""
- name = 'warning.group.dynblocks.tests.powerdns.com.'
+ name = 'allowlisted.dynblocks.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
allowed = 0
sent = 0
- for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
(receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
sent = sent + 1
if receivedQuery:
# let's clear the response queue
self.clearToResponderQueue()
- # a dynamic rule should have been inserted, but the queries should
- # still go on because we are still at warning level
+ # we should not have been blocked
self.assertEqual(allowed, sent)
- # wait for the maintenance function to run
- time.sleep(2)
+ waitForMaintenanceToRun()
- # the rule should still be present, but the queries pass through anyway
+ # we should still not be blocked
(receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
self.assertEqual(receivedResponse, receivedResponse)
- # check that the rule has been inserted
- self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
-
- self.doTestQRate(name)
-
-class TestDynBlockGroupPort(DNSDistTest):
-
- _dynBlockQPS = 20
- _dynBlockPeriod = 2
- _dynBlockDuration = 5
- _config_template = """
- local dbr = dynBlockRulesGroup()
- dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop)
- -- take the exact port into account
- dbr:setMasks(32, 128, 16)
-
- function maintenance()
- dbr:apply()
- end
- newServer{address="127.0.0.1:%d"}
- """
- _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-
- def testPort(self):
- """
- Dyn Blocks (group): Exact port matching
- """
- name = 'port.group.dynblocks.tests.powerdns.com.'
+ # check that we would have been blocked without the allowlisting
+ name = 'allowlisted-test.dynblocks.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
60,
dns.rdataclass.IN,
dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
-
- allowed = 0
- sent = 0
- for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- sent = sent + 1
- if receivedQuery:
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
- allowed = allowed + 1
- else:
- # the query has not reached the responder,
- # let's clear the response queue
- self.clearToResponderQueue()
-
- # we might be already blocked, but we should have been able to send
- # at least self._dynBlockQPS queries
- self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
- if allowed == sent:
- # wait for the maintenance function to run
- time.sleep(2)
-
- # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ '192.0.2.42')
+ expectedResponse.answer.append(rrset)
(_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
- self.assertEqual(receivedResponse, None)
-
- # use a new socket, so a new port
- self._toResponderQueue.put(response, True, 1.0)
- newsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- newsock.settimeout(1.0)
- newsock.connect(("127.0.0.1", self._dnsDistPort))
- newsock.send(query.to_wire())
- receivedResponse = newsock.recv(4096)
- if receivedResponse:
- receivedResponse = dns.message.from_wire(receivedResponse)
- receivedQuery = self._fromResponderQueue.get(True, 1.0)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
+ self.assertEqual(receivedResponse, expectedResponse)
--- /dev/null
+#!/usr/bin/env python
+import dns
+import os
+import unittest
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest
+
+class EBPFTest(object):
+ pass
+
+@unittest.skipUnless('ENABLE_SUDO_TESTS' in os.environ, "sudo is not available")
+class TestDynBlockEBPFQPS(DynBlocksTest):
+
+ _config_template = """
+ bpf = newBPFFilter({ipv4MaxItems=10, ipv6MaxItems=10, qnamesMaxItems=10})
+ setDefaultBPFFilter(bpf)
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+ function maintenance()
+ dbr:apply()
+ end
+
+ -- not going to wait 60s!
+ setDynBlocksPurgeInterval(1)
+
+ -- exercise the manual blocking methods
+ bpf:block(newCA("2001:DB8::42"))
+ bpf:blockQName(newDNSName("powerdns.com."), 255)
+ bpf:getStats()
+ bpf:unblock(newCA("2001:DB8::42"))
+ bpf:unblockQName(newDNSName("powerdns.com."), 255)
+
+ newServer{address="127.0.0.1:%s"}
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({password="%s", apiKey="%s"})
+ """
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+ _sudoMode = True
+
+ def testDynBlocksQRate(self):
+ """
+ Dyn Blocks: QRate
+ """
+ name = 'qrate.dynblocks.tests.powerdns.com.'
+ self.doTestQRate(name, ebpf=True)
--- /dev/null
+#!/usr/bin/env python
+import base64
+import socket
+import time
+import dns
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
+
+class TestDynBlockGroupQPS(DynBlocksTest):
+
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+
+ function maintenance()
+ dbr:apply()
+ end
+ newServer{address="127.0.0.1:%s"}
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({password="%s", apiKey="%s"})
+ """
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+
+ def testDynBlocksQRate(self):
+ """
+ Dyn Blocks (Group): QRate
+ """
+ name = 'qrate.group.dynblocks.tests.powerdns.com.'
+ self.doTestQRate(name)
+
+class TestDynBlockGroupQPSRefused(DynBlocksTest):
+
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+
+ function maintenance()
+ dbr:apply()
+ end
+ setDynBlocksAction(DNSAction.Refused)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksQRate(self):
+ """
+ Dyn Blocks (Group): QRate refused
+ """
+ name = 'qraterefused.group.dynblocks.tests.powerdns.com.'
+ self.doTestQRateRCode(name, dns.rcode.REFUSED)
+
+class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
+
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused)
+
+ function maintenance()
+ dbr:apply()
+ end
+ setDynBlocksAction(DNSAction.Drop)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksQRate(self):
+ """
+ Dyn Blocks (group): QRate refused (action)
+ """
+ name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
+ self.doTestQRateRCode(name, dns.rcode.REFUSED)
+
+class TestDynBlockGroupExcluded(DynBlocksTest):
+
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+ dbr:excludeRange("127.0.0.1/32")
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testExcluded(self):
+ """
+ Dyn Blocks (group) : Excluded from the dynamic block rules
+ """
+ name = 'excluded.group.dynblocks.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we should not have been blocked
+ self.assertEqual(allowed, sent)
+
+ waitForMaintenanceToRun()
+
+ # we should still not be blocked
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, receivedResponse)
+
+class TestDynBlockGroupExcludedViaNMG(DynBlocksTest):
+
+ _config_template = """
+ local nmg = newNMG()
+ nmg:addMask("127.0.0.1/32")
+
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+ dbr:excludeRange(nmg)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testExcluded(self):
+ """
+ Dyn Blocks (group) : Excluded (via NMG) from the dynamic block rules
+ """
+ name = 'excluded-nmg.group.dynblocks.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we should not have been blocked
+ self.assertEqual(allowed, sent)
+
+ waitForMaintenanceToRun()
+
+ # we should still not be blocked
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, receivedResponse)
+
+class TestDynBlockGroupNoOp(DynBlocksTest):
+
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({password="%s", apiKey="%s"})
+ """
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+
+ def testNoOp(self):
+ """
+ Dyn Blocks (group) : NoOp
+ """
+ name = 'noop.group.dynblocks.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # a dynamic rule should have been inserted, but the queries should still go on
+ self.assertEqual(allowed, sent)
+
+ waitForMaintenanceToRun()
+
+ # the rule should still be present, but the queries pass through anyway
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, receivedResponse)
+
+ # check that the rule has been inserted
+ self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, 0, sent)
+
+class TestDynBlockGroupWarning(DynBlocksTest):
+
+ _dynBlockWarningQPS = 5
+ _dynBlockQPS = 20
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({password="%s", apiKey="%s"})
+ """
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+
+ def testWarning(self):
+ """
+ Dyn Blocks (group) : Warning
+ """
+ name = 'warning.group.dynblocks.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # a dynamic rule should have been inserted, but the queries should
+ # still go on because we are still at warning level
+ self.assertEqual(allowed, sent)
+
+ waitForMaintenanceToRun()
+
+ # the rule should still be present, but the queries pass through anyway
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, receivedResponse)
+
+ # check that the rule has been inserted
+ self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, 0, sent)
+
+ self.doTestQRate(name)
+
+class TestDynBlockGroupPort(DNSDistTest):
+
+ _dynBlockQPS = 20
+ _dynBlockPeriod = 2
+ # this needs to be greater than maintenanceWaitTime
+ _dynBlockDuration = _maintenanceWaitTime + 1
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop)
+ -- take the exact port into account
+ dbr:setMasks(32, 128, 16)
+
+ function maintenance()
+ dbr:apply()
+ end
+ newServer{address="127.0.0.1:%d"}
+ """
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+
+ def testPort(self):
+ """
+ Dyn Blocks (group): Exact port matching
+ """
+ name = 'port.group.dynblocks.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, None)
+
+ # use a new socket, so a new port
+ self._toResponderQueue.put(response, True, 1.0)
+ newsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ newsock.settimeout(1.0)
+ newsock.connect(("127.0.0.1", self._dnsDistPort))
+ newsock.send(query.to_wire())
+ receivedResponse = newsock.recv(4096)
+ if receivedResponse:
+ receivedResponse = dns.message.from_wire(receivedResponse)
+ receivedQuery = self._fromResponderQueue.get(True, 1.0)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
--- /dev/null
+#!/usr/bin/env python
+import base64
+import socket
+import time
+import dns
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
+
+class TestDynBlockGroupServFailsRatio(DynBlocksTest):
+
+ # we need this period to be quite long because we request the valid
+ # queries to be still looked at to reach the 20 queries count!
+ _dynBlockPeriod = 6
+ _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksServFailRatio(self):
+ """
+ Dyn Blocks (group): Server Failure Ratio
+ """
+ name = 'servfailratio.group.dynblocks.tests.powerdns.com.'
+ self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10)
--- /dev/null
+#!/usr/bin/env python
+import base64
+import socket
+import time
+import dns
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
+
+class TestDynBlockResponseBytes(DynBlocksTest):
+
+ _dynBlockBytesPerSecond = 200
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+ _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+ _config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%s")
+ function maintenance()
+ addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
+ end
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksResponseByteRate(self):
+ """
+ Dyn Blocks: Response Byte Rate
+ """
+ name = 'responsebyterate.dynblocks.tests.powerdns.com.'
+ self.doTestResponseByteRate(name, self._dynBlockBytesPerSecond)
+
+class TestDynBlockGroupResponseBytes(DynBlocksTest):
+
+ _dynBlockBytesPerSecond = 200
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+ _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+ _config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%s")
+ local dbr = dynBlockRulesGroup()
+ dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksResponseByteRate(self):
+ """
+ Dyn Blocks (group) : Response Byte Rate
+ """
+ name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
+ self.doTestResponseByteRate(name, self._dynBlockBytesPerSecond)
--- /dev/null
+#!/usr/bin/env python
+import base64
+import socket
+import time
+import dns
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
+
+class TestDynBlockServFails(DynBlocksTest):
+
+ _config_template = """
+ function maintenance()
+ addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
+ end
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksServFailRate(self):
+ """
+ Dyn Blocks: Server Failure Rate
+ """
+ name = 'servfailrate.dynblocks.tests.powerdns.com.'
+ self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
+
+class TestDynBlockServFailsCached(DynBlocksTest):
+
+ _config_template = """
+ pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false})
+ getPool(""):setCache(pc)
+ function maintenance()
+ addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
+ end
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksServFailRateCached(self):
+ """
+ Dyn Blocks: Make sure cache hit responses also gets inserted into rings
+ """
+ name = 'servfailrate.dynblocks.tests.powerdns.com.'
+ rcode = dns.rcode.SERVFAIL
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(rcode)
+
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ print(method, "()")
+ sender = getattr(self, method)
+
+ # fill the cache
+ (receivedQuery, receivedResponse) = sender(query, expectedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(expectedResponse, receivedResponse)
+
+ waitForMaintenanceToRun()
+
+ # we should NOT be dropped!
+ (_, receivedResponse) = sender(query, response=None)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ # now with rcode!
+ sent = 0
+ allowed = 0
+ for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (_, receivedResponse) = sender(query, expectedResponse)
+ sent = sent + 1
+ self.assertEqual(expectedResponse, receivedResponse)
+ allowed = allowed + 1
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ waitForMaintenanceToRun()
+
+ # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, None)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = sender(query, response=None)
+ self.assertEqual(expectedResponse, receivedResponse)
+
+class TestDynBlockGroupServFails(DynBlocksTest):
+
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setRCodeRate(DNSRCode.SERVFAIL, %d, %d, "Exceeded query rate", %d)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksServFailRate(self):
+ """
+ Dyn Blocks (group): Server Failure Rate
+ """
+ name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
+ self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
--- /dev/null
+#!/usr/bin/env python
+import extendederrors
+import dns
+from dnsdisttests import DNSDistTest, pickAvailablePort
+
+class TestBasics(DNSDistTest):
+
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+ pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
+ getPool(""):setCache(pc)
+
+ local ffi = require("ffi")
+ function ffiAction(dq)
+ local extraText = 'Synthesized from Lua'
+ ffi.C.dnsdist_ffi_dnsquestion_set_extended_dns_error(dq, 29, extraText, #extraText)
+ local str = "192.0.2.2"
+ local buf = ffi.new("char[?]", #str + 1)
+ ffi.copy(buf, str)
+ ffi.C.dnsdist_ffi_dnsquestion_set_result(dq, buf, #str)
+ return DNSAction.Spoof
+ end
+
+ addAction("self-answered.ede.tests.powerdns.com.", SpoofAction("192.0.2.1"))
+ addAction("self-answered-ffi.ede.tests.powerdns.com.", LuaFFIAction(ffiAction))
+ addSelfAnsweredResponseAction("self-answered.ede.tests.powerdns.com.", SetExtendedDNSErrorResponseAction(42, "my self-answered extended error status"))
+ addAction(AllRule(), SetExtendedDNSErrorAction(16, "my extended error status"))
+
+ """
+
+ def testExtendedErrorNoEDNS(self):
+ """
+ EDE: No EDNS
+ """
+ name = 'no-edns.ede.tests.powerdns.com.'
+ # no EDNS
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+
+ response.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.checkResponseNoEDNS(response, receivedResponse)
+
+ def testExtendedErrorBackendResponse(self):
+ """
+ EDE: Backend response
+ """
+ name = 'backend-response.ede.tests.powerdns.com.'
+ ede = extendederrors.ExtendedErrorOption(16, b'my extended error status')
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+
+ backendResponse = dns.message.make_response(query)
+ backendResponse.use_edns(edns=True, payload=4096, options=[])
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+
+ backendResponse.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.use_edns(edns=True, payload=4096, options=[ede])
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, backendResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+ # testing the cache
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+ def testExtendedErrorBackendResponseWithExistingEDE(self):
+ """
+ EDE: Backend response with existing EDE
+ """
+ name = 'backend-response-existing-ede.ede.tests.powerdns.com.'
+ ede = extendederrors.ExtendedErrorOption(16, b'my extended error status')
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+
+ backendResponse = dns.message.make_response(query)
+ backendEDE = extendederrors.ExtendedErrorOption(3, b'Stale answer')
+ backendResponse.use_edns(edns=True, payload=4096, options=[backendEDE])
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+
+ backendResponse.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.use_edns(edns=True, payload=4096, options=[ede])
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, backendResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+ # testing the cache
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+ def testExtendedErrorSelfAnswered(self):
+ """
+ EDE: Self-answered
+ """
+ name = 'self-answered.ede.tests.powerdns.com.'
+ ede = extendederrors.ExtendedErrorOption(42, b'my self-answered extended error status')
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ # dnsdist sets RA = RD for self-generated responses
+ query.flags &= ~dns.flags.RD
+
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.use_edns(edns=True, payload=1232, options=[ede])
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+ def testExtendedErrorLuaFFI(self):
+ """
+ EDE: Self-answered via Lua FFI
+ """
+ name = 'self-answered-ffi.ede.tests.powerdns.com.'
+ ede = extendederrors.ExtendedErrorOption(29, b'Synthesized from Lua')
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ # dnsdist sets RA = RD for self-generated responses
+ query.flags &= ~dns.flags.RD
+
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.use_edns(edns=True, payload=1232, options=[ede])
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.2')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.checkMessageEDNS(expectedResponse, receivedResponse)
setECSSourcePrefixV4(16)
setECSSourcePrefixV6(16)
newServer{address="127.0.0.1:%s", useClientSubnet=true}
- addAction(makeRule("disabled.ecsrules.tests.powerdns.com."), SetDisableECSAction())
+ addAction(SuffixMatchNodeRule("disabled.ecsrules.tests.powerdns.com."), SetDisableECSAction())
function disableECSViaLua(dq)
dq.useECS = false
return DNSAction.None, ""
setECSSourcePrefixV4(24)
setECSSourcePrefixV6(56)
newServer{address="127.0.0.1:%s", useClientSubnet=true}
- addAction(makeRule("overridden.ecsrules.tests.powerdns.com."), SetECSOverrideAction(true))
+ addAction(SuffixMatchNodeRule("overridden.ecsrules.tests.powerdns.com."), SetECSOverrideAction(true))
function overrideECSViaLua(dq)
dq.ecsOverride = true
return DNSAction.None, ""
setECSSourcePrefixV4(24)
setECSSourcePrefixV6(56)
newServer{address="127.0.0.1:%s", useClientSubnet=true}
- addAction(makeRule("overriddenprefixlength.ecsrules.tests.powerdns.com."), SetECSPrefixLengthAction(32, 128))
+ addAction(SuffixMatchNodeRule("overriddenprefixlength.ecsrules.tests.powerdns.com."), SetECSPrefixLengthAction(32, 128))
function overrideECSPrefixLengthViaLua(dq)
dq.ecsPrefixLength = 32
return DNSAction.None, ""
setECSSourcePrefixV4(32)
setECSSourcePrefixV6(128)
newServer{address="127.0.0.1:%s", useClientSubnet=true}
- addAction(makeRule("setecsaction.ecsrules.tests.powerdns.com."), SetECSAction("192.0.2.1/32"))
+ addAction(SuffixMatchNodeRule("setecsaction.ecsrules.tests.powerdns.com."), SetECSAction("192.0.2.1/32"))
"""
def testWithRegularECS(self):
#!/usr/bin/env python
import base64
+import requests
+import ssl
import threading
import time
-import ssl
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class HealthCheckTest(DNSDistTest):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
+ _webTimeout = 2.0
+ _webServerPort = pickAvailablePort()
+ _webServerAPIKey = 'apisecret'
+ _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
+ _config_params = ['_consoleKeyB64', '_consolePort', '_webServerPort', '_webServerAPIKeyHashed', '_testServerPort']
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%d")
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({apiKey="%s"})
newServer{address="127.0.0.1:%d"}
"""
def getBackendStatus(self):
return self.sendConsoleCommand("if getServer(0):isUp() then return 'up' else return 'down' end").strip("\n")
+ def getBackendMetric(self, backendID, metricName):
+ headers = {'x-api-key': self._webServerAPIKey}
+ url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost'
+ r = requests.get(url, headers=headers, timeout=self._webTimeout)
+ self.assertTrue(r)
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue(r.json())
+ content = r.json()
+ self.assertIn('servers', content)
+ servers = content['servers']
+ server = servers[backendID]
+ return int(server[metricName])
+
class TestDefaultHealthCheck(HealthCheckTest):
# this test suite uses a different responder port
# because we need fresh counters
- _testServerPort = 5380
+ _testServerPort = pickAvailablePort()
def testDefault(self):
"""
before = TestDefaultHealthCheck._healthCheckCounter
time.sleep(1.5)
self.assertGreater(TestDefaultHealthCheck._healthCheckCounter, before)
+ self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
self.assertEqual(self.getBackendStatus(), 'up')
self.sendConsoleCommand("getServer(0):setUp()")
time.sleep(1.5)
self.assertGreater(TestDefaultHealthCheck._healthCheckCounter, before)
self.assertEqual(self.getBackendStatus(), 'up')
+ self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
class TestHealthCheckForcedUP(HealthCheckTest):
# this test suite uses a different responder port
# because we need fresh counters
- _testServerPort = 5381
+ _testServerPort = pickAvailablePort()
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%d")
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({apiKey="%s"})
srv = newServer{address="127.0.0.1:%d"}
srv:setUp()
"""
time.sleep(1.5)
self.assertEqual(TestHealthCheckForcedUP._healthCheckCounter, before)
self.assertEqual(self.getBackendStatus(), 'up')
+ self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
class TestHealthCheckForcedDown(HealthCheckTest):
# this test suite uses a different responder port
# because we need fresh counters
- _testServerPort = 5382
+ _testServerPort = pickAvailablePort()
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%d")
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({apiKey="%s"})
srv = newServer{address="127.0.0.1:%d"}
srv:setDown()
"""
before = TestHealthCheckForcedDown._healthCheckCounter
time.sleep(1.5)
self.assertEqual(TestHealthCheckForcedDown._healthCheckCounter, before)
+ self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
class TestHealthCheckCustomName(HealthCheckTest):
# this test suite uses a different responder port
# because it uses a different health check name
- _testServerPort = 5383
+ _testServerPort = pickAvailablePort()
_healthCheckName = 'powerdns.com.'
- _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_healthCheckName']
+ _config_params = ['_consoleKeyB64', '_consolePort', '_webServerPort', '_webServerAPIKeyHashed', '_testServerPort', '_healthCheckName']
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%d")
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({apiKey="%s"})
srv = newServer{address="127.0.0.1:%d", checkName='%s'}
"""
time.sleep(1.5)
self.assertGreater(TestHealthCheckCustomName._healthCheckCounter, before)
self.assertEqual(self.getBackendStatus(), 'up')
+ self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
class TestHealthCheckCustomNameNoAnswer(HealthCheckTest):
# this test suite uses a different responder port
# because it uses a different health check configuration
- _testServerPort = 5384
+ _testServerPort = pickAvailablePort()
_answerUnexpected = False
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%d")
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({apiKey="%s"})
srv = newServer{address="127.0.0.1:%d", checkName='powerdns.com.'}
"""
time.sleep(1.5)
self.assertEqual(TestHealthCheckCustomNameNoAnswer._healthCheckCounter, before)
self.assertEqual(self.getBackendStatus(), 'down')
+ self.assertGreater(self.getBackendMetric(0, 'healthCheckFailures'), 0)
+ self.assertGreater(self.getBackendMetric(0, 'healthCheckFailuresTimeout'), 0)
class TestHealthCheckCustomFunction(HealthCheckTest):
# this test suite uses a different responder port
# because it uses a different health check configuration
- _testServerPort = 5385
+ _testServerPort = pickAvailablePort()
_answerUnexpected = False
_healthCheckName = 'powerdns.com.'
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%d")
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({apiKey="%s"})
function myHealthCheckFunction(qname, qtype, qclass, dh)
dh:setCD(true)
class TestLazyHealthChecks(HealthCheckTest):
_extraStartupSleep = 1
- _do53Port = 10700
- _dotPort = 10701
- _dohPort = 10702
+ _do53Port = pickAvailablePort()
+ _dotPort = pickAvailablePort()
+ _dohPort = pickAvailablePort()
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
tlsContext.load_cert_chain('server.chain', 'server.key')
Do53Responder = threading.Thread(name='Do53 Lazy Responder', target=cls.UDPResponder, args=[cls._do53Port, cls._toResponderQueue, cls._fromResponderQueue, False, cls.Do53Callback])
- Do53Responder.setDaemon(True)
+ Do53Responder.daemon = True
Do53Responder.start()
Do53TCPResponder = threading.Thread(name='Do53 TCP Lazy Responder', target=cls.TCPResponder, args=[cls._do53Port, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.Do53Callback])
- Do53TCPResponder.setDaemon(True)
+ Do53TCPResponder.daemon = True
Do53TCPResponder.start()
DoTResponder = threading.Thread(name='DoT Lazy Responder', target=cls.TCPResponder, args=[cls._dotPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.DoTCallback, tlsContext])
- DoTResponder.setDaemon(True)
+ DoTResponder.daemon = True
DoTResponder.start()
DoHResponder = threading.Thread(name='DoH Lazy Responder', target=cls.DOHResponder, args=[cls._dohPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.DoHCallback, tlsContext])
- DoHResponder.setDaemon(True)
+ DoHResponder.daemon = True
DoHResponder.start()
def testDo53Lazy(self):
#!/usr/bin/env python
import base64
+import dns
import time
import unittest
from dnsdisttests import DNSDistTest
time.sleep(3)
count2 = self.sendConsoleCommand('counter')
self.assertTrue(count2 > count1)
+
+class TestLuaDNSHeaderBindings(DNSDistTest):
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+
+ function checkTCSet(dq)
+ local tc = dq.dh:getTC()
+ if not tc then
+ return DNSAction.Spoof, 'tc-not-set.check-tc.lua-dnsheaders.tests.powerdns.com.'
+ end
+ return DNSAction.Allow
+ end
+
+ addAction('check-tc.lua-dnsheaders.tests.powerdns.com.', LuaAction(checkTCSet))
+ """
+
+ def testLuaGetTC(self):
+ """
+ LuaDNSHeaders: TC
+ """
+ name = 'notset.check-tc.lua-dnsheaders.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.CNAME,
+ 'tc-not-set.check-tc.lua-dnsheaders.tests.powerdns.com.')
+ response.answer.append(rrset)
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertEqual(response, receivedResponse)
+
+ name = 'set.check-tc.lua-dnsheaders.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ query.flags |= dns.flags.TC
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
print(ffi.string(tag))
return false
end
+
+ local raw_tag_buf_size = 255
+ local raw_tag_buf = ffi.new("char [?]", raw_tag_buf_size)
+ local raw_tag_size = ffi.C.dnsdist_ffi_dnsquestion_get_tag_raw(dq, 'raw-tag', raw_tag_buf, raw_tag_buf_size)
+ if ffi.string(raw_tag_buf, raw_tag_size) ~= 'a\0b' then
+ print('invalid raw tag value')
+ print(ffi.string(raw_tag_buf, raw_tag_size))
+ return false
+ end
+
return true
end
return DNSAction.None
end
+ function luaffiactionsettagraw(dq)
+ local value = "a\0b"
+ ffi.C.dnsdist_ffi_dnsquestion_set_tag_raw(dq, 'raw-tag', value, #value)
+ return DNSAction.None
+ end
+
addAction(AllRule(), LuaFFIAction(luaffiactionsettag))
+ addAction(AllRule(), LuaFFIAction(luaffiactionsettagraw))
addAction(LuaFFIRule(luaffirulefunction), LuaFFIAction(luaffiactionfunction))
- -- newServer{address="127.0.0.1:%s"}
+ -- newServer{address="127.0.0.1:%d"}
"""
def testAdvancedLuaFFI(self):
addAction(AllRule(), LuaFFIPerThreadAction(settagfunction))
addAction(LuaFFIPerThreadRule(rulefunction), LuaFFIPerThreadAction(actionfunction))
- -- newServer{address="127.0.0.1:%s"}
+ -- newServer{address="127.0.0.1:%d"}
"""
def testAdvancedLuaPerthreadFFI(self):
sender = getattr(self, method)
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertEqual(receivedResponse, response)
+
+class TestLuaFFIHeader(DNSDistTest):
+
+ _config_template = """
+ local bit = require("bit")
+ local ffi = require("ffi")
+
+ -- check that the AA bit is clear, set the rcode to REFUSED otherwise
+ function checkAAResponseAction(dr)
+ local header_void = ffi.C.dnsdist_ffi_dnsquestion_get_header(dr)
+ local header = ffi.cast("unsigned char *", header_void)
+ -- get AA
+ local aa = bit.band(header[2], bit.lshift(1, 2)) ~= 0
+ if aa then
+ ffi.C.dnsdist_ffi_dnsquestion_set_rcode(dr, DNSRCode.REFUSED)
+ -- prevent subsequent rules from being applied
+ return DNSResponseAction.HeaderModify
+ end
+ return DNSResponseAction.None
+ end
+
+ -- set the AA bit to 1
+ function setAAResponseAction(dr)
+ local header_void = ffi.C.dnsdist_ffi_dnsquestion_get_header(dr)
+ local header = ffi.cast("unsigned char *", header_void)
+ -- set AA=1
+ header[2] = bit.bor(header[2], bit.lshift(1, 2))
+ return DNSResponseAction.None
+ end
+
+ addResponseAction(AllRule(), LuaFFIResponseAction(checkAAResponseAction))
+ addResponseAction(AllRule(), LuaFFIResponseAction(setAAResponseAction))
+ newServer{address="127.0.0.1:%d"}
+ """
+
+ def testLuaFFISetAAHeader(self):
+ """
+ Lua FFI: Set AA=1
+ """
+ name = 'dnsheader-set-aa.luaffi.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ expectedResponse.answer.append(rrset)
+ expectedResponse.flags |= dns.flags.AA
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(expectedResponse, receivedResponse)
+
+ def testLuaFFIGetAAHeader(self):
+ """
+ Lua FFI: check AA=0, return REFUSED otherwise
+ """
+ name = 'dnsheader-get-aa.luaffi.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ response.flags |= dns.flags.AA
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.AA
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(expectedResponse, receivedResponse)
import threading
import unittest
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
-class TestRuleMetrics(DNSDistTest):
+class RuleMetricsTest(object):
_config_template = """
addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
addAction('cache.metrics.tests.powerdns.com', PoolAction('cache'))
"""
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerAPIKey = 'apisecret'
_webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
- _dohServerPort = 8443
+ _tlsServerPort = pickAvailablePort()
+ _dohServerPort = pickAvailablePort()
_dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_params = ['_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey', '_testServerPort', '_webServerPort', '_webServerAPIKeyHashed']
self.assertIn(name, stats)
return int(stats[name])
+ def getPoolMetric(self, poolName, metricName):
+ headers = {'x-api-key': self._webServerAPIKey}
+ url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/pool?name=' + poolName
+ r = requests.get(url, headers=headers, timeout=self._webTimeout)
+ self.assertTrue(r)
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue(r.json())
+ content = r.json()
+ stats = content['stats']
+ self.assertIn(metricName, stats)
+ return int(stats[metricName])
+
def testRCodeIncreaseMetrics(self):
"""
Metrics: Check that metrics are correctly updated for RCodeAction
# self-generated responses should not increase this metric
self.assertEqual(self.getMetric('servfail-responses'), servfailBackendResponses)
+
def testCacheMetrics(self):
"""
Metrics: Check that metrics are correctly updated for cache misses and hits
responsesBefore = self.getMetric('responses')
cacheHitsBefore = self.getMetric('cache-hits')
cacheMissesBefore = self.getMetric('cache-misses')
+ poolCacheHitsBefore = self.getPoolMetric('cache', 'cacheHits')
+ poolCacheMissesBefore = self.getPoolMetric('cache', 'cacheMisses')
sender = getattr(self, method)
# first time, cache miss
self.assertEqual(self.getMetric('responses'), responsesBefore + 2)
self.assertEqual(self.getMetric('cache-hits'), cacheHitsBefore + 1)
self.assertEqual(self.getMetric('cache-misses'), cacheMissesBefore + 1)
+ self.assertEqual(self.getPoolMetric('cache', 'cacheHits'), poolCacheHitsBefore + 1)
+ self.assertEqual(self.getPoolMetric('cache', 'cacheMisses'), poolCacheMissesBefore + 1)
def testServFailMetrics(self):
"""
- Metrics: Check that servfail metrics are correctly updated for cache misses and hits
+ Metrics: Check that servfail metrics are correctly updated for server failures
"""
for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHQueryWrapper"):
self.assertEqual(self.getMetric('frontend-servfail'), frontendBefore + 2)
self.assertEqual(self.getMetric('servfail-responses'), servfailBefore + 1)
self.assertEqual(self.getMetric('rule-servfail'), ruleBefore)
+
+class TestRuleMetricsDefault(RuleMetricsTest, DNSDistTest):
+ None
+
+class TestRuleMetricsRecvmmsg(RuleMetricsTest, DNSDistTest):
+ # test the metrics with recvmmsg/sendmmsg support enabled as well
+ _config_template = RuleMetricsTest._config_template + """
+ setUDPMultipleMessagesVectorSize(10)
+ """
import os
import subprocess
import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class DNSDistOCSPStaplingTest(DNSDistTest):
def getTLSProvider(self):
return self.sendConsoleCommand("getBind(0):getEffectiveTLSProvider()").rstrip()
+ @classmethod
+ def setUpClass(cls):
+ cls.generateNewCertificateAndKey('server-ocsp')
+ cls.startResponders()
+ cls.startDNSDist()
+ cls.setUpSockets()
+
@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
+ _serverKey = 'server-ocsp.key'
+ _serverCert = 'server-ocsp.chain'
_serverName = 'tls.tests.dnsdist.org'
_ocspFile = 'server.ocsp'
_caCert = 'ca.pem'
_caKey = 'ca.key'
- _dohServerPort = 8443
+ _dohWithNGHTTP2ServerPort = pickAvailablePort()
+ _dohWithH2OServerPort = pickAvailablePort()
_config_template = """
- newServer{address="127.0.0.1:%s"}
+ newServer{address="127.0.0.1:%d"}
setKey("%s")
- controlSocket("127.0.0.1:%s")
+ controlSocket("127.0.0.1:%d")
-- generate an OCSP response file for our certificate, valid one day
generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { ocspResponses={"%s"}})
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='nghttp2'})
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='h2o'})
"""
- _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_serverCert', '_caCert', '_caKey', '_ocspFile', '_dohServerPort', '_serverCert', '_serverKey', '_ocspFile']
+ _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_serverCert', '_caCert', '_caKey', '_ocspFile', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_ocspFile', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_ocspFile']
@classmethod
def setUpClass(cls):
if 'SKIP_DOH_TESTS' in os.environ:
raise unittest.SkipTest('DNS over HTTPS tests are disabled')
+ cls.generateNewCertificateAndKey('server-ocsp')
cls.startResponders()
cls.startDNSDist()
cls.setUpSockets()
"""
OCSP Stapling: DOH
"""
- output = self.checkOCSPStaplingStatus('127.0.0.1', self._dohServerPort, self._serverName, self._caCert)
- self.assertIn('OCSP Response Status: successful (0x0)', output)
+ for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]:
+ output = self.checkOCSPStaplingStatus('127.0.0.1', port, self._serverName, self._caCert)
+ self.assertIn('OCSP Response Status: successful (0x0)', output)
- serialNumber = self.getOCSPSerial(output)
- self.assertTrue(serialNumber)
+ serialNumber = self.getOCSPSerial(output)
+ self.assertTrue(serialNumber)
- self.generateNewCertificateAndKey()
- self.sendConsoleCommand("generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)" % (self._serverCert, self._caCert, self._caKey, self._ocspFile))
- self.sendConsoleCommand("reloadAllCertificates()")
+ self.generateNewCertificateAndKey('server-ocsp')
+ self.sendConsoleCommand("generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)" % (self._serverCert, self._caCert, self._caKey, self._ocspFile))
+ self.sendConsoleCommand("reloadAllCertificates()")
- output = self.checkOCSPStaplingStatus('127.0.0.1', self._dohServerPort, self._serverName, self._caCert)
- self.assertIn('OCSP Response Status: successful (0x0)', output)
- serialNumber2 = self.getOCSPSerial(output)
- self.assertTrue(serialNumber2)
- self.assertNotEqual(serialNumber, serialNumber2)
+ output = self.checkOCSPStaplingStatus('127.0.0.1', port, self._serverName, self._caCert)
+ self.assertIn('OCSP Response Status: successful (0x0)', output)
+ serialNumber2 = self.getOCSPSerial(output)
+ self.assertTrue(serialNumber2)
+ self.assertNotEqual(serialNumber, serialNumber2)
class TestBrokenOCSPStaplingDoH(DNSDistOCSPStaplingTest):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
+ _serverKey = 'server-ocsp.key'
+ _serverCert = 'server-ocsp.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
# invalid OCSP file!
_ocspFile = '/dev/null'
- _tlsServerPort = 8443
+ _dohWithNGHTTP2ServerPort = pickAvailablePort()
+ _dohWithH2OServerPort = pickAvailablePort()
_config_template = """
newServer{address="127.0.0.1:%s"}
setKey("%s")
controlSocket("127.0.0.1:%s")
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { ocspResponses={"%s"}})
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='nghttp2'})
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='h2o'})
+
"""
- _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_tlsServerPort', '_serverCert', '_serverKey', '_ocspFile']
+ _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_ocspFile', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_ocspFile']
def testBrokenOCSPStapling(self):
"""
OCSP Stapling: Broken (DoH)
"""
- output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
- self.assertNotIn('OCSP Response Status: successful (0x0)', output)
+ for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]:
+ output = self.checkOCSPStaplingStatus('127.0.0.1', port, self._serverName, self._caCert)
+ self.assertNotIn('OCSP Response Status: successful (0x0)', output)
class TestOCSPStaplingTLSGnuTLS(DNSDistOCSPStaplingTest):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
+ _serverKey = 'server-ocsp.key'
+ _serverCert = 'server-ocsp.chain'
_serverName = 'tls.tests.dnsdist.org'
_ocspFile = 'server.ocsp'
_caCert = 'ca.pem'
_caKey = 'ca.key'
- _tlsServerPort = 8443
+ _tlsServerPort = pickAvailablePort()
_config_template = """
newServer{address="127.0.0.1:%s"}
setKey("%s")
"""
output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
self.assertIn('OCSP Response Status: successful (0x0)', output)
- self.assertEquals(self.getTLSProvider(), "gnutls")
+ self.assertEqual(self.getTLSProvider(), "gnutls")
serialNumber = self.getOCSPSerial(output)
self.assertTrue(serialNumber)
- self.generateNewCertificateAndKey()
+ self.generateNewCertificateAndKey('server-ocsp')
self.sendConsoleCommand("generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)" % (self._serverCert, self._caCert, self._caKey, self._ocspFile))
self.sendConsoleCommand("reloadAllCertificates()")
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
+ _serverKey = 'server-ocsp.key'
+ _serverCert = 'server-ocsp.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
# invalid OCSP file!
_ocspFile = '/dev/null'
- _tlsServerPort = 8443
+ _tlsServerPort = pickAvailablePort()
_config_template = """
newServer{address="127.0.0.1:%s"}
setKey("%s")
"""
output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
self.assertNotIn('OCSP Response Status: successful (0x0)', output)
- self.assertEquals(self.getTLSProvider(), "gnutls")
+ self.assertEqual(self.getTLSProvider(), "gnutls")
class TestOCSPStaplingTLSOpenSSL(DNSDistOCSPStaplingTest):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
+ _serverKey = 'server-ocsp.key'
+ _serverCert = 'server-ocsp.chain'
_serverName = 'tls.tests.dnsdist.org'
_ocspFile = 'server.ocsp'
_caCert = 'ca.pem'
_caKey = 'ca.key'
- _tlsServerPort = 8443
+ _tlsServerPort = pickAvailablePort()
_config_template = """
newServer{address="127.0.0.1:%s"}
setKey("%s")
"""
output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
self.assertIn('OCSP Response Status: successful (0x0)', output)
- self.assertEquals(self.getTLSProvider(), "openssl")
+ self.assertEqual(self.getTLSProvider(), "openssl")
serialNumber = self.getOCSPSerial(output)
self.assertTrue(serialNumber)
- self.generateNewCertificateAndKey()
+ self.generateNewCertificateAndKey('server-ocsp')
self.sendConsoleCommand("generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)" % (self._serverCert, self._caCert, self._caKey, self._ocspFile))
self.sendConsoleCommand("reloadAllCertificates()")
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
+ _serverKey = 'server-ocsp.key'
+ _serverCert = 'server-ocsp.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
# invalid OCSP file!
_ocspFile = '/dev/null'
- _tlsServerPort = 8443
+ _tlsServerPort = pickAvailablePort()
_config_template = """
newServer{address="127.0.0.1:%s"}
setKey("%s")
"""
output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
self.assertNotIn('OCSP Response Status: successful (0x0)', output)
- self.assertEquals(self.getTLSProvider(), "openssl")
+ self.assertEqual(self.getTLSProvider(), "openssl")
import struct
import time
import threading
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class OOORTCPResponder(object):
thread = threading.Thread(name='Connection Handler',
target=self.handleConnection,
args=[conn])
- thread.setDaemon(True)
+ thread.daemon = True
thread.start()
sock.close()
thread = threading.Thread(name='Connection Handler',
target=self.handleConnection,
args=[conn])
- thread.setDaemon(True)
+ thread.daemon = True
thread.start()
sock.close()
-OOORResponderPort = 5371
+OOORResponderPort = pickAvailablePort()
ooorTCPResponder = threading.Thread(name='TCP Responder', target=OOORTCPResponder, args=[OOORResponderPort])
-ooorTCPResponder.setDaemon(True)
+ooorTCPResponder.daemon = True
ooorTCPResponder.start()
-ReverseOOORResponderPort = 5372
+ReverseOOORResponderPort = pickAvailablePort()
ReverseOoorTCPResponder = threading.Thread(name='TCP Responder', target=ReverseOOORTCPResponder, args=[ReverseOOORResponderPort])
-ReverseOoorTCPResponder.setDaemon(True)
+ReverseOoorTCPResponder.daemon = True
ReverseOoorTCPResponder.start()
class TestOOORWithClientNotBackend(DNSDistTest):
import threading
import time
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class OutgoingDOHTests(object):
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerAPIKey = 'apisecret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
class BrokenOutgoingDOHTests(object):
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerAPIKey = 'apisecret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
class OutgoingDOHBrokenResponsesTests(object):
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerAPIKey = 'apisecret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
self.assertEqual(response, receivedResponse)
class TestOutgoingDOHOpenSSL(DNSDistTest, OutgoingDOHTests):
- _tlsBackendPort = 10543
+ _tlsBackendPort = pickAvailablePort()
_tlsProvider = 'openssl'
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
class TestOutgoingDOHGnuTLS(DNSDistTest, OutgoingDOHTests):
- _tlsBackendPort = 10544
+ _tlsBackendPort = pickAvailablePort()
_tlsProvider = 'gnutls'
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
class TestOutgoingDOHOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingDOHTests):
- _tlsBackendPort = 10545
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
class TestOutgoingDOHGnuTLSWrongCertName(DNSDistTest, BrokenOutgoingDOHTests):
- _tlsBackendPort = 10546
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
class TestOutgoingDOHOpenSSLWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests):
- _tlsBackendPort = 10547
+ _tlsBackendPort = pickAvailablePort()
_tlsProvider = 'openssl'
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
class TestOutgoingDOHGnuTLSWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests):
- _tlsBackendPort = 10548
+ _tlsBackendPort = pickAvailablePort()
_tlsProvider = 'gnutls'
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
class TestOutgoingDOHBrokenResponsesOpenSSL(DNSDistTest, OutgoingDOHBrokenResponsesTests):
- _tlsBackendPort = 10549
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
class TestOutgoingDOHBrokenResponsesGnuTLS(DNSDistTest, OutgoingDOHBrokenResponsesTests):
- _tlsBackendPort = 10550
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
class TestOutgoingDOHProxyProtocol(DNSDistTest):
- _tlsBackendPort = 10551
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching DOH woth Proxy Protocol responder..")
cls._DOHResponder = threading.Thread(name='DOH with Proxy Protocol Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, True])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
def testPP(self):
self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True)
class TestOutgoingDOHXForwarded(DNSDistTest):
- _tlsBackendPort = 10560
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching DOH responder..")
cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
- cls._DOHResponder.setDaemon(True)
+ cls._DOHResponder.daemon = True
cls._DOHResponder.start()
def testXForwarded(self):
import threading
import time
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class OutgoingTLSTests(object):
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerAPIKey = 'apisecret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
class BrokenOutgoingTLSTests(object):
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerAPIKey = 'apisecret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
self.checkNoResponderHit()
class TestOutgoingTLSOpenSSL(DNSDistTest, OutgoingTLSTests):
- _tlsBackendPort = 10443
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching TLS responder..")
cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._TLSResponder.setDaemon(True)
+ cls._TLSResponder.daemon = True
cls._TLSResponder.start()
class TestOutgoingTLSGnuTLS(DNSDistTest, OutgoingTLSTests):
- _tlsBackendPort = 10444
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching TLS responder..")
cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._TLSResponder.setDaemon(True)
+ cls._TLSResponder.daemon = True
cls._TLSResponder.start()
class TestOutgoingTLSOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingTLSTests):
- _tlsBackendPort = 10445
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching TLS responder..")
cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._TLSResponder.setDaemon(True)
+ cls._TLSResponder.daemon = True
cls._TLSResponder.start()
class TestOutgoingTLSGnuTLSWrongCertName(DNSDistTest, BrokenOutgoingTLSTests):
- _tlsBackendPort = 10446
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching TLS responder..")
cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._TLSResponder.setDaemon(True)
+ cls._TLSResponder.daemon = True
cls._TLSResponder.start()
class TestOutgoingTLSOpenSSLWrongCertNameButNoCheck(DNSDistTest, OutgoingTLSTests):
- _tlsBackendPort = 10447
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching TLS responder..")
cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._TLSResponder.setDaemon(True)
+ cls._TLSResponder.daemon = True
cls._TLSResponder.start()
class TestOutgoingTLSGnuTLSWrongCertNameButNoCheck(DNSDistTest, OutgoingTLSTests):
- _tlsBackendPort = 10448
+ _tlsBackendPort = pickAvailablePort()
_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
_config_template = """
setMaxTCPClientThreads(1)
print("Launching TLS responder..")
cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
- cls._TLSResponder.setDaemon(True)
+ cls._TLSResponder.daemon = True
cls._TLSResponder.start()
import requests
import subprocess
import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
@unittest.skipIf('SKIP_PROMETHEUS_TESTS' in os.environ, 'Prometheus tests are disabled')
class TestPrometheus(DNSDistTest):
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
_webServerAPIKey = 'apisecret'
import struct
import sys
import time
-from dnsdisttests import DNSDistTest, Queue
+from dnsdisttests import DNSDistTest, pickAvailablePort, Queue
from proxyprotocol import ProxyProtocol
import dns
import dnsmessage_pb2
+import extendederrors
class DNSDistProtobufTest(DNSDistTest):
- _protobufServerPort = 4242
+ _protobufServerPort = pickAvailablePort()
_protobufQueue = Queue()
_protobufServerID = 'dnsdist-server-1'
_protobufCounter = 0
@classmethod
def startResponders(cls):
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._protobufListener = threading.Thread(name='Protobuf Listener', target=cls.ProtobufListener, args=[cls._protobufServerPort])
- cls._protobufListener.setDaemon(True)
+ cls._protobufListener.daemon = True
cls._protobufListener.start()
def getFirstProtobufMessage(self):
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the UDP query
msg = self.getFirstProtobufMessage()
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the TCP query
msg = self.getFirstProtobufMessage()
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
-
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the UDP query
msg = self.getFirstProtobufMessage()
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the TCP query
msg = self.getFirstProtobufMessage()
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the UDP query
msg = self.getFirstProtobufMessage()
# no ':' when the value is empty
self.assertIn('my-empty-key', msg.meta[0].value.stringVal)
+class TestProtobufExtendedDNSErrorTags(DNSDistProtobufTest):
+ _config_params = ['_testServerPort', '_protobufServerPort']
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+ rl = newRemoteLogger('127.0.0.1:%d')
+
+ addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}))
+ addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1', exportExtendedErrorsToMeta='extended-error'}))
+ """
+
+ def testProtobufExtendedError(self):
+ """
+ Protobuf: Extended Error
+ """
+ name = 'extended-error.protobuf.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ ede = extendederrors.ExtendedErrorOption(15, b'Blocked by RPZ!')
+ response.use_edns(edns=True, payload=4096, options=[ede])
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
+
+ # check the protobuf message corresponding to the UDP query
+ msg = self.getFirstProtobufMessage()
+
+ self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+
+ # meta tags
+ self.assertEqual(len(msg.meta), 0)
+
+ # check the protobuf message corresponding to the UDP response
+ msg = self.getFirstProtobufMessage()
+ self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, response)
+
+ # meta tags
+ self.assertEqual(len(msg.meta), 1)
+
+ self.assertEqual(msg.meta[0].key, 'extended-error')
+ self.assertEqual(len(msg.meta[0].value.intVal), 1)
+ self.assertEqual(len(msg.meta[0].value.stringVal), 1)
+ self.assertIn(15, msg.meta[0].value.intVal)
+ self.assertIn('Blocked by RPZ!', msg.meta[0].value.stringVal)
+
class TestProtobufMetaDOH(DNSDistProtobufTest):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
- _dohServerPort = 8443
- _dohBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohServerPort))
- _config_params = ['_testServerPort', '_protobufServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey']
+ _tlsServerPort = pickAvailablePort()
+ _dohWithNGHTTP2ServerPort = pickAvailablePort()
+ _dohWithNGHTTP2BaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithNGHTTP2ServerPort))
+ _dohWithH2OServerPort = pickAvailablePort()
+ _dohWithH2OBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithH2OServerPort))
_config_template = """
newServer{address="127.0.0.1:%d"}
rl = newRemoteLogger('127.0.0.1:%d')
addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true, library='nghttp2' })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true, library='h2o' })
local mytags = {path='doh-path', host='doh-host', ['query-string']='doh-query-string', scheme='doh-scheme', agent='doh-header:user-agent'}
addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}, mytags))
addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1'}, mytags))
"""
+ _config_params = ['_testServerPort', '_protobufServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
def testProtobufMetaDoH(self):
"""
'127.0.0.1')
response.answer.append(rrset)
- for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHQueryWrapper"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"):
sender = getattr(self, method)
(receivedQuery, receivedResponse) = sender(query, response)
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the query
msg = self.getFirstProtobufMessage()
pbMessageType = dnsmessage_pb2.PBDNSMessage.TCP
elif method == "sendDOTQueryWrapper":
pbMessageType = dnsmessage_pb2.PBDNSMessage.DOT
- elif method == "sendDOHQueryWrapper":
+ elif method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
pbMessageType = dnsmessage_pb2.PBDNSMessage.DOH
- print(method)
self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name)
self.assertEqual(len(msg.meta), 5)
tags = {}
tags[entry.key] = entry.value.stringVal[0]
self.assertIn('agent', tags)
- if method == "sendDOHQueryWrapper":
+ if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
self.assertIn('PycURL', tags['agent'])
self.assertIn('host', tags)
- self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohServerPort))
+ if method == "sendDOHWithNGHTTP2QueryWrapper":
+ self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithNGHTTP2ServerPort))
+ elif method == "sendDOHWithH2OQueryWrapper":
+ self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithH2OServerPort))
self.assertIn('path', tags)
self.assertEqual(tags['path'], '/dns-query')
self.assertIn('query-string', tags)
self.assertIn('?dns=', tags['query-string'])
self.assertIn('scheme', tags)
self.assertEqual(tags['scheme'], 'https')
+ self.assertEqual(msg.httpVersion, dnsmessage_pb2.PBDNSMessage.HTTPVersion.HTTP2)
# check the protobuf message corresponding to the response
msg = self.getFirstProtobufMessage()
tags[entry.key] = entry.value.stringVal[0]
self.assertIn('agent', tags)
- if method == "sendDOHQueryWrapper":
+ if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
self.assertIn('PycURL', tags['agent'])
self.assertIn('host', tags)
- self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohServerPort))
+ if method == "sendDOHWithNGHTTP2QueryWrapper":
+ self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithNGHTTP2ServerPort))
+ elif method == "sendDOHWithH2OQueryWrapper":
+ self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithH2OServerPort))
self.assertIn('path', tags)
self.assertEqual(tags['path'], '/dns-query')
self.assertIn('query-string', tags)
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the UDP query
msg = self.getFirstProtobufMessage()
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the UDP query
msg = self.getFirstProtobufMessage()
self.assertEqual(query, receivedQuery)
self.assertEqual(response, receivedResponse)
- # let the protobuf messages the time to get there
- time.sleep(1)
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
# check the protobuf message corresponding to the TCP query
msg = self.getFirstProtobufMessage()
rr = msg.response.rrs[1]
self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, target, 3600)
self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
+
+class TestProtobufQUIC(DNSDistProtobufTest):
+
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _doh3ServerPort = pickAvailablePort()
+ _dohBaseURL = ("https://%s:%d/" % (_serverName, _doh3ServerPort))
+ _config_template = """
+ newServer{address="127.0.0.1:%d"}
+ rl = newRemoteLogger('127.0.0.1:%d')
+
+ addDOQLocal("127.0.0.1:%d", "%s", "%s")
+ addDOH3Local("127.0.0.1:%d", "%s", "%s")
+
+ addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}))
+ """
+ _config_params = ['_testServerPort', '_protobufServerPort', '_doqServerPort', '_serverCert', '_serverKey', '_doh3ServerPort', '_serverCert', '_serverKey']
+
+ def testProtobufMetaDoH(self):
+ """
+ Protobuf: Test logged protocol for QUIC and DOH3
+ """
+ name = 'quic.protobuf.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ for method in ("sendDOQQueryWrapper", "sendDOH3QueryWrapper"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ if self._protobufQueue.empty():
+ # let the protobuf messages the time to get there
+ time.sleep(1)
+
+ # check the protobuf message corresponding to the query
+ msg = self.getFirstProtobufMessage()
+
+ if method == "sendDOQQueryWrapper":
+ pbMessageType = dnsmessage_pb2.PBDNSMessage.DOQ
+ elif method == "sendDOH3QueryWrapper":
+ pbMessageType = dnsmessage_pb2.PBDNSMessage.DOH
+ self.assertEqual(msg.httpVersion, dnsmessage_pb2.PBDNSMessage.HTTPVersion.HTTP3)
+
+ self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name)
#!/usr/bin/env python
-import copy
import dns
+import selectors
import socket
+import ssl
import struct
import sys
import threading
import time
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
from proxyprotocol import ProxyProtocol
+from proxyprotocolutils import ProxyProtocolUDPResponder, ProxyProtocolTCPResponder
from dnsdistdohtests import DNSDistDOHTest
# Python2/3 compatibility hacks
except ImportError:
from Queue import Queue
-def ProxyProtocolUDPResponder(port, fromQueue, toQueue):
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
- try:
- sock.bind(("127.0.0.1", port))
- except socket.error as e:
- print("Error binding in the Proxy Protocol UDP responder: %s" % str(e))
- sys.exit(1)
+toProxyQueue = Queue()
+fromProxyQueue = Queue()
+proxyResponderPort = pickAvailablePort()
- while True:
- data, addr = sock.recvfrom(4096)
-
- proxy = ProxyProtocol()
- if len(data) < proxy.HEADER_SIZE:
- continue
-
- if not proxy.parseHeader(data):
- continue
-
- if proxy.local:
- # likely a healthcheck
- data = data[proxy.HEADER_SIZE:]
- request = dns.message.from_wire(data)
- response = dns.message.make_response(request)
- wire = response.to_wire()
- sock.settimeout(2.0)
- sock.sendto(wire, addr)
- sock.settimeout(None)
-
- continue
-
- payload = data[:(proxy.HEADER_SIZE + proxy.contentLen)]
- dnsData = data[(proxy.HEADER_SIZE + proxy.contentLen):]
- toQueue.put([payload, dnsData], True, 2.0)
- # computing the correct ID for the response
- request = dns.message.from_wire(dnsData)
- response = fromQueue.get(True, 2.0)
- response.id = request.id
-
- sock.settimeout(2.0)
- sock.sendto(response.to_wire(), addr)
- sock.settimeout(None)
+udpResponder = threading.Thread(name='UDP Proxy Protocol Responder', target=ProxyProtocolUDPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
+udpResponder.daemon = True
+udpResponder.start()
+tcpResponder = threading.Thread(name='TCP Proxy Protocol Responder', target=ProxyProtocolTCPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
+tcpResponder.daemon = True
+tcpResponder.start()
- sock.close()
+backgroundThreads = {}
+
+def MockTCPReverseProxyAddingProxyProtocol(listeningPort, forwardingPort, serverCtx=None, ca=None, sni=None):
+ # this responder accepts TCP connections on the listening port,
+ # and relay the raw content to a second TCP connection to the
+ # forwarding port, after adding a Proxy Protocol v2 payload
+ # containing the initial source IP and port, destination IP
+ # and port.
+ backgroundThreads[threading.get_native_id()] = True
-def ProxyProtocolTCPResponder(port, fromQueue, toQueue):
- # be aware that this responder will not accept a new connection
- # until the last one has been closed. This is done on purpose to
- # to check for connection reuse, making sure that a lot of connections
- # are not opened in parallel.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+
+ if serverCtx is not None:
+ sock = serverCtx.wrap_socket(sock, server_side=True)
+
try:
- sock.bind(("127.0.0.1", port))
+ sock.bind(("127.0.0.1", listeningPort))
except socket.error as e:
- print("Error binding in the TCP responder: %s" % str(e))
+ print("Error binding in the Mock TCP reverse proxy: %s" % str(e))
sys.exit(1)
-
+ sock.settimeout(0.5)
sock.listen(100)
+
while True:
- (conn, _) = sock.accept()
- conn.settimeout(5.0)
- # try to read the entire Proxy Protocol header
- proxy = ProxyProtocol()
- header = conn.recv(proxy.HEADER_SIZE)
- if not header:
- conn.close()
- continue
-
- if not proxy.parseHeader(header):
- conn.close()
- continue
-
- proxyContent = conn.recv(proxy.contentLen)
- if not proxyContent:
- conn.close()
- continue
-
- payload = header + proxyContent
- while True:
+ try:
+ (incoming, _) = sock.accept()
+ except socket.timeout:
+ if backgroundThreads.get(threading.get_native_id(), False) == False:
+ del backgroundThreads[threading.get_native_id()]
+ break
+ else:
+ continue
+
+ incoming.settimeout(5.0)
+ payload = ProxyProtocol.getPayload(False, True, False, '127.0.0.1', '127.0.0.1', incoming.getpeername()[1], listeningPort, [ [ 2, b'foo'], [ 3, b'proxy'] ])
+
+ outgoing = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ outgoing.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ outgoing.settimeout(2.0)
+ if sni:
+ if hasattr(ssl, 'create_default_context'):
+ sslctx = ssl.create_default_context(cafile=ca)
+ if hasattr(sslctx, 'set_alpn_protocols'):
+ sslctx.set_alpn_protocols(['h2'])
+ outgoing = sslctx.wrap_socket(outgoing, server_hostname=sni)
+ else:
+ outgoing = ssl.wrap_socket(outgoing, ca_certs=ca, cert_reqs=ssl.CERT_REQUIRED)
+
+ outgoing.connect(('127.0.0.1', forwardingPort))
+
+ outgoing.send(payload)
+
+ sel = selectors.DefaultSelector()
+ def readFromClient(conn):
+ data = conn.recv(512)
+ if not data or len(data) == 0:
+ return False
+ outgoing.send(data)
+ return True
+
+ def readFromBackend(conn):
+ data = conn.recv(512)
+ if not data or len(data) == 0:
+ return False
+ incoming.send(data)
+ return True
+
+ sel.register(incoming, selectors.EVENT_READ, readFromClient)
+ sel.register(outgoing, selectors.EVENT_READ, readFromBackend)
+ done = False
+ while not done:
try:
- data = conn.recv(2)
+ events = sel.select()
+ for key, mask in events:
+ if not (key.data)(key.fileobj):
+ done = True
+ break
except socket.timeout:
- data = None
-
- if not data:
- conn.close()
break
-
- (datalen,) = struct.unpack("!H", data)
- data = conn.recv(datalen)
-
- toQueue.put([payload, data], True, 2.0)
-
- response = copy.deepcopy(fromQueue.get(True, 2.0))
- if not response:
- conn.close()
+ except:
break
- # computing the correct ID for the response
- request = dns.message.from_wire(data)
- response.id = request.id
-
- wire = response.to_wire()
- conn.send(struct.pack("!H", len(wire)))
- conn.send(wire)
-
- conn.close()
+ incoming.close()
+ outgoing.close()
sock.close()
-toProxyQueue = Queue()
-fromProxyQueue = Queue()
-proxyResponderPort = 5470
-
-udpResponder = threading.Thread(name='UDP Proxy Protocol Responder', target=ProxyProtocolUDPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
-udpResponder.setDaemon(True)
-udpResponder.start()
-tcpResponder = threading.Thread(name='TCP Proxy Protocol Responder', target=ProxyProtocolTCPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
-tcpResponder.setDaemon(True)
-tcpResponder.start()
-
class ProxyProtocolTest(DNSDistTest):
_proxyResponderPort = proxyResponderPort
_config_params = ['_proxyResponderPort']
"""
_config_template = """
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library='nghttp2', proxyProtocolOutsideTLS=true})
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library='nghttp2', proxyProtocolOutsideTLS=false})
setProxyProtocolACL( { "127.0.0.1/32" } )
- newServer{address="127.0.0.1:%d", useProxyProtocol=true}
+ newServer{address="127.0.0.1:%d", useProxyProtocol=true, proxyProtocolAdvertiseTLS=true}
function addValues(dq)
dq:addProxyProtocolValue(0, 'foo')
-- override all existing values
addAction("override.proxy-protocol-incoming.tests.powerdns.com.", SetProxyProtocolValuesAction({["50"]="overridden"}))
"""
- _config_params = ['_proxyResponderPort']
- _verboseMode = True
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _dohServerPPOutsidePort = pickAvailablePort()
+ _dohServerPPInsidePort = pickAvailablePort()
+ _config_params = ['_dohServerPPOutsidePort', '_serverCert', '_serverKey', '_dohServerPPInsidePort', '_serverCert', '_serverKey', '_proxyResponderPort']
def testNoHeader(self):
"""
name = 'no-header.incoming-proxy-protocol.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
- for method in ("sendUDPQuery", "sendTCPQuery"):
+ for method in ("sendUDPQuery", "sendTCPQuery", "sendDOHQueryWrapper"):
sender = getattr(self, method)
- (_, receivedResponse) = sender(query, response=None)
+ try:
+ (_, receivedResponse) = sender(query, response=None)
+ except Exception:
+ receivedResponse = None
self.assertEqual(receivedResponse, None)
def testIncomingProxyDest(self):
self.assertEqual(receivedResponse, response)
self.checkMessageProxyProtocol(receivedProxyPayload, srcAddr, destAddr, True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [ 42, b'bar'], [255, b'proxy-protocol'] ], True, srcPort, destPort)
+ def testProxyDoHSeveralQueriesOverConnectionPPOutside(self):
+ """
+ Incoming Proxy Protocol: Several queries over the same connection (DoH, PP outside TLS)
+ """
+ name = 'several-queries.doh-outside.proxy-protocol-incoming.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ toProxyQueue.put(response, True, 2.0)
+
+ wire = query.to_wire()
+
+ reverseProxyPort = pickAvailablePort()
+ reverseProxy = threading.Thread(name='Mock Proxy Protocol Reverse Proxy', target=MockTCPReverseProxyAddingProxyProtocol, args=[reverseProxyPort, self._dohServerPPOutsidePort])
+ reverseProxy.start()
+ time.sleep(1)
+
+ receivedResponse = None
+ conn = self.openDOHConnection(reverseProxyPort, self._caCert, timeout=2.0)
+
+ reverseProxyBaseURL = ("https://%s:%d/" % (self._serverName, reverseProxyPort))
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(reverseProxyPort, self._serverName, reverseProxyBaseURL, query, response=response, caFile=self._caCert, useQueue=True, conn=conn)
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEqual(receivedQuery, query)
+ self.assertEqual(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [32, ''], [42, b'bar'], [255, b'proxy-protocol'] ], v6=False, sourcePort=None, destinationPort=reverseProxyPort)
+
+ for idx in range(5):
+ receivedResponse = None
+ toProxyQueue.put(response, True, 2.0)
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(reverseProxyPort, self._serverName, reverseProxyBaseURL, query, response=response, caFile=self._caCert, useQueue=True, conn=conn)
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEqual(receivedQuery, query)
+ self.assertEqual(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [32, ''], [42, b'bar'], [255, b'proxy-protocol'] ], v6=False, sourcePort=None, destinationPort=reverseProxyPort)
+
+ def testProxyDoHSeveralQueriesOverConnectionPPInside(self):
+ """
+ Incoming Proxy Protocol: Several queries over the same connection (DoH, PP inside TLS)
+ """
+ name = 'several-queries.doh-inside.proxy-protocol-incoming.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ toProxyQueue.put(response, True, 2.0)
+
+ wire = query.to_wire()
+
+ reverseProxyPort = pickAvailablePort()
+ tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ tlsContext.load_cert_chain(self._serverCert, self._serverKey)
+ tlsContext.set_alpn_protocols(['h2'])
+ reverseProxy = threading.Thread(name='Mock Proxy Protocol Reverse Proxy', target=MockTCPReverseProxyAddingProxyProtocol, args=[reverseProxyPort, self._dohServerPPInsidePort, tlsContext, self._caCert, self._serverName])
+ reverseProxy.start()
+
+ receivedResponse = None
+ time.sleep(1)
+ conn = self.openDOHConnection(reverseProxyPort, self._caCert, timeout=2.0)
+
+ reverseProxyBaseURL = ("https://%s:%d/" % (self._serverName, reverseProxyPort))
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(reverseProxyPort, self._serverName, reverseProxyBaseURL, query, response=response, caFile=self._caCert, useQueue=True, conn=conn)
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEqual(receivedQuery, query)
+ self.assertEqual(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [32, ''], [ 42, b'bar'], [255, b'proxy-protocol'] ], v6=False, sourcePort=None, destinationPort=reverseProxyPort)
+
+ for idx in range(5):
+ receivedResponse = None
+ toProxyQueue.put(response, True, 2.0)
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(reverseProxyPort, self._serverName, reverseProxyBaseURL, query, response=response, caFile=self._caCert, useQueue=True, conn=conn)
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEqual(receivedQuery, query)
+ self.assertEqual(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [32, ''], [ 42, b'bar'], [255, b'proxy-protocol'] ], v6=False, sourcePort=None, destinationPort=reverseProxyPort)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls._sock.close()
+ for backgroundThread in cls._backgroundThreads:
+ cls._backgroundThreads[backgroundThread] = False
+ for backgroundThread in backgroundThreads:
+ backgroundThreads[backgroundThread] = False
+ cls.killProcess(cls._dnsdist)
+
class TestProxyProtocolNotExpected(DNSDistTest):
"""
dnsdist is configured to expect a Proxy Protocol header on incoming queries but not from 127.0.0.1
print('timeout')
self.assertEqual(receivedResponse, None)
+class TestProxyProtocolNotAllowedOnBind(DNSDistTest):
+ """
+ dnsdist is configured to expect a Proxy Protocol header on incoming queries but not on the 127.0.0.1 bind
+ """
+ _skipListeningOnCL = True
+ _config_template = """
+ -- proxy protocol payloads are not allowed on this bind address!
+ addLocal('127.0.0.1:%d', {enableProxyProtocol=false})
+ setProxyProtocolACL( { "127.0.0.1/8" } )
+ newServer{address="127.0.0.1:%d"}
+ """
+ # NORMAL responder, does not expect a proxy protocol payload!
+ _config_params = ['_dnsDistPort', '_testServerPort']
+
+ def testNoHeader(self):
+ """
+ Unexpected Proxy Protocol: no header
+ """
+ # no proxy protocol header and none is expected from this source, should be passed on
+ name = 'no-header.unexpected-proxy-protocol.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+
+ response.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ def testIncomingProxyDest(self):
+ """
+ Unexpected Proxy Protocol: should be dropped
+ """
+ name = 'with-proxy-payload.unexpected-protocol-incoming.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+
+ # Make sure that the proxy payload does NOT turn into a legal qname
+ destAddr = "ff:db8::ffff"
+ destPort = 65535
+ srcAddr = "ff:db8::ffff"
+ srcPort = 65535
+
+ udpPayload = ProxyProtocol.getPayload(False, False, True, srcAddr, destAddr, srcPort, destPort, [ [ 2, b'foo'], [ 3, b'proxy'] ])
+ (_, receivedResponse) = self.sendUDPQuery(udpPayload + query.to_wire(), response=None, useQueue=False, rawQuery=True)
+ self.assertEqual(receivedResponse, None)
+
+ tcpPayload = ProxyProtocol.getPayload(False, True, True, srcAddr, destAddr, srcPort, destPort, [ [ 2, b'foo'], [ 3, b'proxy'] ])
+ wire = query.to_wire()
+
+ receivedResponse = None
+ try:
+ conn = self.openTCPConnection(2.0)
+ conn.send(tcpPayload)
+ conn.send(struct.pack("!H", len(wire)))
+ conn.send(wire)
+ receivedResponse = self.recvTCPResponseOverConnection(conn)
+ except socket.timeout:
+ print('timeout')
+ self.assertEqual(receivedResponse, None)
+
class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
- _dohBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohServerPort))
+ _dohWithNGHTTP2ServerPort = pickAvailablePort()
+ _dohWithNGHTTP2BaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithNGHTTP2ServerPort))
+ _dohWithH2OServerPort = pickAvailablePort()
+ _dohWithH2OBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithH2OServerPort))
_proxyResponderPort = proxyResponderPort
_config_template = """
newServer{address="127.0.0.1:%s", useProxyProtocol=true}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='nghttp2' })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='h2o' })
setACL( { "::1/128", "127.0.0.0/8" } )
"""
- _config_params = ['_proxyResponderPort', '_dohServerPort', '_serverCert', '_serverKey']
+ _config_params = ['_proxyResponderPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
+ _verboseMode = True
def testTruncation(self):
"""
- DOH: Truncation over UDP (with cache)
+ DOH: Truncation over UDP
"""
# the query is first forwarded over UDP, leading to a TC=1 answer from the
# backend, then over TCP
- name = 'truncated-udp.doh-with-cache.tests.powerdns.com.'
+ name = 'truncated-udp.doh.proxy-protocol.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
query.id = 42
expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
'127.0.0.1')
response.answer.append(rrset)
- # first response is a TC=1
- tcResponse = dns.message.make_response(query)
- tcResponse.flags |= dns.flags.TC
- toProxyQueue.put(tcResponse, True, 2.0)
+ for (port,url) in [(self._dohWithNGHTTP2ServerPort, self._dohWithNGHTTP2BaseURL), (self._dohWithH2OServerPort, self._dohWithH2OBaseURL)]:
+ # first response is a TC=1
+ tcResponse = dns.message.make_response(query)
+ tcResponse.flags |= dns.flags.TC
+ toProxyQueue.put(tcResponse, True, 2.0)
- ((receivedProxyPayload, receivedDNSData), receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, response=response, fromQueue=fromProxyQueue, toQueue=toProxyQueue)
- # first query, received by the responder over UDP
- self.assertTrue(receivedProxyPayload)
- self.assertTrue(receivedDNSData)
- receivedQuery = dns.message.from_wire(receivedDNSData)
- self.assertTrue(receivedQuery)
- receivedQuery.id = expectedQuery.id
- self.assertEqual(expectedQuery, receivedQuery)
- self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
- self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, destinationPort=self._dohServerPort)
+ ((receivedProxyPayload, receivedDNSData), receivedResponse) = self.sendDOHQuery(port, self._serverName, url, query, caFile=self._caCert, response=response, fromQueue=fromProxyQueue, toQueue=toProxyQueue)
+ # first query, received by the responder over UDP
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ self.assertTrue(receivedQuery)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, destinationPort=port)
- # check the response
- self.assertTrue(receivedResponse)
- self.assertEqual(response, receivedResponse)
+ # check the response
+ self.assertTrue(receivedResponse)
+ self.assertEqual(response, receivedResponse)
- # check the second query, received by the responder over TCP
- (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
- self.assertTrue(receivedDNSData)
- receivedQuery = dns.message.from_wire(receivedDNSData)
- self.assertTrue(receivedQuery)
- receivedQuery.id = expectedQuery.id
- self.assertEqual(expectedQuery, receivedQuery)
- self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
- self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, destinationPort=self._dohServerPort)
+ # check the second query, received by the responder over TCP
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedDNSData)
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ self.assertTrue(receivedQuery)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, destinationPort=port)
- # make sure we consumed everything
- self.assertTrue(toProxyQueue.empty())
- self.assertTrue(fromProxyQueue.empty())
+ # make sure we consumed everything
+ self.assertTrue(toProxyQueue.empty())
+ self.assertTrue(fromProxyQueue.empty())
def testAddressFamilyMismatch(self):
"""
'127.0.0.1')
response.answer.append(rrset)
- # the query should be dropped
- (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, customHeaders=['x-forwarded-for: [::1]:8080'], useQueue=False)
- self.assertFalse(receivedQuery)
- self.assertFalse(receivedResponse)
+ for (port,url) in [(self._dohWithNGHTTP2ServerPort, self._dohWithNGHTTP2BaseURL), (self._dohWithH2OServerPort, self._dohWithH2OBaseURL)]:
+ # the query should be dropped
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(port, self._serverName, url, query, caFile=self._caCert, customHeaders=['x-forwarded-for: [::1]:8080'], useQueue=False)
+ self.assertFalse(receivedQuery)
+ self.assertFalse(receivedResponse)
- # make sure the timeout is detected, if any
- time.sleep(4)
+ # make sure the timeout is detected, if any
+ time.sleep(4)
- # this one should not
- ((receivedProxyPayload, receivedDNSData), receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, customHeaders=['x-forwarded-for: 127.0.0.42:8080'], response=response, fromQueue=fromProxyQueue, toQueue=toProxyQueue)
- self.assertTrue(receivedProxyPayload)
- self.assertTrue(receivedDNSData)
- receivedQuery = dns.message.from_wire(receivedDNSData)
- self.assertTrue(receivedQuery)
- receivedQuery.id = expectedQuery.id
- self.assertEqual(expectedQuery, receivedQuery)
- self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
- self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.42', '127.0.0.1', True, destinationPort=self._dohServerPort)
- # check the response
- self.assertTrue(receivedResponse)
- receivedResponse.id = response.id
- self.assertEqual(response, receivedResponse)
+ # this one should not
+ ((receivedProxyPayload, receivedDNSData), receivedResponse) = self.sendDOHQuery(port, self._serverName, url, query, caFile=self._caCert, customHeaders=['x-forwarded-for: 127.0.0.42:8080'], response=response, fromQueue=fromProxyQueue, toQueue=toProxyQueue)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ self.assertTrue(receivedQuery)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.42', '127.0.0.1', True, destinationPort=port)
+ # check the response
+ self.assertTrue(receivedResponse)
+ receivedResponse.id = response.id
+ self.assertEqual(response, receivedResponse)
import threading
import clientsubnetoption
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
def servFailResponseCallback(request):
response = dns.message.make_response(request)
class TestRestartQuery(DNSDistTest):
# this test suite uses different responder ports
- _testNormalServerPort = 5420
- _testServfailServerPort = 5421
+ _testNormalServerPort = pickAvailablePort()
+ _testServfailServerPort = pickAvailablePort()
_config_template = """
newServer{address="127.0.0.1:%d", pool='restarted'}:setUp()
newServer{address="127.0.0.1:%d", pool=''}:setUp()
# servfail
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServfailServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, servFailResponseCallback])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServfailServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, servFailResponseCallback])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._UDPResponderNormal = threading.Thread(name='UDP ResponderNormal', target=cls.UDPResponder, args=[cls._testNormalServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, normalResponseCallback])
- cls._UDPResponderNormal.setDaemon(True)
+ cls._UDPResponderNormal.daemon = True
cls._UDPResponderNormal.start()
cls._TCPResponderNormal = threading.Thread(name='TCP ResponderNormal', target=cls.TCPResponder, args=[cls._testNormalServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, normalResponseCallback])
- cls._TCPResponderNormal.setDaemon(True)
+ cls._TCPResponderNormal.daemon = True
cls._TCPResponderNormal.start()
def testRestartingQuery(self):
sender = getattr(self, method)
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertTrue(receivedResponse)
- self.assertEquals(receivedResponse, expectedResponse)
+ self.assertEqual(receivedResponse, expectedResponse)
import threading
import time
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class TestRoutingPoolRouting(DNSDistTest):
_config_template = """
newServer{address="127.0.0.1:%s", pool="real"}
- addAction(makeRule("poolaction.routing.tests.powerdns.com"), PoolAction("real"))
+ addAction(SuffixMatchNodeRule("poolaction.routing.tests.powerdns.com"), PoolAction("real"))
-- by default PoolAction stops the processing so the second rule should not be executed
- addAction(makeRule("poolaction.routing.tests.powerdns.com"), PoolAction("not-real"))
+ addAction(SuffixMatchNodeRule("poolaction.routing.tests.powerdns.com"), PoolAction("not-real"))
-- this time we configure PoolAction to not stop the processing
- addAction(makeRule("poolaction-nostop.routing.tests.powerdns.com"), PoolAction("no-real", false))
+ addAction(SuffixMatchNodeRule("poolaction-nostop.routing.tests.powerdns.com"), PoolAction("no-real", false))
-- so the second rule should be executed
- addAction(makeRule("poolaction-nostop.routing.tests.powerdns.com"), PoolAction("real"))
+ addAction(SuffixMatchNodeRule("poolaction-nostop.routing.tests.powerdns.com"), PoolAction("real"))
"""
def testPolicyPoolAction(self):
class TestRoutingQPSPoolRouting(DNSDistTest):
_config_template = """
newServer{address="127.0.0.1:%s", pool="regular"}
- addAction(makeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "regular"))
+ addAction(SuffixMatchNodeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "regular"))
"""
def testQPSPoolAction(self):
(_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
self.assertEqual(receivedResponse, None)
+class RoundRobinTest(object):
+ def doTestRR(self, name):
+ """
+ Routing: Round Robin
-class TestRoutingRoundRobinLB(DNSDistTest):
+ Send 10 A queries to the requested name,
+ check that dnsdist routes half of it to each backend.
+ """
+ numberOfQueries = 10
+ name = name
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
- _testServer2Port = 5351
+ # the round robin counter is shared for UDP and TCP,
+ # so we need to do UDP then TCP to have a clean count
+ for _ in range(numberOfQueries):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ for _ in range(numberOfQueries):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ for key in self._responsesCounter:
+ value = self._responsesCounter[key]
+ self.assertEqual(value, numberOfQueries / 2)
+
+class TestRoutingRoundRobinLB(RoundRobinTest, DNSDistTest):
+
+ _testServer2Port = pickAvailablePort()
_config_params = ['_testServerPort', '_testServer2Port']
_config_template = """
setServerPolicy(roundrobin)
def startResponders(cls):
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder2.setDaemon(True)
+ cls._UDPResponder2.daemon = True
cls._UDPResponder2.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder2.setDaemon(True)
+ cls._TCPResponder2.daemon = True
cls._TCPResponder2.start()
def testRR(self):
Send 10 A queries to "rr.routing.tests.powerdns.com.",
check that dnsdist routes half of it to each backend.
"""
- numberOfQueries = 10
- name = 'rr.routing.tests.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
+ self.doTestRR('rr.routing.tests.powerdns.com.')
- # the round robin counter is shared for UDP and TCP,
- # so we need to do UDP then TCP to have a clean count
- for _ in range(numberOfQueries):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
+class TestRoutingRoundRobinLBViaPool(RoundRobinTest, DNSDistTest):
- for _ in range(numberOfQueries):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
+ _testServer2Port = pickAvailablePort()
+ _config_params = ['_testServerPort', '_testServer2Port']
+ _config_template = """
+ s1 = newServer{address="127.0.0.1:%d"}
+ s1:setUp()
+ s2 = newServer{address="127.0.0.1:%d"}
+ s2:setUp()
+ setPoolServerPolicy(roundrobin, '')
+ """
- for key in self._responsesCounter:
- value = self._responsesCounter[key]
- self.assertEqual(value, numberOfQueries / 2)
+ @classmethod
+ def startResponders(cls):
+ print("Launching responders..")
+ cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._UDPResponder.daemon = True
+ cls._UDPResponder.start()
+ cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._UDPResponder2.daemon = True
+ cls._UDPResponder2.start()
+
+ cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._TCPResponder.daemon = True
+ cls._TCPResponder.start()
+
+ cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._TCPResponder2.daemon = True
+ cls._TCPResponder2.start()
+
+ def testRR(self):
+ """
+ Routing: Round Robin (pool)
+
+ Send 10 A queries to "rr-pool.routing.tests.powerdns.com.",
+ check that dnsdist routes half of it to each backend.
+ """
+ self.doTestRR('rr-pool.routing.tests.powerdns.com.')
class TestRoutingRoundRobinLBOneDown(DNSDistTest):
- _testServer2Port = 5351
+ _testServer2Port = pickAvailablePort()
_config_params = ['_testServerPort', '_testServer2Port']
_config_template = """
setServerPolicy(roundrobin)
class TestRoutingRoundRobinLBAllDown(DNSDistTest):
- _testServer2Port = 5351
+ _testServer2Port = pickAvailablePort()
_config_params = ['_testServerPort', '_testServer2Port']
_config_template = """
setServerPolicy(roundrobin)
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertEqual(receivedResponse, None)
-class TestRoutingLuaFFIPerThreadRoundRobinLB(DNSDistTest):
+class TestRoutingLuaFFIPerThreadRoundRobinLB(RoundRobinTest, DNSDistTest):
- _testServer2Port = 5351
+ _testServer2Port = pickAvailablePort()
_config_params = ['_testServerPort', '_testServer2Port']
_config_template = """
-- otherwise we start too many TCP workers, and as each thread
def startResponders(cls):
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder2.setDaemon(True)
+ cls._UDPResponder2.daemon = True
cls._UDPResponder2.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder2.setDaemon(True)
+ cls._TCPResponder2.daemon = True
cls._TCPResponder2.start()
def testRR(self):
"""
- Routing: Round Robin
-
- Send 10 A queries to "rr.routing.tests.powerdns.com.",
- check that dnsdist routes half of it to each backend.
+ Routing: Round Robin (LuaFFI)
"""
- numberOfQueries = 10
- name = 'rr.routing.tests.powerdns.com.'
- query = dns.message.make_query(name, 'A', 'IN')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 60,
- dns.rdataclass.IN,
- dns.rdatatype.A,
- '192.0.2.1')
- response.answer.append(rrset)
+ self.doTestRR('rr-luaffi.routing.tests.powerdns.com.')
- # the round robin counter is shared for UDP and TCP,
- # so we need to do UDP then TCP to have a clean count
- for _ in range(numberOfQueries):
- (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
+class TestRoutingCustomLuaRoundRobinLB(RoundRobinTest, DNSDistTest):
- for _ in range(numberOfQueries):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
- receivedQuery.id = query.id
- self.assertEqual(query, receivedQuery)
- self.assertEqual(response, receivedResponse)
+ _testServer2Port = pickAvailablePort()
+ _config_params = ['_testServerPort', '_testServer2Port']
+ _config_template = """
+ -- otherwise we start too many TCP workers, and as each thread
+ -- uses it own counter this makes the TCP queries distribution hard to predict
+ setMaxTCPClientThreads(1)
- for key in self._responsesCounter:
- value = self._responsesCounter[key]
- self.assertEqual(value, numberOfQueries / 2)
+ local counter = 0
+ function luaroundrobin(servers_list, dq)
+ counter = counter + 1
+ return servers_list[(counter %% #servers_list)+1]
+ end
+ setServerPolicy(newServerPolicy("custom lua round robin policy", luaroundrobin))
+
+ s1 = newServer{address="127.0.0.1:%s"}
+ s1:setUp()
+ s2 = newServer{address="127.0.0.1:%s"}
+ s2:setUp()
+ """
+
+ @classmethod
+ def startResponders(cls):
+ print("Launching responders..")
+ cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._UDPResponder.daemon = True
+ cls._UDPResponder.start()
+ cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._UDPResponder2.daemon = True
+ cls._UDPResponder2.start()
+
+ cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._TCPResponder.daemon = True
+ cls._TCPResponder.start()
+
+ cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+ cls._TCPResponder2.daemon = True
+ cls._TCPResponder2.start()
+
+ def testRR(self):
+ """
+ Routing: Round Robin (Lua)
+ """
+ self.doTestRR('rr-lua.routing.tests.powerdns.com.')
class TestRoutingOrder(DNSDistTest):
- _testServer2Port = 5351
+ _testServer2Port = pickAvailablePort()
_config_params = ['_testServerPort', '_testServer2Port']
_config_template = """
setServerPolicy(firstAvailable)
def startResponders(cls):
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder2.setDaemon(True)
+ cls._UDPResponder2.daemon = True
cls._UDPResponder2.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder2.setDaemon(True)
+ cls._TCPResponder2.daemon = True
cls._TCPResponder2.start()
def testOrder(self):
class TestFirstAvailableQPSPacketCacheHits(DNSDistTest):
_verboseMode = True
- _testServer2Port = 5351
+ _testServer2Port = pickAvailablePort()
_config_params = ['_testServerPort', '_testServer2Port']
_config_template = """
setServerPolicy(firstAvailable)
def startResponders(cls):
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder2.setDaemon(True)
+ cls._UDPResponder2.daemon = True
cls._UDPResponder2.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder2.setDaemon(True)
+ cls._TCPResponder2.daemon = True
cls._TCPResponder2.start()
def testOrderQPSCacheHits(self):
class TestRoutingWRandom(DNSDistTest):
- _testServer2Port = 5351
+ _testServer2Port = pickAvailablePort()
_config_params = ['_testServerPort', '_testServer2Port']
_config_template = """
setServerPolicy(wrandom)
def startResponders(cls):
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder2.setDaemon(True)
+ cls._UDPResponder2.daemon = True
cls._UDPResponder2.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder2.setDaemon(True)
+ cls._TCPResponder2.daemon = True
cls._TCPResponder2.start()
def testWRandom(self):
class TestRoutingHighValueWRandom(DNSDistTest):
- _testServer2Port = 5351
+ _testServer2Port = pickAvailablePort()
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
_config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
def startResponders(cls):
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder2.setDaemon(True)
+ cls._UDPResponder2.daemon = True
cls._UDPResponder2.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
- cls._TCPResponder2.setDaemon(True)
+ cls._TCPResponder2.daemon = True
cls._TCPResponder2.start()
def testHighValueWRandom(self):
import base64
from datetime import datetime, timedelta
import os
+import sys
import time
import unittest
import dns
_config_template = """
addAction(AllRule(), NoneAction())
- addAction(makeRule("allowed.advanced.tests.powerdns.com."), AllowAction())
+ addAction(QNameSuffixRule("allowed.advanced.tests.powerdns.com."), AllowAction())
addAction(AllRule(), DropAction())
newServer{address="127.0.0.1:%s"}
"""
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertEqual(receivedResponse, expectedResponse)
+class TestAdvancedNMGAddNMG(DNSDistTest):
+ _config_template = """
+ oneNMG = newNMG()
+ anotherNMG = newNMG()
+ anotherNMG:addMask('127.0.0.1/32')
+ oneNMG:addNMG(anotherNMG)
+ addAction(NotRule(NetmaskGroupRule(oneNMG)), DropAction())
+ addAction(AllRule(), SpoofAction('192.0.2.1'))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testAdvancedNMGRuleAddNMG(self):
+ """
+ Advanced: NMGRule:addNMG()
+ """
+ name = 'nmgrule-addnmg.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_,receivedResponse) = sender(query, response=expectedResponse, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+class TestAdvancedNMGRuleFromString(DNSDistTest):
+
+ _config_template = """
+ addAction(NotRule(NetmaskGroupRule('192.0.2.1')), RCodeAction(DNSRCode.REFUSED))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testAdvancedNMGRule(self):
+ """
+ Advanced: NMGRule (from string) should refuse our queries
+ """
+ name = 'nmgrule-from-string.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+class TestAdvancedNMGRuleFromMultipleStrings(DNSDistTest):
+
+ _config_template = """
+ addAction(NotRule(NetmaskGroupRule({'192.0.2.1', '192.0.2.128/25'})), RCodeAction(DNSRCode.REFUSED))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testAdvancedNMGRule(self):
+ """
+ Advanced: NMGRule (from multiple strings) should refuse our queries
+ """
+ name = 'nmgrule-from-multiple-strings.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
class TestDSTPortRule(DNSDistTest):
_config_params = ['_dnsDistPort', '_testServerPort']
Advanced: A question with ECS version larger than 0 yields BADVERS
"""
+ if sys.version_info >= (3, 11) and sys.version_info < (3, 12):
+ raise unittest.SkipTest("Test skipped, see https://github.com/PowerDNS/pdns/pull/12912")
+
name = 'ednsversionrule.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=1)
self.assertTrue(receivedResponse)
self.assertEqual(expectedResponse, receivedResponse)
+class TestRmRules(DNSDistTest):
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+ _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
+ _config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%s")
+ newServer{address="127.0.0.1:%s"}
+ addAction(AllRule(), SpoofAction("192.0.2.1"), {name='myFirstRule', uuid='090736ca-2fb6-41e7-a836-58efaca3d71e'})
+ addAction(AllRule(), SpoofAction("192.0.2.1"), {name='mySecondRule'})
+ addResponseAction(AllRule(), AllowResponseAction(), {name='myFirstResponseRule', uuid='745a03b5-89e0-4eee-a6bf-c9700b0d31f0'})
+ addResponseAction(AllRule(), AllowResponseAction(), {name='mySecondResponseRule'})
+ """
+
+ def testRmRules(self):
+ """
+ Advanced: Remove rules
+ """
+ lines = self.sendConsoleCommand("showRules({showUUIDs=true})").splitlines()
+ self.assertEqual(len(lines), 3)
+ self.assertIn('myFirstRule', lines[1])
+ self.assertIn('mySecondRule', lines[2])
+ self.assertIn('090736ca-2fb6-41e7-a836-58efaca3d71e', lines[1])
+
+ lines = self.sendConsoleCommand("showResponseRules({showUUIDs=true})").splitlines()
+ self.assertEqual(len(lines), 3)
+ self.assertIn('myFirstResponseRule', lines[1])
+ self.assertIn('mySecondResponseRule', lines[2])
+ self.assertIn('745a03b5-89e0-4eee-a6bf-c9700b0d31f0', lines[1])
+
+ self.sendConsoleCommand("rmRule('090736ca-2fb6-41e7-a836-58efaca3d71e')")
+ self.sendConsoleCommand("rmRule('mySecondRule')")
+ lines = self.sendConsoleCommand("showRules({showUUIDs=true})").splitlines()
+ self.assertEqual(len(lines), 1)
+
+ self.sendConsoleCommand("rmResponseRule('745a03b5-89e0-4eee-a6bf-c9700b0d31f0')")
+ self.sendConsoleCommand("rmResponseRule('mySecondResponseRule')")
+ lines = self.sendConsoleCommand("showResponseRules({showUUIDs=true})").splitlines()
+ self.assertEqual(len(lines), 1)
+
class TestAdvancedContinueAction(DNSDistTest):
_config_template = """
local basicSVC = { newSVCRecordParameters(1, "dot.powerdns.com.", { mandatory={"port"}, alpn={"dot"}, noDefaultAlpn=true, port=853, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" } }),
newSVCRecordParameters(2, "doh.powerdns.com.", { mandatory={"port"}, alpn={"h2"}, port=443, ipv4hint={ "192.0.2.2" }, ipv6hint={ "2001:db8::2" }, key7="/dns-query{?dns}" })
}
- addAction(AndRule{QTypeRule(64), makeRule("basic.svcb.tests.powerdns.com.")}, SpoofSVCAction(basicSVC, {aa=true}))
+ addAction(AndRule{QTypeRule(64), SuffixMatchNodeRule("basic.svcb.tests.powerdns.com.")}, SpoofSVCAction(basicSVC, {aa=true}))
local noHintsSVC = { newSVCRecordParameters(1, "dot.powerdns.com.", { mandatory={"port"}, alpn={"dot"}, noDefaultAlpn=true, port=853}),
newSVCRecordParameters(2, "doh.powerdns.com.", { mandatory={"port"}, alpn={"h2"}, port=443, key7="/dns-query{?dns}" })
}
- addAction(AndRule{QTypeRule(64), makeRule("no-hints.svcb.tests.powerdns.com.")}, SpoofSVCAction(noHintsSVC, {aa=true}))
+ addAction(AndRule{QTypeRule(64), SuffixMatchNodeRule("no-hints.svcb.tests.powerdns.com.")}, SpoofSVCAction(noHintsSVC, {aa=true}))
local effectiveTargetSVC = { newSVCRecordParameters(1, ".", { mandatory={"port"}, alpn={ "dot" }, noDefaultAlpn=true, port=853, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" }}),
newSVCRecordParameters(2, ".", { mandatory={"port"}, alpn={ "h2" }, port=443, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" }, key7="/dns-query{?dns}"})
}
- addAction(AndRule{QTypeRule(64), makeRule("effective-target.svcb.tests.powerdns.com.")}, SpoofSVCAction(effectiveTargetSVC, {aa=true}))
+ addAction(AndRule{QTypeRule(64), SuffixMatchNodeRule("effective-target.svcb.tests.powerdns.com.")}, SpoofSVCAction(effectiveTargetSVC, {aa=true}))
local httpsSVC = { newSVCRecordParameters(1, ".", { mandatory={"port"}, alpn={ "h2" }, noDefaultAlpn=true, port=8002, ipv4hint={ "192.0.2.2" }, ipv6hint={ "2001:db8::2" }}) }
- addAction(AndRule{QTypeRule(65), makeRule("https.svcb.tests.powerdns.com.")}, SpoofSVCAction(httpsSVC))
+ addAction(AndRule{QTypeRule(65), SuffixMatchNodeRule("https.svcb.tests.powerdns.com.")}, SpoofSVCAction(httpsSVC))
newServer{address="127.0.0.1:%s"}
"""
_config_template = """
-- this is a silly test config, please do not do this in production.
- addAction(makeRule("udp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
- addSelfAnsweredResponseAction(AndRule({makeRule("udp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
- addAction(makeRule("tcp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
- addSelfAnsweredResponseAction(AndRule({makeRule("tcp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
+ addAction(SuffixMatchNodeRule("udp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
+ addSelfAnsweredResponseAction(AndRule({SuffixMatchNodeRule("udp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
+ addAction(SuffixMatchNodeRule("tcp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
+ addSelfAnsweredResponseAction(AndRule({SuffixMatchNodeRule("tcp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
newServer{address="127.0.0.1:%s"}
"""
--- /dev/null
+#!/usr/bin/env python
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestSize(DNSDistTest):
+
+ _payloadSize = 49
+ _config_template = """
+ addAction(PayloadSizeRule("smaller", %d), SpoofAction("192.0.2.1"))
+ addAction(PayloadSizeRule("greater", %d), SpoofAction("192.0.2.2"))
+ addAction(PayloadSizeRule("equal", %d), SpoofAction("192.0.2.3"))
+ newServer{address="127.0.0.1:%d"}
+ """
+ _config_params = ['_payloadSize', '_payloadSize', '_payloadSize', '_testServerPort']
+
+ def testPayloadSize(self):
+ """
+ Size: Check that PayloadSizeRule works
+ """
+ name = 'payload.size.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.3')
+ expectedResponse.answer.append(rrset)
+ self.assertEqual(len(query.to_wire()), self._payloadSize)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+class TestTruncateOversizedResponse(DNSDistTest):
+
+ _payloadSize = 512
+ _config_template = """
+ addResponseAction(PayloadSizeRule("greater", %d), TCResponseAction())
+ newServer{address="127.0.0.1:%d"}
+ """
+ _config_params = ['_payloadSize', '_testServerPort']
+
+ def testTruncateOversizedResponse(self):
+ """
+ Size: Truncate oversized response
+ """
+ name = 'truncate-oversized.size.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'TXT', 'IN')
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.TC
+
+ backendResponse = dns.message.make_response(query)
+ content = ''
+ for i in range(2):
+ if len(content) > 0:
+ content = content + ' '
+ content = content + 'A' * 255
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.TXT,
+ content)
+ backendResponse.answer.append(rrset)
+ self.assertGreater(len(backendResponse.to_wire()), self._payloadSize)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response=backendResponse)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response=backendResponse)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(receivedResponse, backendResponse)
class TestSpoofingSpoof(DNSDistTest):
_config_template = """
- addAction(makeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}))
- addAction(makeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {aa=true}))
- addAction(makeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ad=true}))
- addAction(makeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=true}))
- addAction(makeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=false}))
- addAction(makeRule("spoofaction-ttl.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ttl=1500}))
- addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
+ addAction(SuffixMatchNodeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}))
+ addAction(SuffixMatchNodeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {aa=true}))
+ addAction(SuffixMatchNodeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ad=true}))
+ addAction(SuffixMatchNodeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=true}))
+ addAction(SuffixMatchNodeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=false}))
+ addAction(SuffixMatchNodeRule("spoofaction-ttl.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ttl=1500}))
+ addAction(SuffixMatchNodeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
- addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001"))
- addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
- addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
- addAction(AndRule{makeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"}))
- addAction(AndRule{makeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"}))
+ addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001"))
+ addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
+ addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
+ addAction(AndRule{SuffixMatchNodeRule("rawchaos.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT), QClassRule(DNSClass.CHAOS)}, SpoofRawAction("\\005chaos"))
+ addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"}))
+ addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"}))
+ -- rfc8482
+ addAction(AndRule{SuffixMatchNodeRule("raw-any.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.ANY)}, SpoofRawAction("\\007rfc\\056\\052\\056\\050\\000", { typeForAny=DNSQType.HINFO }))
newServer{address="127.0.0.1:%s"}
"""
self.assertEqual(expectedResponse, receivedResponse)
self.assertEqual(receivedResponse.answer[0].ttl, 3600)
+ def testSpoofRawChaosAction(self):
+ """
+ Spoofing: Spoof a response from several raw bytes in QCLass CH
+ """
+ name = 'rawchaos.spoofing.tests.powerdns.com.'
+
+ # TXT CH
+ query = dns.message.make_query(name, 'TXT', 'CH')
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags &= ~dns.flags.AA
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.CH,
+ dns.rdatatype.TXT,
+ '"chaos"')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(expectedResponse, receivedResponse)
+ self.assertEqual(receivedResponse.answer[0].ttl, 60)
+
+ def testSpoofRawANYAction(self):
+ """
+ Spoofing: Spoof a HINFO response for ANY queries
+ """
+ name = 'raw-any.spoofing.tests.powerdns.com.'
+
+ query = dns.message.make_query(name, 'ANY', 'IN')
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags &= ~dns.flags.AA
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.HINFO,
+ '"rfc8482" ""')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(expectedResponse, receivedResponse)
+ self.assertEqual(receivedResponse.answer[0].ttl, 60)
+
def testSpoofRawActionMulti(self):
"""
Spoofing: Spoof a response from several raw bytes
return DNSAction.Spoof, "spoofedcname.spoofing.tests.powerdns.com."
end
- addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
- addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
+ addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
+ addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
function spoofrawrule(dq)
if dq.qtype == DNSQType.A then
addAction("lua-raw-packet.spoofing.tests.powerdns.com.", LuaAction(spoofpacket))
local rawResponse="\\000\\000\\129\\133\\000\\001\\000\\000\\000\\000\\000\\000\\019rule\\045lua\\045raw\\045packet\\008spoofing\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001"
- addAction(AndRule{QTypeRule(DNSQType.A), makeRule("rule-lua-raw-packet.spoofing.tests.powerdns.com.")}, SpoofPacketAction(rawResponse, string.len(rawResponse)))
+ addAction(AndRule{QTypeRule(DNSQType.A), SuffixMatchNodeRule("rule-lua-raw-packet.spoofing.tests.powerdns.com.")}, SpoofPacketAction(rawResponse, string.len(rawResponse)))
local ffi = require("ffi")
import requests
import socket
import struct
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class TestBrokenTCPFastOpen(DNSDistTest):
# because, contrary to the other ones, its
# TCP responder will accept a connection, read the
# query then just close the connection right away
- _testServerPort = 5410
+ _testServerPort = pickAvailablePort()
_testServerRetries = 5
_webTimeout = 2.0
- _webServerPort = 8083
+ _webServerPort = pickAvailablePort()
_webServerBasicAuthPassword = 'secret'
_webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
_webServerAPIKey = 'apisecret'
# Normal responder
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
# Close the connection right after reading the query
cls._TCPResponder = threading.Thread(name='Broken TCP Responder', target=cls.BrokenTCPResponder, args=[cls._testServerPort])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
def testTCOFastOpenOnCloseAfterRead(self):
import struct
import time
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
try:
range = xrange
# this test suite uses a different responder port
# because it uses a different health check configuration
- _testServerPort = 5395
+ _testServerPort = pickAvailablePort()
_answerUnexpected = True
_tcpIdleTimeout = 2
# this test suite uses a different responder port
# because it uses a different health check configuration
- _testServerPort = 5395
+ _testServerPort = pickAvailablePort()
_answerUnexpected = True
_skipListeningOnCL = True
import threading
import time
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
try:
range = xrange
# because, contrary to the other ones, its
# responders allow trailing data and multiple responses,
# and we don't want to mix things up.
- _testServerPort = 5361
+ _testServerPort = pickAvailablePort()
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
+ _tlsServerPort = pickAvailablePort()
_tcpSendTimeout = 60
_config_template = """
newServer{address="127.0.0.1:%s"}
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, True])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, True, True])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
def testTCPShortRead(self):
import subprocess
import time
import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class TLSTests(object):
self.assertEqual(names, ['tls.tests.dnsdist.org', 'powerdns.com', '127.0.0.1'])
serialNumber = cert['serialNumber']
- self.generateNewCertificateAndKey()
+ self.generateNewCertificateAndKey('server-tls')
self.sendConsoleCommand("reloadAllCertificates()")
conn.close()
_extraStartupSleep = 1
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
+ _serverKey = 'server-tls.key'
+ _serverCert = 'server-tls.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
+ _tlsServerPort = pickAvailablePort()
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%s")
"""
_config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
+ @classmethod
+ def setUpClass(cls):
+ cls.generateNewCertificateAndKey('server-tls')
+ cls.startResponders()
+ cls.startDNSDist()
+ cls.setUpSockets()
+
def testProvider(self):
- self.assertEquals(self.getTLSProvider(), "openssl")
+ self.assertEqual(self.getTLSProvider(), "openssl")
class TestGnuTLS(DNSDistTest, TLSTests):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverKey = 'server.key'
- _serverCert = 'server.chain'
+ _serverKey = 'server-tls.key'
+ _serverCert = 'server-tls.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
+ _tlsServerPort = pickAvailablePort()
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%s")
"""
_config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
+ @classmethod
+ def setUpClass(cls):
+ cls.generateNewCertificateAndKey('server-tls')
+ cls.startResponders()
+ cls.startDNSDist()
+ cls.setUpSockets()
+
def testProvider(self):
- self.assertEquals(self.getTLSProvider(), "gnutls")
+ self.assertEqual(self.getTLSProvider(), "gnutls")
class TestDOTWithCache(DNSDistTest):
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
+ _tlsServerPort = pickAvailablePort()
_config_template = """
newServer{address="127.0.0.1:%s"}
# this test suite uses a different responder port
# because it uses a different health check configuration
- _testServerPort = 5395
+ _testServerPort = pickAvailablePort()
_answerUnexpected = True
_serverKey = 'server.key'
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
+ _tlsServerPort = pickAvailablePort()
_skipListeningOnCL = True
_tcpIdleTimeout = 2
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
+ _tlsServerPort = pickAvailablePort()
_config_template = """
function checkDOT(dq)
class TestPKCSTLSCertificate(DNSDistTest, TLSTests):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _serverCert = 'server.p12'
+ _serverCert = 'server-tls.p12'
_pkcsPassphrase = 'passw0rd'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8453
+ _tlsServerPort = pickAvailablePort()
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%s")
addAction(SNIRule("powerdns.com"), SpoofAction("1.2.3.4"))
"""
_config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_serverCert', '_pkcsPassphrase', '_tlsServerPort']
+
+ @classmethod
+ def setUpClass(cls):
+ cls.generateNewCertificateAndKey('server-tls')
+ cls.startResponders()
+ cls.startDNSDist()
+ cls.setUpSockets()
import tempfile
import time
import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
try:
range = xrange
except NameError:
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohWithNGHTTP2ServerPort = pickAvailablePort()
+ _dohWithH2OServerPort = pickAvailablePort()
_numberOfKeys = 0
_config_template = """
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false, library='nghttp2' })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false, library='h2o' })
"""
- _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
+ _config_params = ['_testServerPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_numberOfKeys', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
def testNoSessionResumption(self):
"""
Session Resumption: DoH (disabled)
"""
- self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/no-session.out.doh', None, allowNoTicket=True))
- self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/no-session.out.doh', '/tmp/no-session.out.doh', allowNoTicket=True))
+ for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]:
+ self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/no-session.out.doh', None, allowNoTicket=True))
+ self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/no-session.out.doh', '/tmp/no-session.out.doh', allowNoTicket=True))
@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest):
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _dohServerPort = 8443
+ _dohWithNGHTTP2ServerPort = pickAvailablePort()
+ _dohWithH2OServerPort = pickAvailablePort()
_numberOfKeys = 5
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%s")
newServer{address="127.0.0.1:%s"}
- addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, library='nghttp2' })
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, library='h2o' })
"""
- _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
+ _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_numberOfKeys', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
def testSessionResumption(self):
"""
Session Resumption: DoH
"""
- self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', None))
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+ for (port, bindIdx) in [(self._dohWithNGHTTP2ServerPort, 0), (self._dohWithH2OServerPort, 1)]:
+ self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', None))
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
- # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
- for _ in range(self._numberOfKeys - 1):
- self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
+ # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
+ for _ in range(self._numberOfKeys - 1):
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
- # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+ # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
- # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
- for _ in range(self._numberOfKeys - 1):
- self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
+ # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
+ for _ in range(self._numberOfKeys - 1):
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
- # rotate the TLS session ticket keys several times, not keeping any key around this time!
- for _ in range(self._numberOfKeys):
- self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
+ # rotate the TLS session ticket keys several times, not keeping any key around this time!
+ for _ in range(self._numberOfKeys):
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
- # we should not be able to resume
- self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+ # we should not be able to resume
+ self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
- # generate a file containing _numberOfKeys ticket keys
- self.generateTicketKeysFile(self._numberOfKeys, '/tmp/ticketKeys.1')
- self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2')
- # load all ticket keys from the file
- self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
+ # generate a file containing _numberOfKeys ticket keys
+ self.generateTicketKeysFile(self._numberOfKeys, '/tmp/ticketKeys.1')
+ self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2')
+ # load all ticket keys from the file
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
- # create a new session, resume it
- self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', None))
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+ # create a new session, resume it
+ self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', None))
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
- # reload the same keys
- self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
+ # reload the same keys
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
- # should still be able to resume
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+ # should still be able to resume
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
- # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
- for _ in range(self._numberOfKeys - 1):
- self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
- # should still be able to resume
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+ # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
+ for _ in range(self._numberOfKeys - 1):
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
+ # should still be able to resume
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
- # reload the same keys
- self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
- # since the last key was only present in memory, we should not be able to resume
- self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+ # reload the same keys
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
+ # since the last key was only present in memory, we should not be able to resume
+ self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
- # but now we can
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+ # but now we can
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
- # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
- self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2')
- self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')")
- # we should be able to resume, and the ticket should be re-encrypted with the new key (NOTE THAT we store into a new file!!)
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh'))
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True))
+ # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
+ self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2')
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.2')")
+ # we should be able to resume, and the ticket should be re-encrypted with the new key (NOTE THAT we store into a new file!!)
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh'))
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True))
- # rotate all keys, we should not be able to resume
- for _ in range(self._numberOfKeys):
- self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
- self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.3', '/tmp/session.doh.2'))
+ # rotate all keys, we should not be able to resume
+ for _ in range(self._numberOfKeys):
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
+ self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh.3', '/tmp/session.doh.2'))
- # reload from file 1, the old session should resume
- self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+ # reload from file 1, the old session should resume
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
- # reload from file 2, the latest session should resume
- self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')")
- self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True))
+ # reload from file 2, the latest session should resume
+ self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.2')")
+ self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True))
class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest):
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8443
+ _tlsServerPort = pickAvailablePort()
_numberOfKeys = 0
_config_template = """
newServer{address="127.0.0.1:%s"}
_serverCert = 'server.chain'
_serverName = 'tls.tests.dnsdist.org'
_caCert = 'ca.pem'
- _tlsServerPort = 8443
+ _tlsServerPort = pickAvailablePort()
_numberOfKeys = 5
_config_template = """
setKey("%s")
import threading
import clientsubnetoption
import dns
-from dnsdisttests import DNSDistTest, Queue
+from dnsdisttests import DNSDistTest, Queue, pickAvailablePort
+from proxyprotocolutils import ProxyProtocolUDPResponder, ProxyProtocolTCPResponder
class TestTeeAction(DNSDistTest):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _teeServerPort = 5390
+ _teeServerPort = pickAvailablePort()
+ _teeProxyServerPort = pickAvailablePort()
_toTeeQueue = Queue()
_fromTeeQueue = Queue()
+ _toTeeProxyQueue = Queue()
+ _fromTeeProxyQueue = Queue()
_config_template = """
setKey("%s")
controlSocket("127.0.0.1:%s")
newServer{address="127.0.0.1:%d"}
addAction(QTypeRule(DNSQType.A), TeeAction("127.0.0.1:%d", true))
addAction(QTypeRule(DNSQType.AAAA), TeeAction("127.0.0.1:%d", false))
+ addAction(QTypeRule(DNSQType.ANY), TeeAction("127.0.0.1:%d", false, '127.0.0.1', true))
"""
- _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_teeServerPort', '_teeServerPort']
+ _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_teeServerPort', '_teeServerPort', '_teeProxyServerPort']
@classmethod
def startResponders(cls):
print("Launching responders..")
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, True])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
cls._TeeResponder = threading.Thread(name='Tee Responder', target=cls.UDPResponder, args=[cls._teeServerPort, cls._toTeeQueue, cls._fromTeeQueue])
- cls._TeeResponder.setDaemon(True)
+ cls._TeeResponder.daemon = True
cls._TeeResponder.start()
+ cls._TeeProxyResponder = threading.Thread(name='Proxy Protocol Tee Responder', target=ProxyProtocolUDPResponder, args=[cls._teeProxyServerPort, cls._toTeeProxyQueue, cls._fromTeeProxyQueue])
+ cls._TeeProxyResponder.daemon = True
+ cls._TeeProxyResponder.start()
+
def testTeeWithECS(self):
"""
TeeAction: ECS
send-errors\t0
servfails\t0
tcp-drops\t0
+""" % (numberOfQueries, numberOfQueries, numberOfQueries))
+
+ def testTeeWithProxy(self):
+ """
+ TeeAction: Proxy
+ """
+ name = 'proxy.tee.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'ANY', 'IN')
+ response = dns.message.make_response(query)
+
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ numberOfQueries = 10
+ for _ in range(numberOfQueries):
+ # push the response to the Tee Proxy server
+ self._toTeeProxyQueue.put(response, True, 2.0)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ # retrieve the query from the Tee Proxy server
+ [payload, teedQuery] = self._fromTeeProxyQueue.get(True, 2.0)
+ self.checkMessageNoEDNS(query, dns.message.from_wire(teedQuery))
+ self.checkMessageProxyProtocol(payload, '127.0.0.1', '127.0.0.1', False)
+
+ # check the TeeAction stats
+ stats = self.sendConsoleCommand("getAction(0):printStats()")
+ self.assertEqual(stats, """noerrors\t%d
+nxdomains\t0
+other-rcode\t0
+queries\t%d
+recv-errors\t0
+refuseds\t0
+responses\t%d
+send-errors\t0
+servfails\t0
+tcp-drops\t0
""" % (numberOfQueries, numberOfQueries, numberOfQueries))
#!/usr/bin/env python
import threading
import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
class TestTrailingDataToBackend(DNSDistTest):
# because, contrary to the other ones, its
# responders allow trailing data and we don't want
# to mix things up.
- _testServerPort = 5360
+ _testServerPort = pickAvailablePort()
_verboseMode = True
_config_template = """
newServer{address="127.0.0.1:%s"}
# Respond REFUSED to queries with trailing data.
cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, dns.rcode.REFUSED])
- cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.daemon = True
cls._UDPResponder.start()
# Respond REFUSED to queries with trailing data.
cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, dns.rcode.REFUSED])
- cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.daemon = True
cls._TCPResponder.start()
def testTrailingPassthrough(self):
expectedQuery = dns.message.make_query(name, 'A', 'IN')
# 0x04 is IPv4, 0x11 (17) is UDP then 127.0.0.1 as source and destination
# and finally the ports, zeroed because we have no way to know them beforehand
- xpfData = "\# 14 04117f0000017f00000100000000"
+ xpfData = "\\# 14 04117f0000017f00000100000000"
rdata = dns.rdata.from_text(dns.rdataclass.IN, self._xpfCode, xpfData)
rrset = dns.rrset.from_rdata(".", 0, rdata)
expectedQuery.additional.append(rrset)
expectedQuery = dns.message.make_query(name, 'A', 'IN')
# 0x04 is IPv4, 0x06 (6) is TCP then 127.0.0.1 as source and destination
# and finally the ports, zeroed because we have no way to know them beforehand
- xpfData = "\# 14 04067f0000017f00000100000000"
+ xpfData = "\\# 14 04067f0000017f00000100000000"
rdata = dns.rdata.from_text(dns.rdataclass.IN, self._xpfCode, xpfData)
rrset = dns.rrset.from_rdata(".", 0, rdata)
expectedQuery.additional.append(rrset)
if cls._config_domains is not None:
conf.write("domains:\n")
- for domain, master in cls._config_domains.items():
- conf.write(" - domain: %s\n" % (domain))
- conf.write(" master: %s\n" % (master))
+ for item in cls._config_domains:
+ conf.write(" - domain: %s\n" % (item['domain']))
+ conf.write(" master: %s\n" % (item['master']))
+ if ('notify' in item) :
+ conf.write(" notify: %s\n" % (item['notify']))
ixfrdistcmd = [os.environ['IXFRDISTBIN'], '--config', conffile, '--debug']
dnspython==2.1.0
-nose>=1.3.7
+pytest
git+https://github.com/PowerDNS/xfrserver.git@0.1
requests
rm -rf ixfrdist.dir
mkdir ixfrdist.dir
-nosetests --with-xunit $@
+pytest --junitxml=pytest.xml $@
import dns.serial
import time
import itertools
+import socket
from ixfrdisttests import IXFRDistTest
from xfrserver.xfrserver import AXFRServer
""",
3: """
$ORIGIN example.
-@ 86400 SOA foo bar 3 2 3 4 5
+@ 86400 SOA foo bar 3 1500 3 4 5
@ 4242 NS ns1.example.
@ 4242 NS ns2.example.
ns1.example. 4242 A 192.0.2.1
global xfrServerPort
_xfrDone = 0
- _config_domains = { 'example': '127.0.0.1:' + str(xfrServerPort), # zone for actual XFR testing
- 'example2': '127.0.0.1:1', # bogus port is intentional - zone is intentionally unloadable
- # example3 # intentionally absent for 'unconfigured zone' testing
- 'example4': '127.0.0.1:' + str(xfrServerPort) } # for testing how ixfrdist deals with getting the wrong zone on XFR
+ _config_domains = [
+ # zone for actual XFR testing
+ {"domain" : "example", "master" : "127.0.0.1:" + str(xfrServerPort), 'notify' : "127.0.0.1:" + str(xfrServerPort + 1)},
+ # bogus port is intentional - zone is intentionally unloadable
+ {"domain" : "example2", "master" : "127.0.0.1:1"},
+ # for testing how ixfrdist deals with getting the wrong zone on XFR
+ {"domain" : "example4", "master" : '127.0.0.1:' + str(xfrServerPort)},
+
+ ]
_loaded_serials = []
@classmethod
def tearDownClass(cls):
cls.tearDownIXFRDist()
- def waitUntilCorrectSerialIsLoaded(self, serial, timeout=10):
+ def waitUntilCorrectSerialIsLoaded(self, serial, timeout=10, notify=False):
global xfrServer
xfrServer.moveToSerial(serial)
+ if notify:
+ notif = dns.message.make_query('example.', 'SOA')
+ notif.set_opcode(dns.opcode.NOTIFY)
+ notify_response = self.sendUDPQuery(notif)
+ assert notify_response.rcode() == dns.rcode.NOERROR
+
def get_current_serial():
query = dns.message.make_query('example.', 'SOA')
response_message = self.sendUDPQuery(query)
def checkFullZone(self, serial):
global zones
-
+
# FIXME: 90% duplication from _getRecordsForSerial
zone = []
for i in dns.zone.from_text(zones[serial], relativize=False).iterate_rdatasets():
def test_b_UDP_SOA_existing(self):
query = dns.message.make_query('example.', 'SOA')
expected = dns.message.make_response(query)
+ expected.flags |= dns.flags.AA
expected.answer.append(xfrServer._getSOAForSerial(2))
response = self.sendUDPQuery(query)
self.checkIXFR(2,3)
self.checkIXFR(1,3)
- self.waitUntilCorrectSerialIsLoaded(4)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.bind(("127.0.0.1", xfrServerPort + 1))
+ sock.settimeout(2)
+
+ self.waitUntilCorrectSerialIsLoaded(serial=4, timeout=10, notify=True)
+
+ # recv the forwarded NOTIFY
+ data, addr = sock.recvfrom(4096)
+ received = dns.message.from_wire(data)
+ sock.close()
+
+ notif = dns.message.make_query('example.', 'SOA')
+ notif.set_opcode(dns.opcode.NOTIFY)
+ notif.flags |= dns.flags.AA
+ notif.flags &= ~dns.flags.RD
+ notif.id = received.id
+
+ self.assertEqual(received, notif)
+
self.checkFullZone(4)
self.checkIXFR(3,4)
self.checkIXFR(2,4)
webserver-address: %s
"""
- _config_domains = {'example': '127.0.0.1:' + str(xfrServerPort)}
+ _config_domains = [{'domain' : 'example', 'master' : '127.0.0.1:' + str(xfrServerPort)}]
metric_prog_stats = ["ixfrdist_uptime_seconds", "ixfrdist_domains",
"ixfrdist_unknown_domain_inqueries_total",
"ixfrdist_sys_msec", "ixfrdist_user_msec",
"ixfrdist_real_memory_usage",
- "ixfrdist_fd_usage"]
+ "ixfrdist_fd_usage",
+ "ixfrdist_notimp"]
metric_domain_stats = ["ixfrdist_soa_serial", "ixfrdist_soa_checks_total",
"ixfrdist_soa_checks_failed_total",
"ixfrdist_soa_inqueries_total",
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 &
$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
--- /dev/null
+
+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
};
zone "test.com"{
- type master;
+ type primary;
file "./test.com";
};
};
zone "minimal.com"{
- type master;
+ type primary;
file "./minimal.com";
};
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 \
};
zone "minimal.com"{
- type master;
+ type primary;
file "./minimal.com";
};
};
zone "example.com" {
- type master;
+ type primary;
file "example.com.zone";
};
cat >> ./named.conf << __EOF__
zone "."{
- type master;
+ type primary;
file "../../regression-tests.rootzone/zones/ROOT";
};
__EOF__
};
zone "minimal.com"{
- type master;
+ type primary;
file "./minimal.com";
};
minimal-responses yes;
};
zone "example.com"{
- type master;
+ type primary;
file "example.com";
};
zone "test.com"{
- type master;
+ type primary;
file "test.com";
};
EOF
{
$RUNWRAPPER $PDNS --daemon=no --local-port=$port --config-dir=. --module-dir=../regression-tests/modules \
--config-name=gsqlite3-master --socket-dir=./ --no-shuffle \
- --master=yes --local-address=127.0.0.1 \
+ --primary=yes --local-address=127.0.0.1 \
--query-local-address=127.0.0.1 --cache-ttl=$cachettl --dname-processing --allow-axfr-ips= &
}
$RUNWRAPPER $PDNS --daemon=no --local-port=$slaveport --config-dir=. --module-dir=../regression-tests/modules \
--config-name=gsqlite3-slave --socket-dir=./ --no-shuffle --local-address=127.0.0.2 \
- --slave --retrieval-threads=4 --slave=yes --superslave=yes --query-local-address=127.0.0.2 \
- --slave-cycle-interval=300 --allow-unsigned-notify=no --allow-unsigned-supermaster=no &
+ --secondary --retrieval-threads=4 --autosecondary=yes --query-local-address=127.0.0.2 \
+ --xfr-cycle-interval=300 --allow-unsigned-notify=no --allow-unsigned-autoprimary=no &
}
check_process ()
minimal-responses yes;
};
zone "example.com"{
- type master;
+ type primary;
file "example.com";
};
zone "test.com"{
- type master;
+ type primary;
file "test.com";
};
EOF
{
$RUNWRAPPER $PDNS --daemon=no --local-port=$port --config-dir=. --module-dir=../regression-tests/modules \
--config-name=gsqlite3-master --socket-dir=./ --no-shuffle \
- --master=yes --local-address=127.0.0.1 \
+ --primary=yes --local-address=127.0.0.1 \
--query-local-address=127.0.0.1 --cache-ttl=$cachettl --dname-processing --allow-axfr-ips= &
}
$RUNWRAPPER $PDNS --daemon=no --local-port=$slaveport --config-dir=. --module-dir=../regression-tests/modules \
--config-name=gsqlite3-slave --socket-dir=./ --no-shuffle --local-address=127.0.0.2 \
- --slave --retrieval-threads=4 --slave=yes --superslave=yes --query-local-address=127.0.0.2 \
- --slave-cycle-interval=300 --dname-processing &
+ --secondary --retrieval-threads=4 --autosecondary=yes --query-local-address=127.0.0.2 \
+ --xfr-cycle-interval=300 --dname-processing &
}
check_process ()
-229dad9ea0464a429685d3dda8a8e9ef ../regression-tests/zones/example.com
+2ed2eb54a4cb3fd105aa78e6d4c99685 ../regression-tests/zones/example.com
f8f0d7b157495ec8ee70851e3d5cb65e ../regression-tests/zones/test.com
e5e3ee998d151fe194b98997eaa36c53 ../regression-tests/zones/test.dyndns
dee3e8b568549d9450134b555ca73990 ../regression-tests/zones/sub.test.dyndns
9aeed2c26d0c3ba3baf22dfa9568c451 ../regression-tests/zones/2.0.192.in-addr.arpa
99c73e8b5db5781fec1ac3fa6a2662a9 ../regression-tests/zones/cryptokeys.org
1f9e19be0cff67330f3a0a5347654f91 ../regression-tests/zones/hiddencryptokeys.org
-964425367cec0d828222b144c4e1c540 ../modules/tinydnsbackend/data
-f3932b1df41d683f47516455b571c358 ../modules/tinydnsbackend/data.cdb
+e85d67cb577cf1de3127e439e7529311 ../modules/tinydnsbackend/data
+7715e725358f969aa92e46ae9be0c464 ../modules/tinydnsbackend/data.cdb
import os.path
import glob
-e = xml.etree.ElementTree.parse('nosetests.xml')
+e = xml.etree.ElementTree.parse('pytest.xml')
root = e.getroot()
for child in root:
disable-syslog=yes
log-common-errors=yes
statistics-interval=0
+"""
+ _config_template_yaml_default = """
+recursor:
+ daemon: false
+ threads: 2
+ include_dir: %s
+recordcache:
+ max_ttl: 15
+incoming:
+ listen:
+ - 127.0.0.1
+packetcache:
+ ttl: 15
+ servfail_ttl: 15
+outgoing:
+ dont_query: []
+logging:
+ trace: true
+ disable_syslog: true
+ common_errors: true
+ loglevel: 9
+ statistics_interval: 0
"""
_config_template = """
"""
roothints.write(cls._roothints)
conf.write("hint-file=%s\n" % roothintspath)
+ @classmethod
+ def generateRecursorYamlConfig(cls, confdir):
+ params = tuple([getattr(cls, param) for param in cls._config_params])
+ if len(params):
+ print(params)
+
+ recursorconf = os.path.join(confdir, 'recursor.yml')
+
+ with open(recursorconf, 'w') as conf:
+ conf.write("# Autogenerated by recursortests.py\n")
+ conf.write(cls._config_template_yaml_default % confdir)
+ recursorconf = os.path.join(confdir, 'recursor01.yml')
+ with open(recursorconf, 'w') as conf:
+ conf.write(cls._config_template % params)
+ conf.write("\n")
+ recursorconf = os.path.join(confdir, 'recursor02.yml')
+ with open(recursorconf, 'w') as conf:
+ conf.write("recursor:\n")
+ conf.write(" socket_dir: %s\n" % confdir)
+ if cls._lua_config_file or cls._root_DS:
+ luaconfpath = os.path.join(confdir, 'conffile.lua')
+ with open(luaconfpath, 'w') as luaconf:
+ if cls._root_DS:
+ luaconf.write("addTA('.', '%s')\n" % cls._root_DS)
+ if cls._lua_config_file:
+ luaconf.write(cls._lua_config_file)
+ conf.write(" lua_config_file: %s\n" % luaconfpath)
+ if cls._lua_dns_script_file:
+ luascriptpath = os.path.join(confdir, 'dnsscript.lua')
+ with open(luascriptpath, 'w') as luascript:
+ luascript.write(cls._lua_dns_script_file)
+ conf.write(" lua_dns_script: %s\n" % luascriptpath)
+ if cls._roothints:
+ roothintspath = os.path.join(confdir, 'root.hints')
+ with open(roothintspath, 'w') as roothints:
+ roothints.write(cls._roothints)
+ conf.write(" hint_file: %s\n" % roothintspath)
+
@classmethod
def startResponders(cls):
pass
dnspython>=1.11
-nose>=1.3.7
+pytest
protobuf>=2.5; sys_platform != 'darwin'
protobuf>=3.0; sys_platform == 'darwin'
pyasn1==0.4.8
# LIBFAKETIME is only added to LD_PRELOAD by the pyton code when needed
if [ "${LIBASAN}" != "" -o "${LIBAUTHBIND}" != "" ]; then
-LD_PRELOAD="${LIBASAN} ${LIBAUTHBIND}" nosetests -I test_WellKnown.py --with-xunit $@
+LD_PRELOAD="${LIBASAN} ${LIBAUTHBIND}" pytest --ignore=test_WellKnown.py --junitxml=pytest.xml $@
else
-nosetests -I test_WellKnown.py --with-xunit $@
+pytest --ignore=test_WellKnown.py --junitxml=pytest.xml $@
fi
_config_template = """
dnssec=validate
aggressive-nsec-cache-size=10000
+ aggressive-cache-max-nsec3-hash-cost=204
+ nsec3-max-iterations=150
webserver=yes
webserver-port=%d
webserver-address=127.0.0.1
import struct
import threading
import time
+import sys
+from unittest import SkipTest
from recursortests import RecursorTest
from twisted.internet.protocol import DatagramProtocol
Ensure the rcode is BADVERS when we send an unsupported EDNS version and
the query is not processed any further.
"""
+ if sys.version_info >= (3, 11) and sys.version_info <= (3, 11, 3):
+ raise SkipTest("Test skipped, see https://github.com/PowerDNS/pdns/pull/12912")
query = dns.message.make_query('version.bind.', 'TXT', 'CH', use_edns=5,
payload=4096)
response = self.sendUDPQuery(query)
import dns
import os
import extendederrors
+import pytest
from recursortests import RecursorTest
super(ExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir)
+ @pytest.mark.skip(reason="sidnlabs no longer serves thiss until further notice")
def testNotIncepted(self):
qname = 'signotincepted.bad-dnssec.wb.sidnlabs.nl.'
query = dns.message.make_query(qname, 'A', want_dnssec=True)
self.assertEqual(res.options[0].otype, 15)
self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(8, b''))
+ @pytest.mark.skip(reason="sidnlabs no longer serves thiss until further notice")
def testExpired(self):
qname = 'sigexpired.bad-dnssec.wb.sidnlabs.nl.'
query = dns.message.make_query(qname, 'A', want_dnssec=True)
self.assertEqual(res.options[0].otype, 15)
self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(6, b''))
+ @pytest.mark.skip(reason="sidnlabs no longer serves thiss until further notice")
def testBogus(self):
qname = 'bogussig.ok.bad-dnssec.wb.sidnlabs.nl.'
query = dns.message.make_query(qname, 'A', want_dnssec=True)
def generateRecursorConfig(cls, confdir):
super(NoExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir)
+ @pytest.mark.skip(reason="sidnlabs no longer serves thiss until further notice")
def testNotIncepted(self):
qname = 'signotincepted.bad-dnssec.wb.sidnlabs.nl.'
query = dns.message.make_query(qname, 'A', want_dnssec=True)
""" postresolve_ffi: test that we can do a DROP for a name and type combo"""
query = dns.message.make_query('example', 'TXT')
res = self.sendUDPQuery(query)
- self.assertEquals(res, None)
+ self.assertEqual(res, None)
def testNXDOMAIN(self):
""" postresolve_ffi: test that we can return a NXDOMAIN for a name and type combo"""
sender = getattr(self, method)
res = sender(notify)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
- self.assertEquals(res.opcode(), 4)
+ self.assertEqual(res.opcode(), 4)
print(res)
- self.assertEquals(res.question[0].to_text(), 'example. IN SOA')
+ self.assertEqual(res.question[0].to_text(), 'example. IN SOA')
self.checkRecordCacheMetrics(3, 1)
def getFirstProtobufMessage(self, retries=1, waitTime=1):
msg = None
- print("in getFirstProtobufMessage")
+ #print("in getFirstProtobufMessage")
for param in protobufServersParameters:
- print(param.port)
+ #print(param.port)
failed = 0
- while param.queue.empty:
- print(failed)
- print(retries)
+ while param.queue.empty():
+ #print(failed)
+ #print(retries)
if failed >= retries:
break
failed = failed + 1
- print("waiting")
+ #print("waiting")
time.sleep(waitTime)
self.assertFalse(param.queue.empty())
if oldmsg is not None:
self.assertEqual(msg, oldmsg)
- print(msg)
+ #print(msg)
return msg
def emptyProtoBufQueue(self):
self.assertEqual(msg.response.appliedPolicyKind, kind)
def checkProtobufTags(self, msg, tags):
- print(tags)
- print('---')
- print(msg.response.tags)
+ #print(tags)
+ #print('---')
+ #print(msg.response.tags)
self.assertEqual(len(msg.response.tags), len(tags))
for tag in msg.response.tags:
self.assertTrue(tag in tags)
def checkProtobufMetas(self, msg, metas):
- print(metas)
- print('---')
- print(msg.meta)
+ #print(metas)
+ #print('---')
+ #print(msg.meta)
self.assertEqual(len(msg.meta), len(metas))
for m in msg.meta:
self.assertTrue(m.HasField('key'))
self.assertEqual(msg.response.rcode, 65536)
def checkProtobufIdentity(self, msg, requestorId, deviceId, deviceName):
- print(msg)
+ #print(msg)
self.assertTrue((requestorId == '') == (not msg.HasField('requestorId')))
self.assertTrue((deviceId == b'') == (not msg.HasField('deviceId')))
self.assertTrue((deviceName == '') == (not msg.HasField('deviceName')))
self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
tags = [ self._tag_from_gettag ] + self._tags
+ #print(msg)
self.checkProtobufTags(msg, tags)
self.checkNoRemainingMessage()
+ # Again to check PC case
+ res = self.sendUDPQuery(query)
+ self.assertRRsetInAnswer(res, expected)
+
+ # check the protobuf messages corresponding to the UDP query and answer
+ msg = self.getFirstProtobufMessage()
+ self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+ self.checkProtobufTags(msg, [ self._tag_from_gettag ])
+ # then the response
+ msg = self.getFirstProtobufMessage()
+ self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+ self.assertEqual(len(msg.response.rrs), 1)
+ rr = msg.response.rrs[0]
+ # time may have passed, so do not check TTL
+ self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15, checkTTL=False)
+ self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
+ tags = [ self._tag_from_gettag ] + self._tags
+ self.checkProtobufTags(msg, tags)
+ self.checkNoRemainingMessage()
+
+class ProtobufTagCacheTest(TestRecursorProtobuf):
+ """
+ This test makes sure that we correctly cache tags (actually not cache them)
+ """
+
+ _confdir = 'ProtobufTagCache'
+ _config_template = """
+auth-zones=example=configs/%s/example.zone""" % _confdir
+ _lua_config_file = """
+ protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true } )
+ """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+ _lua_dns_script_file = """
+ function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
+ if qname:equal('tagged.example.') then
+ return 0, { '' .. math.random() }
+ end
+ return 0
+ end
+ """
+
+ def testTagged(self):
+ name = 'tagged.example.'
+ expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
+ query = dns.message.make_query(name, 'A', want_dnssec=True)
+ query.flags |= dns.flags.CD
+ res = self.sendUDPQuery(query)
+ self.assertRRsetInAnswer(res, expected)
+
+ msg = self.getFirstProtobufMessage()
+ self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+ self.assertEqual(len(msg.response.rrs), 1)
+ rr = msg.response.rrs[0]
+ # we have max-cache-ttl set to 15
+ self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+ self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
+ self.checkNoRemainingMessage()
+ self.assertEqual(len(msg.response.tags), 1)
+ ts1 = msg.response.tags[0]
+
+ # Again to check PC case
+ res = self.sendUDPQuery(query)
+ self.assertRRsetInAnswer(res, expected)
+
+ msg = self.getFirstProtobufMessage()
+ self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+ self.assertEqual(len(msg.response.rrs), 1)
+ rr = msg.response.rrs[0]
+ # time may have passed, so do not check TTL
+ self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15, checkTTL=False)
+ self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
+ self.checkNoRemainingMessage()
+ self.assertEqual(len(msg.response.tags), 1)
+ ts2 = msg.response.tags[0]
+ self.assertNotEqual(ts1, ts2)
+
class ProtobufSelectedFromLuaTest(TestRecursorProtobuf):
"""
This test makes sure that we correctly export queries and responses but only if they have been selected from Lua.
--- /dev/null
+import dns
+import os
+from recursortests import RecursorTest
+
+class testRDNotAllowed(RecursorTest):
+ _confdir = 'RDFlagNotAllowed'
+
+ _config_template = """
+"""
+ def testRD0(self):
+ query = dns.message.make_query('ns.secure.example', 'A', want_dnssec=True)
+ query.flags |= dns.flags.AD
+ query.flags &= ~dns.flags.RD
+
+ res = self.sendUDPQuery(query)
+
+ self.assertRcodeEqual(res, dns.rcode.REFUSED)
+ self.assertAnswerEmpty(res)
+
+class testRDAllowed(RecursorTest):
+ _confdir = 'RDFlagAllowed'
+
+ _config_template = """
+ disable-packetcache=yes
+ allow-no-rd=yes
+"""
+ def testRD0(self):
+ expected = dns.rrset.from_text('ns.secure.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.9'.format(prefix=self._PREFIX))
+ query = dns.message.make_query('ns.secure.example', 'A', want_dnssec=True)
+ query.flags |= dns.flags.AD
+ query.flags &= ~dns.flags.RD
+
+ # First time empty answer
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertAnswerEmpty(res)
+
+ # Second time with RD=1 fills the record cache
+ query.flags |= dns.flags.RD
+
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertMessageIsAuthenticated(res)
+ self.assertRRsetInAnswer(res, expected)
+ self.assertMatchingRRSIGInAnswer(res, expected)
+
+ # Third time with RD=0 retrieves record cache content
+ query.flags &= ~dns.flags.RD
+
+ res = self.sendUDPQuery(query)
+ self.assertMessageIsAuthenticated(res)
+ self.assertRRsetInAnswer(res, expected)
+ self.assertMatchingRRSIGInAnswer(res, expected)
self._targetSerial = 1
self._serverPort = port
listener = threading.Thread(name='RPZ Listener', target=self._listener, args=[])
- listener.setDaemon(True)
+ listener.daemon = True
listener.start()
def getCurrentSerial(self):
break
wire = answer.to_wire()
- conn.send(struct.pack("!H", len(wire)))
+ lenprefix = struct.pack("!H", len(wire))
+
+ for b in lenprefix:
+ conn.send(bytes([b]))
+ time.sleep(0.5)
+
conn.send(wire)
self._currentSerial = serial
break
thread = threading.Thread(name='RPZ Connection Handler',
target=self._connectionHandler,
args=[conn])
- thread.setDaemon(True)
+ thread.daemon = True
thread.start()
except socket.error as e:
log-rpz-changes=yes
""" % (_confdir, _wsPort, _wsPassword, _apiKey)
- def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False, singleCheck=False):
+ def sendNotify(self):
+ notify = dns.message.make_query('zone.rpz', 'SOA', want_dnssec=False)
+ notify.set_opcode(4) # notify
+ res = self.sendUDPQuery(notify)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(res.opcode(), 4)
+ self.assertEqual(res.question[0].to_text(), 'zone.rpz. IN SOA')
+
+ def assertAdditionalHasSOA(self, msg):
+ if not isinstance(msg, dns.message.Message):
+ raise TypeError("msg is not a dns.message.Message but a %s" % type(msg))
+
+ found = False
+ for rrset in msg.additional:
+ if rrset.rdtype == dns.rdatatype.SOA:
+ found = True
+ break
+
+ if not found:
+ raise AssertionError("No SOA record found in the authority section:\n%s" % msg.to_text())
+
+ def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False, singleCheck=False, soa=False):
query = dns.message.make_query(name, 'A', want_dnssec=True)
query.flags |= dns.flags.CD
if adQuery:
expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
self.assertRRsetInAnswer(res, expected)
+ if soa:
+ self.assertAdditionalHasSOA(res)
if singleCheck:
break
def checkNotBlocked(self, name, adQuery=False, singleCheck=False):
self.checkBlocked(name, False, adQuery, singleCheck)
- def checkCustom(self, qname, qtype, expected):
+ def checkCustom(self, qname, qtype, expected, soa=False):
query = dns.message.make_query(qname, qtype, want_dnssec=True)
query.flags |= dns.flags.CD
for method in ("sendUDPQuery", "sendTCPQuery"):
res = sender(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertRRsetInAnswer(res, expected)
+ if soa:
+ self.assertAdditionalHasSOA(res)
- def checkNoData(self, qname, qtype):
+ def checkNoData(self, qname, qtype, soa=False):
query = dns.message.make_query(qname, qtype, want_dnssec=True)
query.flags |= dns.flags.CD
for method in ("sendUDPQuery", "sendTCPQuery"):
res = sender(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertEqual(len(res.answer), 0)
+ if soa:
+ self.assertAdditionalHasSOA(res)
def checkNXD(self, qname, qtype='A'):
query = dns.message.make_query(qname, qtype, want_dnssec=True)
self.assertEqual(len(res.answer), 0)
self.assertEqual(len(res.authority), 1)
- def checkTruncated(self, qname, qtype='A'):
+ def checkTruncated(self, qname, qtype='A', soa=False):
query = dns.message.make_query(qname, qtype, want_dnssec=True)
query.flags |= dns.flags.CD
res = self.sendUDPQuery(query)
self.assertMessageHasFlags(res, ['QR', 'RA', 'RD', 'CD', 'TC'])
self.assertEqual(len(res.answer), 0)
self.assertEqual(len(res.authority), 0)
- self.assertEqual(len(res.additional), 0)
+ if soa:
+ self.assertAdditionalHasSOA(res)
res = self.sendTCPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
global rpzServerPort
_lua_config_file = """
-- The first server is a bogus one, to test that we correctly fail over to the second one
- rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1 })
+ rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1, includeSOA=true})
""" % (rpzServerPort)
_confdir = 'RPZXFR'
_wsPort = 8042
webserver-password=%s
api-key=%s
disable-packetcache
+allow-notify-from=127.0.0.0/8
+allow-notify-for=zone.rpz
""" % (_confdir, _wsPort, _wsPassword, _apiKey)
_xfrDone = 0
raise AssertionError("Waited %d seconds for the serial to be updated to %d but the serial is still %d" % (timeout, serial, currentSerial))
def testRPZ(self):
+ # Fresh RPZ does not need a notify
self.waitForTCPSocket("127.0.0.1", self._wsPort)
# first zone, only a should be blocked
self.waitUntilCorrectSerialIsLoaded(1)
self.checkRPZStats(1, 1, 1, self._xfrDone)
- self.checkBlocked('a.example.')
+ self.checkBlocked('a.example.', soa=True)
self.checkNotBlocked('b.example.')
self.checkNotBlocked('c.example.')
# second zone, a and b should be blocked
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(2)
self.checkRPZStats(2, 2, 1, self._xfrDone)
- self.checkBlocked('a.example.')
- self.checkBlocked('b.example.')
+ self.checkBlocked('a.example.', soa=True)
+ self.checkBlocked('b.example.', soa=True)
self.checkNotBlocked('c.example.')
# third zone, only b should be blocked
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(3)
self.checkRPZStats(3, 1, 1, self._xfrDone)
self.checkNotBlocked('a.example.')
- self.checkBlocked('b.example.')
+ self.checkBlocked('b.example.', soa=True)
self.checkNotBlocked('c.example.')
# fourth zone, only c should be blocked
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(4)
self.checkRPZStats(4, 1, 1, self._xfrDone)
self.checkNotBlocked('a.example.')
self.checkNotBlocked('b.example.')
- self.checkBlocked('c.example.')
+ self.checkBlocked('c.example.', soa=True)
# fifth zone, we should get a full AXFR this time, and only d should be blocked
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(5)
self.checkRPZStats(5, 3, 2, self._xfrDone)
self.checkNotBlocked('a.example.')
self.checkNotBlocked('b.example.')
self.checkNotBlocked('c.example.')
- self.checkBlocked('d.example.')
+ self.checkBlocked('d.example.', soa=True)
# sixth zone, only e should be blocked, f is a local data record
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(6)
self.checkRPZStats(6, 2, 2, self._xfrDone)
self.checkNotBlocked('a.example.')
self.checkNotBlocked('b.example.')
self.checkNotBlocked('c.example.')
self.checkNotBlocked('d.example.')
- self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1', '192.0.2.2'))
+ self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1', '192.0.2.2'), soa=True)
self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
- self.checkNoData('e.example.', 'AAAA')
- self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
+ self.checkNoData('e.example.', 'AAAA', soa=True)
+ self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'), soa=True)
# seventh zone, e should only have one A
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(7)
self.checkRPZStats(7, 4, 2, self._xfrDone)
self.checkNotBlocked('a.example.')
self.checkNotBlocked('b.example.')
self.checkNotBlocked('c.example.')
self.checkNotBlocked('d.example.')
- self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.2'))
- self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
- self.checkNoData('e.example.', 'AAAA')
- self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
+ self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.2'), soa=True)
+ self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'), soa=True)
+ self.checkNoData('e.example.', 'AAAA', soa=True)
+ self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'), soa=True)
# check that the policy is disabled for AD=1 queries
self.checkNotBlocked('e.example.', True)
# check non-custom policies
- self.checkTruncated('tc.example.')
+ self.checkTruncated('tc.example.', soa=True)
self.checkDropped('drop.example.')
# eighth zone, all entries should be gone
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(8)
self.checkRPZStats(8, 0, 3, self._xfrDone)
self.checkNotBlocked('a.example.')
# 9th zone is a duplicate, it might get skipped
global rpzServer
rpzServer.moveToSerial(9)
+ self.sendNotify()
time.sleep(3)
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(10)
self.checkRPZStats(10, 1, 4, self._xfrDone)
self.checkNotBlocked('a.example.')
self.checkNotBlocked('c.example.')
self.checkNotBlocked('d.example.')
self.checkNotBlocked('e.example.')
- self.checkBlocked('f.example.')
+ self.checkBlocked('f.example.', soa=True)
self.checkNXD('tc.example.')
self.checkNXD('drop.example.')
# the next update will update the zone twice
rpzServer.moveToSerial(11)
+ self.sendNotify()
time.sleep(3)
+ self.sendNotify()
self.waitUntilCorrectSerialIsLoaded(12)
self.checkRPZStats(12, 1, 4, self._xfrDone)
self.checkNotBlocked('a.example.')
self.checkNotBlocked('d.example.')
self.checkNotBlocked('e.example.')
self.checkNXD('f.example.')
- self.checkBlocked('g.example.')
+ self.checkBlocked('g.example.', soa=True)
self.checkNXD('tc.example.')
self.checkNXD('drop.example.')
_confdir = 'RPZFile'
_lua_config_file = """
- rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
+ rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", includeSOA=true })
""" % (_confdir)
_config_template = """
auth-zones=example=configs/%s/example.zone
def testRPZ(self):
self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42', '192.0.2.43'))
self.checkCustom('a.example.', 'TXT', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'TXT', '"some text"'))
- self.checkBlocked('z.example.')
+ self.checkBlocked('z.example.', soa=True)
self.checkNotBlocked('b.example.')
self.checkNotBlocked('c.example.')
self.checkNotBlocked('d.example.')
# check that the policy is disabled for AD=1 queries
self.checkNotBlocked('z.example.', True)
# check non-custom policies
- self.checkTruncated('tc.example.')
+ self.checkTruncated('tc.example.', soa=True)
self.checkDropped('drop.example.')
class RPZFileDefaultPolRecursorTest(RPZRecursorTest):
def __init__(self, port):
self._serverPort = port
listener = threading.Thread(name='RPZ Simple Auth Listener', target=self._listener, args=[])
- listener.setDaemon(True)
+ listener.daemon = True
listener.start()
def _getAnswer(self, message):
+import errno
import os
import socket
import struct
import threading
import dns
import dnstap_pb2
-from nose import SkipTest
+from unittest import SkipTest
from recursortests import RecursorTest
FSTRM_CONTROL_ACCEPT = 0x01
fstrm_handle_bidir_connection(conn, lambda data: \
param.queue.put(data, True, timeout=2.0))
except socket.error as e:
- if e.errno == 9:
+ if e.errno in (errno.EBADF, errno.EPIPE):
break
sys.stderr.write("Unexpected socket error %s\n" % str(e))
sys.exit(1)
listener.setDaemon(True)
listener.start()
except socket.error as e:
- if e.errno != 9:
+ if e.errno != errno.EBADF:
sys.stderr.write("Socket error on accept: %s\n" % str(e))
else:
break
"""
def _checkStatsValues(self, results):
- for i in list(range(1, 93)):
+ count = 148
+ for i in list(range(1, count)):
oid = self._snmpOID + '.1.' + str(i) + '.0'
self.assertTrue(oid in results)
self.assertTrue(isinstance(results[oid], Counter64))
+ oid = self._snmpOID + '.1.' + str(count + 1) + '.0'
+ self.assertFalse(oid in results)
+
# check uptime > 0
self.assertGreater(results['1.3.6.1.4.1.43315.2.1.75.0'], 0)
# check memory usage > 0
response = self.sendUDPQuery(query)
self.assertEqual(len(response.options), 1)
- self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+ if dns.version.MAJOR < 2 or (dns.version.MAJOR == 2 and dns.version.MINOR < 6):
+ self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+ else:
+ self.assertEqual(response.options[0].to_text(), 'NSID ' + self._servername)
def testNSIDTCP(self):
"""
response = self.sendTCPQuery(query)
self.assertEqual(len(response.options), 1)
- self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+ if dns.version.MAJOR < 2 or (dns.version.MAJOR == 2 and dns.version.MINOR < 6):
+ self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+ else:
+ self.assertEqual(response.options[0].to_text(), 'NSID ' + self._servername)
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)
--- /dev/null
+import dns
+import os
+from recursortests import RecursorTest
+
+class testSimple(RecursorTest):
+ _confdir = 'Simple'
+
+ _config_template = """
+recursor:
+ auth_zones:
+ - zone: authzone.example
+ file: configs/%s/authzone.zone
+dnssec:
+ validation: validate""" % _confdir
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ authzonepath = os.path.join(confdir, 'authzone.zone')
+ with open(authzonepath, 'w') as authzone:
+ authzone.write("""$ORIGIN authzone.example.
+@ 3600 IN SOA {soa}
+@ 3600 IN A 192.0.2.88
+""".format(soa=cls._SOA))
+ super(testSimple, cls).generateRecursorYamlConfig(confdir)
+
+ def testSOAs(self):
+ for zone in ['.', 'example.', 'secure.example.']:
+ expected = dns.rrset.from_text(zone, 0, dns.rdataclass.IN, 'SOA', self._SOA)
+ query = dns.message.make_query(zone, 'SOA', want_dnssec=True)
+ query.flags |= dns.flags.AD
+
+ res = self.sendUDPQuery(query)
+
+ self.assertMessageIsAuthenticated(res)
+ self.assertRRsetInAnswer(res, expected)
+ self.assertMatchingRRSIGInAnswer(res, expected)
+
+ def testA(self):
+ expected = dns.rrset.from_text('ns.secure.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.9'.format(prefix=self._PREFIX))
+ query = dns.message.make_query('ns.secure.example', 'A', want_dnssec=True)
+ query.flags |= dns.flags.AD
+
+ res = self.sendUDPQuery(query)
+
+ self.assertMessageIsAuthenticated(res)
+ self.assertRRsetInAnswer(res, expected)
+ self.assertMatchingRRSIGInAnswer(res, expected)
+
+ def testDelegation(self):
+ query = dns.message.make_query('example', 'NS', want_dnssec=True)
+ query.flags |= dns.flags.AD
+
+ expectedNS = dns.rrset.from_text('example.', 0, 'IN', 'NS', 'ns1.example.', 'ns2.example.')
+
+ res = self.sendUDPQuery(query)
+
+ self.assertMessageIsAuthenticated(res)
+ self.assertRRsetInAnswer(res, expectedNS)
+
+ def testBogus(self):
+ query = dns.message.make_query('ted.bogus.example', 'A', want_dnssec=True)
+
+ res = self.sendUDPQuery(query)
+
+ self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+
+ def testAuthZone(self):
+ query = dns.message.make_query('authzone.example', 'A', want_dnssec=True)
+
+ expectedA = dns.rrset.from_text('authzone.example.', 0, 'IN', 'A', '192.0.2.88')
+
+ res = self.sendUDPQuery(query)
+
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expectedA)
+
+ def testLocalhost(self):
+ queryA = dns.message.make_query('localhost', 'A', want_dnssec=True)
+ expectedA = dns.rrset.from_text('localhost.', 0, 'IN', 'A', '127.0.0.1')
+
+ queryPTR = dns.message.make_query('1.0.0.127.in-addr.arpa', 'PTR', want_dnssec=True)
+ expectedPTR = dns.rrset.from_text('1.0.0.127.in-addr.arpa.', 0, 'IN', 'PTR', 'localhost.')
+
+ resA = self.sendUDPQuery(queryA)
+ resPTR = self.sendUDPQuery(queryPTR)
+
+ self.assertRcodeEqual(resA, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(resA, expectedA)
+
+ self.assertRcodeEqual(resPTR, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(resPTR, expectedPTR)
+
+ def testLocalhostSubdomain(self):
+ queryA = dns.message.make_query('foo.localhost', 'A', want_dnssec=True)
+ expectedA = dns.rrset.from_text('foo.localhost.', 0, 'IN', 'A', '127.0.0.1')
+
+ resA = self.sendUDPQuery(queryA)
+
+ self.assertRcodeEqual(resA, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(resA, expectedA)
+
+ def testIslandOfSecurity(self):
+ query = dns.message.make_query('cname-to-islandofsecurity.secure.example.', 'A', want_dnssec=True)
+
+ expectedCNAME = dns.rrset.from_text('cname-to-islandofsecurity.secure.example.', 0, 'IN', 'CNAME', 'node1.islandofsecurity.example.')
+ expectedA = dns.rrset.from_text('node1.islandofsecurity.example.', 0, 'IN', 'A', '192.0.2.20')
+
+ res = self.sendUDPQuery(query)
+
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expectedA)
+
--- /dev/null
+import dns
+import os
+import time
+import subprocess
+from recursortests import RecursorTest
+
+class testTraceFail(RecursorTest):
+ _confdir = 'TraceFail'
+
+ _config_template = """
+trace=fail
+forward-zones-recurse=.=127.0.0.1:9999
+"""
+
+ @classmethod
+ def setUpClass(cls):
+
+ # we don't need all the auth stuff
+ cls.setUpSockets()
+ cls.startResponders()
+
+ confdir = os.path.join('configs', cls._confdir)
+ cls.createConfigDir(confdir)
+
+ cls.generateRecursorConfig(confdir)
+ cls.startRecursor(confdir, cls._recursorPort)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRecursor()
+
+ def testA(self):
+ query = dns.message.make_query('example', 'A', want_dnssec=False)
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+
+ grepCmd = ['grep', 'END OF FAIL TRACE', 'configs/' + self._confdir + '/recursor.log']
+ ret = b''
+ for i in range(10):
+ time.sleep(1)
+ try:
+ ret = subprocess.check_output(grepCmd, stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ continue
+ print(b'A' + ret)
+ break
+ print(ret)
+ self.assertNotEqual(ret, b'')
--- /dev/null
+import dns
+import time
+import os
+import subprocess
+
+from recursortests import RecursorTest
+
+class testZTC(RecursorTest):
+
+ _confdir = 'ZTC'
+ _config_template = """
+dnssec=validate
+"""
+ _lua_config_file = """
+zoneToCache(".", "axfr", "193.0.14.129") -- k-root
+"""
+
+ @classmethod
+ def setUpClass(cls):
+
+ # we don't need all the auth stuff
+ cls.setUpSockets()
+ cls.startResponders()
+
+ confdir = os.path.join('configs', cls._confdir)
+ cls.createConfigDir(confdir)
+
+ cls.generateRecursorConfig(confdir)
+ cls.startRecursor(confdir, cls._recursorPort)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRecursor()
+
+ def testZTC(self):
+ grepCmd = ['grep', 'validationStatus="Secure"', 'configs/' + self._confdir + '/recursor.log']
+ ret = b''
+ for i in range(30):
+ time.sleep(1)
+ try:
+ ret = subprocess.check_output(grepCmd, stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ continue
+ print(b'A' + ret)
+ break
+ print(ret)
+ self.assertNotEqual(ret, b'')
+
except subprocess.CalledProcessError as e:
raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
- params = "1 0 100 AABBCCDDEEFF112233"
+ params = "1 0 50 AABBCCDDEEFF112233"
if zone == "optout.example":
- params = "1 1 100 AABBCCDDEEFF112233"
+ params = "1 1 50 AABBCCDDEEFF112233"
pdnsutilCmd = [os.environ['PDNSUTIL'],
'--config-dir=%s' % confdir,
Reply to question for qname='www3.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 www3.example.net. IN CNAME [ttl] www2.example.net.
-0 www2.example.net. IN A [ttl] 192.0.2.2
+0 www3.example.net. [ttl] IN CNAME www2.example.net.
+0 www2.example.net. [ttl] IN A 192.0.2.2
Reply to question for qname='android.marvin.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 android.marvin.example.net. IN A [ttl] 192.0.2.5
+0 android.marvin.example.net. [ttl] IN A 192.0.2.5
Reply to question for qname='www5.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 www5.example.net. IN A [ttl] 192.0.2.25
+0 www5.example.net. [ttl] IN A 192.0.2.25
echo "==> defpol-with-ttl.example.net should use the default policy's TTL and not the zone one"
$SDIG $nameserver 5301 defpol-with-ttl.example.net a recurse 2>&1
echo "==> defpol-with-ttl-capped.example.net should use the default policy's TTL, but capped to maxTTL"
-$SDIG $nameserver 5301 defpol-with-ttl-capped.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\115/'
+$SDIG $nameserver 5301 defpol-with-ttl-capped.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\t\)\([0-9]\+\)/\115/'
echo "==> defpol-without-ttl.example.net should use the zone's TTL"
-$SDIG $nameserver 5301 defpol-without-ttl.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\115/'
+$SDIG $nameserver 5301 defpol-without-ttl.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\t\)\([0-9]\+\)/\115/'
echo "==> defpol-without-ttl-capped.example.net should use the zone's TTL but capped to maxTTL"
-$SDIG $nameserver 5301 defpol-without-ttl-capped.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\115/'
+$SDIG $nameserver 5301 defpol-without-ttl-capped.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\t\)\([0-9]\+\)/\115/'
echo "==> unsupported.example.net has an unsupported target, should be ignored from the RPZ zone"
$SDIG $nameserver 5301 unsupported.example.net a recurse 2>&1
echo "==> unsupported2.example.net has an unsupported target, should be ignored from the RPZ zone"
==> srv.arthur.example.net RPZ passthru
Reply to question for qname='srv.arthur.example.net.', qtype=SRV
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 srv.arthur.example.net. IN SRV 15 0 100 389 server2.example.net.
+0 srv.arthur.example.net. 15 IN SRV 0 100 389 server2.example.net.
==> www.example.net RPZ local data to www2.example.net
Reply to question for qname='www.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 www.example.net. IN CNAME 7200 www2.example.net.
-0 www2.example.net. IN A 15 192.0.2.2
+0 www.example.net. 7200 IN CNAME www2.example.net.
+0 www2.example.net. 15 IN A 192.0.2.2
==> www4.example.net RPZ IP trigger action, dropped
==> trillian.example.net NXDOMAIN
Reply to question for qname='trillian.example.net.', qtype=A
==> www.trillian.example.net has no RPZ policy attached, so lookup should succeed
Reply to question for qname='www.trillian.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 www.trillian.example.net. IN CNAME 15 www3.arthur.example.net.
-0 www3.arthur.example.net. IN A 15 192.0.2.6
+0 www.trillian.example.net. 15 IN CNAME www3.arthur.example.net.
+0 www3.arthur.example.net. 15 IN A 192.0.2.6
==> www.hijackme.example.net is served on ns.hijackme.example.net, which should be NXDOMAIN
Reply to question for qname='www.hijackme.example.net.', qtype=A
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
==> capped-ttl.example.net TTL exceeds the maximum TTL for the zone
Reply to question for qname='capped-ttl.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 capped-ttl.example.net. IN A 5 192.0.2.35
+0 capped-ttl.example.net. 5 IN A 192.0.2.35
==> defpol-with-ttl.example.net should use the default policy's TTL and not the zone one
Reply to question for qname='defpol-with-ttl.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 defpol-with-ttl.example.net. IN CNAME 10 default.example.net.
-0 default.example.net. IN A 15 192.0.2.42
+0 defpol-with-ttl.example.net. 10 IN CNAME default.example.net.
+0 default.example.net. 15 IN A 192.0.2.42
==> defpol-with-ttl-capped.example.net should use the default policy's TTL, but capped to maxTTL
Reply to question for qname='defpol-with-ttl-capped.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 defpol-with-ttl-capped.example.net. IN CNAME 20 default.example.net.
-0 default.example.net. IN A 15 192.0.2.42
+0 defpol-with-ttl-capped.example.net. 20 IN CNAME default.example.net.
+0 default.example.net. 15 IN A 192.0.2.42
==> defpol-without-ttl.example.net should use the zone's TTL
Reply to question for qname='defpol-without-ttl.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 defpol-without-ttl.example.net. IN CNAME 7200 default.example.net.
-0 default.example.net. IN A 15 192.0.2.42
+0 defpol-without-ttl.example.net. 7200 IN CNAME default.example.net.
+0 default.example.net. 15 IN A 192.0.2.42
==> defpol-without-ttl-capped.example.net should use the zone's TTL but capped to maxTTL
Reply to question for qname='defpol-without-ttl-capped.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 defpol-without-ttl-capped.example.net. IN CNAME 50 default.example.net.
-0 default.example.net. IN A 15 192.0.2.42
+0 defpol-without-ttl-capped.example.net. 50 IN CNAME default.example.net.
+0 default.example.net. 15 IN A 192.0.2.42
==> unsupported.example.net has an unsupported target, should be ignored from the RPZ zone
Reply to question for qname='unsupported.example.net.', qtype=A
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-1 example.net. IN SOA 15 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1 example.net. 15 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
==> unsupported2.example.net has an unsupported target, should be ignored from the RPZ zone
Reply to question for qname='unsupported2.example.net.', qtype=A
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-1 example.net. IN SOA 15 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1 example.net. 15 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
==> not-rpz.example.net is _not_ an RPZ target and should be processed
Reply to question for qname='not-rpz.example.net.', qtype=A
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 not-rpz.example.net. IN CNAME 5 rpz-not.com.
-1 . IN SOA 15 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+0 not-rpz.example.net. 5 IN CNAME rpz-not.com.
+1 . 15 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
==> echo-me.wildcard-target.example.net is an RPZ wildcard target
Reply to question for qname='echo-me.wildcard-target.example.net.', qtype=A
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 echo-me.wildcard-target.example.net. IN CNAME 7200 echo-me.wildcard-target.example.net.walled-garden.example.net.
-1 example.net. IN SOA 15 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+0 echo-me.wildcard-target.example.net. 7200 IN CNAME echo-me.wildcard-target.example.net.walled-garden.example.net.
+1 example.net. 15 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
--- /dev/null
+# Generated by pdns-recursor REST API, DO NOT EDIT
+incoming:
+ allow_from_file: ''
+ allow_from: !override
+ - 1.2.3.4/32, 127.0.0.0/24
+
--- /dev/null
+# Generated by pdns-recursor REST API, DO NOT EDIT
+incoming:
+ allow_notify_from_file: ''
+ allow_notify_from: !override
+ - 127.0.0.0/24
+
--- /dev/null
+forward_zones:
+- zone: 1u1.test.com.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: blabla.com.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: dot.test.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: geo.example.com.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: nodot.test.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: test.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn10.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn100.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1000.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1002.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1003.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1004.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1005.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1006.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1007.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1008.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1009.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn101.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1010.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1011.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1012.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1013.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1014.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1015.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1016.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1018.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1019.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn102.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1020.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1021.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1022.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1023.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1024.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1025.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1027.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1028.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1029.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn103.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1030.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1031.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1032.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1033.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1035.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1036.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1037.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1038.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1039.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn104.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1040.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1041.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1042.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1043.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1044.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1045.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1046.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1047.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1048.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1049.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn105.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1050.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1051.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1053.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1054.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1055.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1056.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1057.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1058.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1059.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn106.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1061.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1062.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1063.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1064.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1065.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1066.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1067.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1069.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn107.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1070.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1071.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1073.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1074.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1076.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1077.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1078.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1079.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn108.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1080.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1081.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1082.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1083.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1084.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1085.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1086.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1087.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1088.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1089.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn109.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1090.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1091.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1092.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1093.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1094.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1095.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1096.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1097.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1098.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1099.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn11.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn110.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1100.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1101.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1102.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1103.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1104.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1105.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1106.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1107.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1108.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1109.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn111.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1110.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1111.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1112.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1113.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1114.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1115.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1116.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1117.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1118.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1119.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn112.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1120.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1122.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1124.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1125.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1126.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1127.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1128.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1129.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn113.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1130.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1131.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1132.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1133.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1135.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1136.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1137.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1138.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1139.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn114.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1140.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1141.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1142.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1143.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1144.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1145.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1146.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1147.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1148.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1149.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn115.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1150.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1152.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1153.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1154.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1155.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1156.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1157.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1158.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1159.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn116.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1160.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1161.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1162.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1163.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1164.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1165.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1166.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1167.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1168.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1169.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn117.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1170.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1171.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1172.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1174.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1175.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1176.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1177.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1178.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1179.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn118.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1180.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1181.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1182.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1183.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1184.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1185.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1186.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1187.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1188.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1189.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn119.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1190.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1191.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1192.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1193.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1194.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1195.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1196.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1197.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1198.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1199.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn12.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn120.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1200.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1201.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1202.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1203.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1204.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1205.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1207.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1208.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1209.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn121.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1210.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1212.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1214.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1215.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1217.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1218.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1219.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn122.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1220.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1221.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1222.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1223.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1224.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1225.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1226.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1227.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1228.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1229.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn123.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1231.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1232.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1233.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1234.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1235.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1236.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1237.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1238.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1239.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn124.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1240.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1241.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1242.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1243.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1244.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1246.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1247.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1248.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1249.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn125.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1251.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1252.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1253.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1254.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1255.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1256.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1257.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1258.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1259.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn126.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1260.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1261.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1262.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1263.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1264.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1265.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1266.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1267.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1268.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1269.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn127.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1270.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1271.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1272.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1273.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1274.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1275.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1276.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1277.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1278.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1279.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn128.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1280.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1282.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1283.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1284.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1285.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1286.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1287.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1289.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn129.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1290.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1291.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1292.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1293.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1294.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1295.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1297.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1298.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1299.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn13.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn130.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1300.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1302.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1303.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1304.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1305.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1306.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1307.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1309.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn131.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1310.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1311.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1312.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1313.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1314.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1316.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1317.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1318.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1319.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn132.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1320.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1321.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1322.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1323.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1324.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1325.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1326.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1327.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1329.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn133.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1330.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1331.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1332.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1333.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1334.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1336.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1337.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1338.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1339.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn134.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1340.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1341.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1342.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1343.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1344.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1345.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1347.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1348.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1349.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn135.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1350.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1351.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1352.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1353.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1354.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1355.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1356.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1357.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1358.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1359.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn136.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1360.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1361.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1362.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1363.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1364.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1366.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1367.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1368.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1369.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn137.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1370.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1371.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1372.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1373.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1374.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1376.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1377.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1378.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1379.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn138.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1380.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1381.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1382.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1384.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1385.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1386.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1387.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1388.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1389.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn139.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1390.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1391.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1392.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1393.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1394.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1395.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1396.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1397.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1398.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1399.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn14.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn140.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1400.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1401.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1402.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1403.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1404.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1406.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1407.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1408.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1409.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn141.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1410.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1411.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1412.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1413.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1414.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1415.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1416.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1417.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1418.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1419.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn142.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1420.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1421.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1422.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1424.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1425.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1427.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1428.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1429.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn143.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1430.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1431.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1433.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1434.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1435.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1436.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1437.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1438.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1439.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn144.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1440.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn1441.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn145.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn146.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn147.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn148.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn149.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn15.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn150.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn151.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn152.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn153.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn154.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn155.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn156.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn157.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn158.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn159.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn16.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn160.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn161.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn162.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn163.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn164.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn165.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn166.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn167.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn168.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn169.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn17.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn170.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn171.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn172.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn173.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn174.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn175.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn176.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn177.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn178.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn179.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn18.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn180.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn181.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn182.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn183.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn184.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn185.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn186.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn187.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn188.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn189.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn19.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn190.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn191.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn192.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn193.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn194.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn195.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn196.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn197.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn198.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn199.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn2.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn20.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn200.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn201.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn202.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn203.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn204.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn205.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn206.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn207.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn208.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn209.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn21.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn210.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn211.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn212.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn213.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn214.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn215.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn216.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn217.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn218.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn219.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn22.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn220.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn221.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn222.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn223.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn224.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn225.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn226.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn227.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn228.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn229.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn23.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn230.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn231.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn232.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn233.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn234.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn235.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn236.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn237.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn238.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn239.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn24.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn240.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn241.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn242.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn243.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn244.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn245.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn246.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn247.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn248.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn249.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn25.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn250.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn251.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn252.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn253.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn254.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn255.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn256.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn257.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn258.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn259.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn26.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn260.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn261.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn262.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn263.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn264.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn265.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn266.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn267.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn268.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn269.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn27.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn270.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn271.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn272.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn273.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn274.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn275.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn276.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn277.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn278.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn279.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn28.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn280.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn281.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn282.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn283.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn284.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn285.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn286.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn287.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn288.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn289.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn29.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn290.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn291.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn292.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn293.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn294.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn295.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn296.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn297.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn298.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn299.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn3.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn30.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn300.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn301.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn302.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn303.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn304.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn305.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn306.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn307.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn308.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn309.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn31.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn310.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn311.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn312.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn313.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn314.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn315.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn316.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn317.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn318.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn319.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn32.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn320.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn321.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn322.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn323.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn324.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn325.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn326.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn327.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn328.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn329.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn33.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn330.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn331.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn332.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn333.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn334.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn335.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn336.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn337.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn338.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn339.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn34.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn340.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn341.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn342.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn343.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn344.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn345.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn346.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn347.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn348.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn349.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn35.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn350.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn351.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn352.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn353.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn354.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn355.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn356.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn357.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn358.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn359.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn36.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn360.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn361.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn362.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn363.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn364.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn365.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn366.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn367.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn368.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn369.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn37.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn370.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn371.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn372.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn373.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn374.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn375.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn376.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn377.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn378.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn379.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn38.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn380.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn381.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn382.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn383.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn384.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn385.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn386.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn387.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn388.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn389.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn39.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn390.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn391.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn392.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn393.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn394.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn395.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn396.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn397.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn398.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn399.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn4.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn40.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn400.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn401.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn402.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn403.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn404.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn405.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn406.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn407.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn408.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn409.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn41.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn410.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn411.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn412.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn413.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn414.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn415.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn416.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn417.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn418.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn419.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn42.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn420.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn421.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn422.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn423.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn424.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn425.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn426.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn427.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn428.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn429.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn43.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn430.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn431.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn432.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn433.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn434.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn435.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn436.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn437.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn438.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn439.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn44.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn440.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn441.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn442.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn443.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn444.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn445.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn446.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn447.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn448.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn449.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn45.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn450.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn451.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn452.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn453.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn454.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn455.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn458.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn459.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn46.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn460.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn461.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn462.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn463.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn464.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn465.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn466.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn467.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn468.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn469.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn47.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn470.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn471.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn472.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn473.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn474.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn475.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn476.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn477.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn478.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn479.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn48.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn481.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn482.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn483.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn484.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn485.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn487.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn488.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn49.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn491.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn492.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn493.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn494.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn495.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn496.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn497.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn498.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn499.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn5.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn50.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn501.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn502.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn503.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn504.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn506.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn507.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn508.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn509.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn51.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn511.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn512.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn513.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn514.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn515.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn517.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn518.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn519.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn52.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn520.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn521.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn522.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn523.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn524.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn525.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn526.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn527.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn528.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn529.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn53.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn530.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn531.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn532.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn533.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn534.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn535.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn536.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn537.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn538.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn539.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn54.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn540.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn541.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn542.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn543.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn544.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn545.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn546.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn547.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn548.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn549.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn55.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn550.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn551.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn552.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn553.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn554.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn555.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn556.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn557.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn558.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn56.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn560.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn561.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn563.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn564.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn565.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn566.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn567.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn568.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn569.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn57.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn570.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn571.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn573.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn574.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn575.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn576.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn577.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn578.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn579.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn58.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn580.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn581.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn582.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn583.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn585.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn586.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn587.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn588.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn589.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn59.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn590.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn591.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn592.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn593.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn595.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn596.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn597.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn598.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn599.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn6.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn60.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn600.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn601.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn602.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn603.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn605.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn606.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn608.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn609.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn61.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn610.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn611.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn612.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn613.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn614.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn615.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn616.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn617.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn618.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn619.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn62.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn620.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn621.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn622.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn623.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn624.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn625.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn626.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn627.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn629.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn63.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn630.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn631.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn632.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn633.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn634.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn635.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn636.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn637.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn638.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn639.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn64.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn640.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn641.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn642.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn645.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn646.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn648.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn649.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn65.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn651.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn653.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn654.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn655.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn656.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn657.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn658.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn659.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn66.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn660.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn661.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn662.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn663.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn664.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn665.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn666.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn667.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn668.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn669.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn67.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn670.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn671.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn672.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn673.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn674.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn675.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn677.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn678.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn679.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn68.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn680.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn681.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn682.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn683.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn684.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn685.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn686.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn687.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn688.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn689.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn69.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn690.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn691.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn692.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn693.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn694.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn695.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn697.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn698.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn699.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn7.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn70.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn701.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn702.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn703.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn704.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn705.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn707.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn708.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn709.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn71.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn710.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn711.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn712.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn713.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn714.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn715.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn716.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn717.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn718.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn719.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn72.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn720.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn721.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn722.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn723.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn724.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn725.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn726.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn727.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn729.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn73.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn730.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn731.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn732.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn733.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn734.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn736.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn737.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn738.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn739.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn74.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn740.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn741.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn742.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn743.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn744.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn745.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn746.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn748.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn749.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn75.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn750.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn751.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn752.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn753.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn754.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn755.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn756.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn757.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn758.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn759.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn76.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn761.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn763.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn764.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn765.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn766.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn768.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn77.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn770.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn771.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn772.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn773.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn774.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn775.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn776.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn777.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn779.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn78.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn780.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn781.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn782.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn783.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn784.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn785.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn786.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn787.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn788.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn789.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn79.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn790.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn791.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn792.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn794.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn795.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn796.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn797.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn798.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn799.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn8.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn80.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn800.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn801.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn802.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn803.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn804.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn805.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn806.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn807.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn808.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn809.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn81.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn810.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn811.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn812.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn814.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn815.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn816.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn817.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn818.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn819.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn82.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn821.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn822.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn823.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn825.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn826.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn829.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn83.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn830.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn831.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn832.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn834.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn835.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn836.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn837.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn838.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn839.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn84.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn840.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn841.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn842.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn843.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn845.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn846.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn847.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn848.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn849.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn85.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn850.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn851.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn852.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn853.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn854.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn855.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn856.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn857.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn858.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn859.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn86.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn860.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn861.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn862.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn863.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn864.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn865.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn866.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn867.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn868.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn869.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn87.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn870.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn871.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn872.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn873.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn874.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn875.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn876.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn878.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn879.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn88.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn880.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn881.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn882.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn883.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn884.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn885.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn886.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn887.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn889.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn89.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn890.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn891.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn893.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn894.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn895.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn896.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn897.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn898.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn899.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn9.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn90.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn900.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn901.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn902.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn903.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn904.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn905.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn906.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn907.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn908.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn909.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn91.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn910.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn911.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn912.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn913.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn914.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn915.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn916.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn917.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn918.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn919.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn92.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn920.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn921.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn922.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn923.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn924.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn926.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn927.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn928.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn929.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn93.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn930.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn931.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn932.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn933.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn935.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn936.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn937.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn938.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn939.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn94.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn941.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn942.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn943.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn944.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn945.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn946.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn947.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn948.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn949.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn95.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn951.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn952.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn953.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn954.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn955.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn958.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn959.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn96.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn960.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn961.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn962.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn963.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn964.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn966.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn967.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn968.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn969.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn97.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn971.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn972.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn973.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn974.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn975.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn976.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn977.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn978.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn979.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn98.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn980.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn981.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn982.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn983.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn984.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn985.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn986.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn987.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn988.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn989.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn99.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn990.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn991.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn992.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn993.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn994.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn995.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn996.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn997.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn998.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: testfwdlearn999.local.
+ forwarders:
+ - 10.28.13.76:5353
+ - 10.28.15.76:5353
+- zone: google.com.
+ forwarders:
+ - 8.8.8.8:53
+ - 1.1.1.1:53
+ - 8.8.4.4:53
+ - 9.9.9.9:53
+ recurse: true
+- zone: internal.
+ forwarders:
+ - 8.8.8.8:53
+ - 1.1.1.1:53
+ - 8.8.4.4:53
+ - 9.9.9.9:53
+ recurse: true
+- zone: nico.
+ forwarders:
+ - 8.8.8.8:53
+ - 1.1.1.1:53
+ - 8.8.4.4:53
+ - 9.9.9.9:53
+ recurse: true
--- /dev/null
+#!/bin/sh
+cd $(dirname $0)
+set -e
+
+d=$(mktemp -d in.XXXXXXXXX)
+d2=$(mktemp -d out.XXXXXXXX)
+cd $d
+tar -zxf ../apiconfig.tar.gz
+cd ..
+cat > recursor.yml << EOF
+incoming:
+ port: 9999
+recursor:
+ include_dir: $d
+ socket_dir: .
+webservice:
+ api_dir: $d2
+EOF
+${PDNSRECURSOR} --config-dir=. &
+
+set +e
+for in in 0 1 2 3 4 5 6 7 8 9; do
+sleep 1
+${RECCONTROL} --config-dir=. quit-nicely
+if [ $? = 0 ]; then
+ break
+fi
+done | uniq
+set -e
+
+diff -u apizones.expected $d2/apizones
+diff -u allow-from.yml.expected $d2/allow-from.yml
+diff -u allow-notify-from.yml.expected $d2/allow-notify-from.yml
+rm -rf $d $d2 recursor.yml
--- /dev/null
+Test the conversion of the recursor's API-maintaned config files to YAML
--- /dev/null
+YAML config found and processed for configname './recursor.yml'
+bye nicely
-cleandig service.box.answer-cname-in-local.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig service.box.answer-cname-in-local.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 pfs.global.box.answer-cname-in-local.example.net. IN CNAME 3600 vip-reunion.pfsbox.answer-cname-in-local.example.net.
-0 service.box.answer-cname-in-local.example.net. IN CNAME 3600 pfs.global.box.answer-cname-in-local.example.net.
-0 vip-reunion.pfsbox.answer-cname-in-local.example.net. IN A 3600 10.1.1.1
+0 pfs.global.box.answer-cname-in-local.example.net. 3600 IN CNAME vip-reunion.pfsbox.answer-cname-in-local.example.net.
+0 service.box.answer-cname-in-local.example.net. 3600 IN CNAME pfs.global.box.answer-cname-in-local.example.net.
+0 vip-reunion.pfsbox.answer-cname-in-local.example.net. 3600 IN A 10.1.1.1
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='service.box.answer-cname-in-local.example.net.', qtype=A
-cleandig host1.something.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig host1.something.auth-zone.example.net. AAAA | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig host1.something.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig host1.something.auth-zone.example.net. AAAA | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 host1.auth-zone.example.net. IN A 3600 127.0.0.55
-0 host1.something.auth-zone.example.net. IN CNAME 3600 host1.auth-zone.example.net.
+0 host1.auth-zone.example.net. 3600 IN A 127.0.0.55
+0 host1.something.auth-zone.example.net. 3600 IN CNAME host1.auth-zone.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='host1.something.auth-zone.example.net.', qtype=A
-0 host1.auth-zone.example.net. IN AAAA 3600 2001:db8::1:45ba
-0 host1.something.auth-zone.example.net. IN CNAME 3600 host1.auth-zone.example.net.
+0 host1.auth-zone.example.net. 3600 IN AAAA 2001:db8::1:45ba
+0 host1.something.auth-zone.example.net. 3600 IN CNAME host1.auth-zone.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='host1.something.auth-zone.example.net.', qtype=AAAA
-cleandig www.france.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig france.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.france.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig france.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 www.france.auth-zone.example.net. IN A 3600 192.0.2.23
+0 www.france.auth-zone.example.net. 3600 IN A 192.0.2.23
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www.france.auth-zone.example.net.', qtype=A
-0 france.auth-zone.example.net. IN A 3600 192.0.2.223
+0 france.auth-zone.example.net. 3600 IN A 192.0.2.223
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='france.auth-zone.example.net.', qtype=A
-cleandig host1.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig host1.auth-zone.example.net. AAAA | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig host2.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig host3.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig you-are.wild.auth-zone.example.net. TXT | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig host1.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig host1.auth-zone.example.net. AAAA | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig host2.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig host3.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig you-are.wild.auth-zone.example.net. TXT | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
# Non-existing QTYPE at the apex
-cleandig auth-zone.example.net. TXT | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig auth-zone.example.net. TXT | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 host1.auth-zone.example.net. IN A 3600 127.0.0.55
+0 host1.auth-zone.example.net. 3600 IN A 127.0.0.55
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='host1.auth-zone.example.net.', qtype=A
-0 host1.auth-zone.example.net. IN AAAA 3600 2001:db8::1:45ba
+0 host1.auth-zone.example.net. 3600 IN AAAA 2001:db8::1:45ba
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='host1.auth-zone.example.net.', qtype=AAAA
-0 host1.another-auth-zone.example.net. IN A 3600 127.0.0.56
-0 host2.auth-zone.example.net. IN CNAME 3600 host1.another-auth-zone.example.net.
+0 host1.another-auth-zone.example.net. 3600 IN A 127.0.0.56
+0 host2.auth-zone.example.net. 3600 IN CNAME host1.another-auth-zone.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='host2.auth-zone.example.net.', qtype=A
-0 host1.not-auth-zone.example.net. IN A 3600 127.0.0.57
-0 host3.auth-zone.example.net. IN CNAME 3600 host1.not-auth-zone.example.net.
+0 host1.not-auth-zone.example.net. 3600 IN A 127.0.0.57
+0 host3.auth-zone.example.net. 3600 IN CNAME host1.not-auth-zone.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='host3.auth-zone.example.net.', qtype=A
-0 you-are.wild.auth-zone.example.net. IN TXT 3600 "Hi there!"
+0 you-are.wild.auth-zone.example.net. 3600 IN TXT "Hi there!"
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='you-are.wild.auth-zone.example.net.', qtype=TXT
-1 auth-zone.example.net. IN SOA 3600 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1 auth-zone.example.net. 3600 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='auth-zone.example.net.', qtype=TXT
#!/bin/bash
$SDIG $nameserver 5302 www.arthur.example.net a recurse
sleep 3
-$SDIG $nameserver 5302 www.arthur.example.net a recurse | sed 's/\(.*\tIN\tA\t\)\(11\)/\112/'
+$SDIG $nameserver 5302 www.arthur.example.net a recurse | sed 's/\(.*\t\)\(11\tIN\)/\112\tIN/'
Reply to question for qname='www.arthur.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 www.arthur.example.net. IN A 15 192.0.2.2
+0 www.arthur.example.net. 15 IN A 192.0.2.2
Reply to question for qname='www.arthur.example.net.', qtype=A
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0 www.arthur.example.net. IN A 12 192.0.2.2
+0 www.arthur.example.net. 12 IN A 192.0.2.2
#!/bin/sh
-cleandig www-a.prefect.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www-a.prefect.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 www-a.prefect.example.net. IN CNAME 3600 www-a-2.prefect.example.net.
-1 prefect.example.net. IN SOA 3600 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+0 www-a.prefect.example.net. 3600 IN CNAME www-a-2.prefect.example.net.
+1 prefect.example.net. 3600 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www-a.prefect.example.net.', qtype=A
#!/bin/sh
-cleandig www.trillian.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.trillian.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 www.trillian.example.net. IN CNAME 3600 www3.arthur.example.net.
-0 www3.arthur.example.net. IN A 3600 192.0.2.6
+0 www.trillian.example.net. 3600 IN CNAME www3.arthur.example.net.
+0 www3.arthur.example.net. 3600 IN A 192.0.2.6
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www.trillian.example.net.', qtype=A
#!/bin/sh
-cleandig www-a.prefect.example.net cname | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www-a.prefect.example.net cname | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 www-a.prefect.example.net. IN CNAME 3600 www-a-2.prefect.example.net.
+0 www-a.prefect.example.net. 3600 IN CNAME www-a-2.prefect.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www-a.prefect.example.net.', qtype=CNAME
#!/bin/sh
-cleandig www-d.prefect.example.net cname | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www-d.prefect.example.net cname | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 www-d.prefect.example.net. IN CNAME 3600 www.arthur.example.net.
+0 www-d.prefect.example.net. 3600 IN CNAME www.arthur.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www-d.prefect.example.net.', qtype=CNAME
#!/bin/sh
. vars
rm -f configs/$PREFIX.17/drop-1
-cleandig a.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig a.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 8
touch configs/$PREFIX.17/drop-1
-cleandig b.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig b.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 5
-cleandig c.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig c.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 5
-cleandig d.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig d.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 5
-cleandig e.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig e.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 a.www.1.ghost.example.net. IN A 3600 192.0.2.7
+0 a.www.1.ghost.example.net. 3600 IN A 192.0.2.7
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='a.www.1.ghost.example.net.', qtype=A
-0 b.www.1.ghost.example.net. IN A 3600 192.0.2.7
+0 b.www.1.ghost.example.net. 3600 IN A 192.0.2.7
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='b.www.1.ghost.example.net.', qtype=A
-0 c.www.1.ghost.example.net. IN A 3600 192.0.2.7
+0 c.www.1.ghost.example.net. 3600 IN A 192.0.2.7
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='c.www.1.ghost.example.net.', qtype=A
-1 ghost.example.net. IN SOA 3600 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1 ghost.example.net. 3600 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='d.www.1.ghost.example.net.', qtype=A
-1 ghost.example.net. IN SOA 3600 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1 ghost.example.net. 3600 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='e.www.1.ghost.example.net.', qtype=A
#!/bin/sh
. vars
rm -f configs/$PREFIX.17/drop-2
-cleandig a.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig a.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 8
touch configs/$PREFIX.17/drop-2
-cleandig b.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig b.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 5
-cleandig c.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig c.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 5
-cleandig d.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig d.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 5
-cleandig e.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig e.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 a.www.2.ghost.example.net. IN A 3600 192.0.2.8
+0 a.www.2.ghost.example.net. 3600 IN A 192.0.2.8
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='a.www.2.ghost.example.net.', qtype=A
-0 b.www.2.ghost.example.net. IN A 3600 192.0.2.8
+0 b.www.2.ghost.example.net. 3600 IN A 192.0.2.8
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='b.www.2.ghost.example.net.', qtype=A
-0 c.www.2.ghost.example.net. IN A 3600 192.0.2.8
+0 c.www.2.ghost.example.net. 3600 IN A 192.0.2.8
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='c.www.2.ghost.example.net.', qtype=A
-1 ghost.example.net. IN SOA 3600 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1 ghost.example.net. 3600 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='d.www.2.ghost.example.net.', qtype=A
-1 ghost.example.net. IN SOA 3600 ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1 ghost.example.net. 3600 IN SOA ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='e.www.2.ghost.example.net.', qtype=A
#!/bin/sh
. vars
-cleandig hijacker.example.net ns | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig www.hijackme.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig hijacker.example.net ns | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig www.hijackme.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
sleep 5
\ No newline at end of file
-0 hijacker.example.net. IN NS 3600 ns.hijackme.example.net.
+0 hijacker.example.net. 3600 IN NS ns.hijackme.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='hijacker.example.net.', qtype=NS
-0 www.hijackme.example.net. IN A 3600 192.0.2.20
+0 www.hijackme.example.net. 3600 IN A 192.0.2.20
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www.hijackme.example.net.', qtype=A
#!/bin/sh
-cleandig www.marvin.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.marvin.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 android.marvin.example.net. IN A 3600 192.0.2.5
-0 www.marvin.example.net. IN CNAME 3600 android.marvin.example.net.
+0 android.marvin.example.net. 3600 IN A 192.0.2.5
+0 www.marvin.example.net. 3600 IN CNAME android.marvin.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www.marvin.example.net.', qtype=A
#!/bin/sh
-cleandig www.ford.example.net A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.ford.example.net A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
#!/bin/sh
-cleandig www.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 www.example.net. IN A 3600 192.0.2.1
+0 www.example.net. 3600 IN A 192.0.2.1
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www.example.net.', qtype=A
#!/bin/sh
-cleandig www-d.prefect.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www-d.prefect.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 www-d.prefect.example.net. IN CNAME 3600 www.arthur.example.net.
-0 www.arthur.example.net. IN A 3600 192.0.2.2
+0 www-d.prefect.example.net. 3600 IN CNAME www.arthur.example.net.
+0 www.arthur.example.net. 3600 IN A 192.0.2.2
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='www-d.prefect.example.net.', qtype=A
#!/bin/sh
-cleandig srv.arthur.example.net srv | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig rp.arthur.example.net rp | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig type1234.arthur.example.net TYPE1234 | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig srv.arthur.example.net srv | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig rp.arthur.example.net rp | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig type1234.arthur.example.net TYPE1234 | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 srv.arthur.example.net. IN SRV 3600 0 100 389 server2.example.net.
+0 srv.arthur.example.net. 3600 IN SRV 0 100 389 server2.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='srv.arthur.example.net.', qtype=SRV
-0 rp.arthur.example.net. IN RP 3600 ahu.ds9a.nl. counter.arthur.example.net.
+0 rp.arthur.example.net. 3600 IN RP ahu.ds9a.nl. counter.arthur.example.net.
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='rp.arthur.example.net.', qtype=RP
-0 type1234.arthur.example.net. IN TYPE1234 3600 \# 2 4142
+0 type1234.arthur.example.net. 3600 IN TYPE1234 \# 2 4142
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='type1234.arthur.example.net.', qtype=TYPE1234
#!/bin/sh
-cleandig big.arthur.example.net txt | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig big.arthur.example.net txt | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
#!/bin/sh
-cleandig weirdtxt.example.net txt | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig weirdtxt.example.net txt | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
-0 weirdtxt.example.net. IN TXT 3600 "x\014x"
+0 weirdtxt.example.net. 3600 IN TXT "x\014x"
Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
Reply to question for qname='weirdtxt.example.net.', qtype=TXT
minimal-responses yes;
};
zone "."{
- type master;
+ type primary;
file "ROOT";
};
fi
if [ "$zone" = "tsig.com" ]; then
$PDNSUTIL --config-dir=. --config-name=bind import-tsig-key test $ALGORITHM $KEY
- $PDNSUTIL --config-dir=. --config-name=bind activate-tsig-key tsig.com test master
+ $PDNSUTIL --config-dir=. --config-name=bind activate-tsig-key tsig.com test primary
fi
done
context=${context}-presigned
- perl -pe 's/type master;/type slave;\n\tmasters { 127.0.0.1:'$port'; };/ ;s/file "([^"]+)/file "$1-slave/' < named.conf > named-slave.conf
+ perl -pe 's/type primary;/type secondary;\n\tprimaries { 127.0.0.1:'$port'; };/ ;s/file "([^"]+)/file "$1-slave/' < named.conf > named-slave.conf
for zone in $(grep 'zone ' named.conf | cut -f2 -d\")
do
port=$((port+100))
$RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --socket-dir=./ \
- --no-shuffle --launch=bind --bind-config=./named-slave.conf --slave \
+ --no-shuffle --launch=bind --bind-config=./named-slave.conf --secondary \
--retrieval-threads=1 --config-name=bind-slave \
--dnsupdate=yes \
--cache-ttl=$cachettl --no-config --dname-processing --bind-dnssec-db=./dnssec-slave.sqlite3 \
mapping_lookup_formats: ['%cn']
custom_mapping:
$geoipregion: earth
+EOF
+ if ! [ -d $testsdir/geozones ]; then
+ mkdir $testsdir/geozones
+ fi
+ cat > $testsdir/geozones/geo2.yaml <<EOF
+zone:
+ domain: geo2.example.com
+ ttl: 30
+ records:
+ geo2.example.com:
+ - soa: ns1.example.com hostmaster.example.com 2014090125 7200 3600 1209600 3600
+ - ns: ns1.example.com
+ - ns: ns2.example.com
+ - mx: 10 mx.example.com
+ moon.map.geo2.example.com:
+ - txt: "overridden moon mapping"
+ services:
+ map.geo2.example.com: '%mp.map.geo2.example.com'
+ custom_mapping:
+ $geoipregion: moon
EOF
cat > $testsdir/region-a-resolution/expected_result <<EOF
0 www.geo.example.com. 30 IN A $geoipregionip
echo "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port');" | $ISQL -b
if [ "$zone" = "tsig.com" ]; then
../pdns/pdnssec --config-dir=. --config-name=godbc2 import-tsig-key test $ALGORITHM $KEY
- ../pdns/pdnssec --config-dir=. --config-name=godbc2 activate-tsig-key tsig.com test slave
+ ../pdns/pdnssec --config-dir=. --config-name=godbc2 activate-tsig-key tsig.com test secondary
fi
if [ "$zone" = "stest.com" ]; then
if [[ $skipreasons != *nolua* ]]; then
$RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \
--config-name=godbc2 --socket-dir=./ --no-shuffle \
- --slave --retrieval-threads=4 \
- --slave-cycle-interval=300 --dname-processing &
+ --secondary --retrieval-threads=4 \
+ --xfr-cycle-interval=300 --dname-processing &
echo 'waiting for zones to be slaved'
set +e
godbc-delete-tsig-key-query=delete from tsigkeys where name=?
godbc-delete-zone-query=delete from records where domain_id=?
godbc-get-all-domain-metadata-query=select kind,content from domains, domainmetadata where domainmetadata.domain_id=domains.id and name=?
-godbc-get-all-domains-query=select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ?
+godbc-get-all-domains-query=select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ?
godbc-get-domain-metadata-query=select content from domains, domainmetadata where domainmetadata.domain_id=domains.id and name=? and domainmetadata.kind=?
godbc-get-last-inserted-key-id-query=select last_insert_rowid()
godbc-get-order-after-query=select min(ordername) from records where disabled=0 and ordername > ? and domain_id=? and ordername is not null
godbc-get-tsig-keys-query=select name,algorithm, secret from tsigkeys
godbc-publish-domain-key-query=update cryptokeys set published=1 where domain_id=(select id from domains where name=?) and cryptokeys.id=?
godbc-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and type=? and name=? and domain_id=?
-godbc-info-all-master-query=select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')
-godbc-info-all-slaves-query=select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')
+godbc-info-all-primary-query=select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')
+godbc-info-all-secondaries-query=select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')
godbc-info-zone-query=select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?
godbc-info-producer-members-query=select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0
godbc-info-consumer-members-query=select id, name, options, master from domains where type='SLAVE' and catalog=?
godbc-search-records-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE name LIKE ? OR content LIKE ? LIMIT ?
godbc-set-domain-metadata-query=insert into domainmetadata (domain_id, kind, content) select id, ?, ? from domains where name=?
godbc-set-tsig-key-query=replace into tsigkeys (name,algorithm,secret) values(?,?,?)
-godbc-supermaster-query=select account from supermasters where ip=? and nameserver=?
+godbc-autoprimary-query=select account from supermasters where ip=? and nameserver=?
godbc-unpublish-domain-key-query=update cryptokeys set published=0 where domain_id=(select id from domains where name=?) and cryptokeys.id=?
godbc-update-account-query=update domains set account=? where name=?
godbc-update-kind-query=update domains set type=? where name=?
godbc-update-lastcheck-query=update domains set last_check=? where id=?
-godbc-update-master-query=update domains set master=? where name=?
+godbc-update-primary-query=update domains set master=? where name=?
godbc-update-ordername-and-auth-query=update records set ordername=?,auth=? where domain_id=? and name=? and disabled=0
godbc-update-ordername-and-auth-type-query=update records set ordername=?,auth=? where domain_id=? and name=? and type=? and disabled=0
godbc-update-serial-query=update domains set notified_serial=? where id=?
"$GPGSQL2DB"
if [ "$zone" = "tsig.com" ]; then
$PDNSUTIL --config-dir=. --config-name=gpgsql2 import-tsig-key test $ALGORITHM $KEY
- $PDNSUTIL --config-dir=. --config-name=gpgsql2 activate-tsig-key tsig.com test slave
+ $PDNSUTIL --config-dir=. --config-name=gpgsql2 activate-tsig-key tsig.com test secondary
fi
if [ "$zone" = "stest.com" ]; then
if [[ $skipreasons != *nolua* ]]; then
$RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \
--config-name=gpgsql2 --socket-dir=./ --no-shuffle \
- --slave --retrieval-threads=4 \
- --slave-cycle-interval=300 --dname-processing &
+ --secondary --retrieval-threads=4 \
+ --xfr-cycle-interval=300 --dname-processing &
echo 'waiting for zones to be slaved'
loopcount=0
fi
if [ "$zone" = "tsig.com" ]; then
$PDNSUTIL --config-dir=. --config-name=$backend import-tsig-key test $ALGORITHM $KEY
- $PDNSUTIL --config-dir=. --config-name=$backend activate-tsig-key tsig.com test master
+ $PDNSUTIL --config-dir=. --config-name=$backend activate-tsig-key tsig.com test primary
fi
done
sqlite3 pdns.sqlite32 "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port');"
if [ "$zone" = "tsig.com" ]; then
$PDNSUTIL --config-dir=. --config-name=gsqlite32 import-tsig-key test $ALGORITHM $KEY
- $PDNSUTIL --config-dir=. --config-name=gsqlite32 activate-tsig-key tsig.com test slave
+ $PDNSUTIL --config-dir=. --config-name=gsqlite32 activate-tsig-key tsig.com test secondary
fi
if [ "$zone" = "stest.com" ]; then
if [[ $skipreasons != *nolua* ]]; then
fi
if [ "$zone" = "tsig.com" ]; then
$PDNSUTIL --config-dir=. --config-name=lmdb import-tsig-key test $ALGORITHM $KEY
- $PDNSUTIL --config-dir=. --config-name=lmdb activate-tsig-key tsig.com test master
+ $PDNSUTIL --config-dir=. --config-name=lmdb activate-tsig-key tsig.com test primary
fi
done
echo "" >> bind.conf
echo "zone \"${zone}\" {" >> bind.conf
- echo " type master;" >> bind.conf
+ echo " type primary;" >> bind.conf
if [ "${zone}" = "tsig.com" ]
then
echo " allow-transfer { key test; none; };" >> bind.conf
echo "" >> bind-slave.conf
echo "zone \"${zone}\" {" >> bind-slave.conf
- echo " type slave;" >> bind-slave.conf
+ echo " type secondary;" >> bind-slave.conf
echo " file \"${zone}-slave\";" >> bind-slave.conf
if [ "${zone}" = "tsig.com" ]
then
minimal-responses yes;
};
zone "example.com"{
- type master;
+ type primary;
file "example.com";
};
zone "test.com"{
- type master;
+ type primary;
file "test.com";
};
zone "test.dyndns" {
- type master;
+ type primary;
file "test.dyndns";
allow-update {
127.0.0.0/8;
};
zone "sub.test.dyndns" {
- type master;
+ type primary;
file "sub.test.dyndns";
allow-update {
127.0.0.0/8;
};
zone "wtest.com"{
- type master;
+ type primary;
file "wtest.com";
};
zone "nztest.com"{
- type master;
+ type primary;
file "nztest.com";
};
zone "dnssec-parent.com"{
- type master;
+ type primary;
file "dnssec-parent.com";
};
zone "insecure.dnssec-parent.com"{
- type master;
+ type primary;
file "insecure.dnssec-parent.com";
};
zone "delegated.dnssec-parent.com"{
- type master;
+ type primary;
file "delegated.dnssec-parent.com";
};
zone "secure-delegated.dnssec-parent.com"{
- type master;
+ type primary;
file "secure-delegated.dnssec-parent.com";
};
zone "minimal.com"{
- type master;
+ type primary;
file "minimal.com";
};
zone "tsig.com"{
- type master;
+ type primary;
file "tsig.com";
};
zone "stest.com"{
- type master;
+ type primary;
file "stest.com";
};
zone "cdnskey-cds-test.com"{
- type master;
+ type primary;
file "cdnskey-cds-test.com";
};
zone "2.0.192.in-addr.arpa"{
- type master;
+ type primary;
file "2.0.192.in-addr.arpa";
};
zone "cryptokeys.org"{
- type master;
+ type primary;
file "cryptokeys.org";
};
zone "hiddencryptokeys.org"{
- type master;
+ type primary;
file "hiddencryptokeys.org";
};
ANANSWER=$[(100*(${DBT_QUEUED}-${DBT_ERRORS}-${DBT_TIMEOUTS}) )/${DBT_QUEUED}]
-if [ "$ANANSWER" -ge $THRESHOLD ]
+if [ $ANANSWER -ge $THRESHOLD ]
then
- echo recursor-bulktest >> passed_tests
- RETVAL=0
+ echo recursor-bulktest >> passed_tests
+ RETVAL=0
else
+ echo "::error title=Recursor-bulktest::Bulk test failed: less than ${THRESHOLD}% of queries answered successfully"
echo recursor-bulktest >> failed_tests
RETVAL=1
fi
#!/usr/bin/env bash
+if [ -z "$testsdir" ]; then
+ echo "Incorrect usage. You probably want ./start-test-stop help"
+ exit 1
+fi
+
PATH=.:$PATH:/usr/sbin
MAKE=${MAKE:-make}
echo >> test-results
done
+if [ $failed -gt 0 ]; then
+ echo -n "::error title=Regression-tests::Tests failed. "
+fi
echo -n $passed out of $[$passed+$failed]
echo -n " ("
res=$((echo scale=2; echo 100*$passed/\($passed+$failed\)) | bc )
--- /dev/null
+#!/bin/sh
+cleandig test.mixed-wc.example.com A
--- /dev/null
+If a CNAME and another type exist on a wildcard (ignoring RFC requirements),
+only the CNAME shall be returned, ignoring the other type.
--- /dev/null
+0 outpost.example.com. 120 IN A 192.168.2.1
+0 test.mixed-wc.example.com. 120 IN CNAME outpost.example.com.
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.mixed-wc.example.com.', qtype=A
#!/bin/sh
cleandig sub.host.sub.example.com a dnssec
+cleandig sub.host.sub.example.com any dnssec tcp
2 . 32768 IN OPT
Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
Reply to question for qname='sub.host.sub.example.com.', qtype=A
+1 example.com. 86400 IN SOA ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+2 . 32768 IN OPT
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='sub.host.sub.example.com.', qtype=ANY
2 . 32768 IN OPT
Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
Reply to question for qname='sub.host.sub.example.com.', qtype=A
+1 example.com. 86400 IN RRSIG SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+1 example.com. 86400 IN SOA ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1 host.*.sub.example.com. 86400 IN NSEC bar.svcb.example.com. A RRSIG NSEC
+1 host.*.sub.example.com. 86400 IN RRSIG NSEC 13 5 86400 [expiry] [inception] [keytag] example.com. ...
+1 start4.example.com. 86400 IN NSEC host.*.sub.example.com. A RRSIG NSEC
+1 start4.example.com. 86400 IN RRSIG NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2 . 32768 IN OPT
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='sub.host.sub.example.com.', qtype=ANY
2 . 32768 IN OPT
Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
Reply to question for qname='sub.host.sub.example.com.', qtype=A
+1 5ui8h56r4776maicvhpdegs6chr19i99.example.com. 86400 IN NSEC3 1 [flags] 1 abcd 5UI8H56R4776MAICVHPDEGS6CHR19I9A
+1 5ui8h56r4776maicvhpdegs6chr19i99.example.com. 86400 IN RRSIG NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1 example.com. 86400 IN RRSIG SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+1 example.com. 86400 IN SOA ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1 hhrsadparthvtuou67trentjstdodla0.example.com. 86400 IN NSEC3 1 [flags] 1 abcd HHRSADPARTHVTUOU67TRENTJSTDODLA1
+1 hhrsadparthvtuou67trentjstdodla0.example.com. 86400 IN RRSIG NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1 pbl3rtqv3mt7eb29gqp0a17o0h42nj76.example.com. 86400 IN NSEC3 1 [flags] 1 abcd PBL3RTQV3MT7EB29GQP0A17O0H42NJ78
+1 pbl3rtqv3mt7eb29gqp0a17o0h42nj76.example.com. 86400 IN RRSIG NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2 . 32768 IN OPT
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='sub.host.sub.example.com.', qtype=ANY
2 . 32768 IN OPT
Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
Reply to question for qname='sub.host.sub.example.com.', qtype=A
+1 5ui8h56r4776maicvhpdegs6chr19i99.example.com. 86400 IN NSEC3 1 [flags] 1 abcd 5UMB87SUFNRRMLILGL48A5GUUHG7RI58
+1 5ui8h56r4776maicvhpdegs6chr19i99.example.com. 86400 IN RRSIG NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1 example.com. 86400 IN RRSIG SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+1 example.com. 86400 IN SOA ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1 hhrsadparthvtuou67trentjstdodla0.example.com. 86400 IN NSEC3 1 [flags] 1 abcd HHTKKD5HB125SGANBTKMQK84LULH60LH
+1 hhrsadparthvtuou67trentjstdodla0.example.com. 86400 IN RRSIG NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1 pbkjnd53pnsru5jmaqnk3k936pv2pq5j.example.com. 86400 IN NSEC3 1 [flags] 1 abcd PBL4SE96F8T4H4Q24UQMRQ4KS96AHPV3 A RRSIG
+1 pbkjnd53pnsru5jmaqnk3k936pv2pq5j.example.com. 86400 IN RRSIG NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2 . 32768 IN OPT
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='sub.host.sub.example.com.', qtype=ANY
+++ /dev/null
-#!/usr/bin/env bash
-
-"$@" 2>&1 | ts "[%F %T]"
-
-exit ${PIPESTATUS[0]}
italy-ns1 IN A 192.168.5.1
italy-ns2 IN A 192.168.5.2
;
+; CNAME on wildcard mixed with another type
+*.mixed-wc IN A 192.168.1.1
+*.mixed-wc IN CNAME outpost
+;
mail IN MX 25 smtp1
smtp1 IN CNAME outpost
;
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')
+quiche_version = '0.20.1'
+quiche_hash = '9c460d8ecf6c80c06bf9b42f91201ef33f912e2615a871ff2d0e50197b901c71'
+
all_build_deps = [
'ccache',
'libboost-all-dev',
'libluajit-5.1-dev',
'libsodium-dev',
- 'libssl-dev',
+ 'libssl-dev', # This will install libssl 1.1 on Debian 11 and libssl3 on Debian 12
'libsystemd-dev',
'libtool',
'make',
'libcap2',
'libfstrm0',
'libluajit-5.1-2',
- 'libsnmp35',
+ '"libsnmp[1-9]+"',
'libsodium23',
- 'libssl1.1',
'libsystemd0',
'moreutils',
'pdns-tools',
'libre2-dev',
'libsnmp-dev',
]
+dnsdist_xdp_build_deps = [
+ 'libbpf-dev',
+ 'libxdp-dev',
+]
auth_test_deps = [ # FIXME: we should be generating some of these from shlibdeps in build
'authbind',
'bc',
'curl',
'default-jre-headless',
'dnsutils',
- 'docker-compose',
'faketime',
'gawk',
'krb5-user',
'ldnsutils',
- 'libboost-serialization1.71.0',
+ '"libboost-serialization1.7[1-9]+"',
'libcdb1',
'libcurl4',
'libgeoip1',
'libkrb5-3',
- 'libldap-2.4-2',
+ '"libldap-2.[1-9]+"',
'liblmdb0',
'libluajit-5.1-2',
'libmaxminddb0',
'libpq5',
'libsodium23',
'libsqlite3-dev',
- 'libssl1.1',
'libsystemd0',
- 'libyaml-cpp0.6',
+ '"libyaml-cpp0.[1-9]+"',
'libzmq3-dev',
'lmdb-utils',
'prometheus',
@task
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(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}')
+
+@task
+def ci_install_rust(c, repo):
+ c.sudo(f'{repo}/builder-support/helpers/install_rust.sh')
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={get_c_compiler()} CXX={get_cxx_compiler()} '
'cmake -B build '
'-DCMAKE_INSTALL_PREFIX=/usr/local '
'-DCMAKE_INSTALL_LIBDIR=lib '
@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))
- install_libdecaf(c, 'pdns-auth')
+ c.sudo('apt-get install -y --no-install-recommends ' + ' '.join(all_build_deps + git_build_deps + auth_build_deps))
+ if os.getenv('DECAF_SUPPORT', 'no') == 'yes':
+ install_libdecaf(c, 'pdns-auth')
+
+def is_coverage_enabled():
+ sanitizers = os.getenv('SANITIZERS')
+ if sanitizers:
+ sanitizers = sanitizers.split('+')
+ if 'tsan' in sanitizers:
+ return False
+ return os.getenv('COVERAGE') == 'yes'
+
+def get_coverage():
+ return '--enable-coverage=clang' if is_coverage_enabled() else ''
+
+@task
+def install_coverage_deps(c):
+ if is_coverage_enabled():
+ c.sudo(f'apt-get install -y --no-install-recommends llvm-{clang_version}')
+
+@task
+def generate_coverage_info(c, binary, outputDir):
+ if is_coverage_enabled():
+ version = os.getenv('BUILDER_VERSION')
+ c.run(f'llvm-profdata-{clang_version} merge -sparse -o {outputDir}/temp.profdata /tmp/code-*.profraw')
+ c.run(f'llvm-cov-{clang_version} export --format=lcov --ignore-filename-regex=\'^/usr/\' -instr-profile={outputDir}/temp.profdata -object {binary} > {outputDir}/coverage.lcov')
+ c.run(f'{outputDir}/.github/scripts/normalize_paths_in_coverage.py {outputDir} {version} {outputDir}/coverage.lcov {outputDir}/normalized_coverage.lcov')
+ c.run(f'mv {outputDir}/normalized_coverage.lcov {outputDir}/coverage.lcov')
def setup_authbind(c):
c.sudo('touch /etc/authbind/byport/53')
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
# FIXME we may want to start a background recursor here to make ALIAS tests more robust
setup_authbind(c)
- # Copy libdecaf out
- c.sudo('mkdir -p /usr/local/lib')
- c.sudo('cp /opt/pdns-auth/libdecaf/libdecaf.so* /usr/local/lib/.')
+ if os.getenv('DECAF_SUPPORT', 'no') == 'yes':
+ # Copy libdecaf out
+ c.sudo('mkdir -p /usr/local/lib')
+ c.sudo('cp /opt/pdns-auth/libdecaf/libdecaf.so* /usr/local/lib/.')
@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 \
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 \
- libluajit-5.1-2 \
- libboost-all-dev \
- libcap2 \
- libcdb1 \
- libcurl4-openssl-dev \
- libfstrm0 \
- libgnutls30 \
- libh2o-evloop0.13 \
- liblmdb0 \
- libnghttp2-14 \
- libre2-5 \
- libssl-dev \
- libsystemd0 \
- libsodium23 \
- lua-socket \
- patch \
- protobuf-compiler \
- python3-venv snmpd prometheus')
+@task(optional=['skipXDP'])
+def install_dnsdist_test_deps(c, skipXDP=False): # FIXME: rename this, we do way more than apt-get
+ deps = 'libluajit-5.1-2 \
+ libboost-all-dev \
+ libcap2 \
+ libcdb1 \
+ libcurl4-openssl-dev \
+ libfstrm0 \
+ libgnutls30 \
+ libh2o-evloop0.13 \
+ liblmdb0 \
+ libnghttp2-14 \
+ "libre2-[1-9]+" \
+ libssl-dev \
+ libsystemd0 \
+ libsodium23 \
+ lua-socket \
+ patch \
+ protobuf-compiler \
+ python3-venv snmpd prometheus'
+ if not skipXDP:
+ deps = deps + '\
+ libbpf1 \
+ libxdp1'
+
+ c.sudo(f'apt-get install -y {deps}')
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))
+@task(optional=['skipXDP'])
+def install_dnsdist_build_deps(c, skipXDP=False):
+ c.sudo('apt-get install -y --no-install-recommends ' + ' '.join(all_build_deps + git_build_deps + dnsdist_build_deps + (dnsdist_xdp_build_deps if not skipXDP else [])))
@task
def ci_autoconf(c):
- c.run('BUILDER_VERSION=0.0.0-git1 autoreconf -vfi')
+ c.run('autoreconf -vfi')
+
+@task
+def ci_docs_rec_generate(c):
+ c.run('python3 generate.py')
@task
def ci_docs_build(c):
def get_sanitizers():
- sanitizers = os.getenv('SANITIZERS')
+ sanitizers = os.getenv('SANITIZERS', '')
if sanitizers != '':
sanitizers = sanitizers.split('+')
sanitizers = ['--enable-' + sanitizer for sanitizer in sanitizers]
sanitizers = ' '.join(sanitizers)
return sanitizers
+def get_unit_tests(auth=False):
+ if os.getenv('UNIT_TESTS') != 'yes':
+ return ''
+ return '--enable-unit-tests --enable-backend-unit-tests' if auth else '--enable-unit-tests'
+
+def get_build_concurrency(default=8):
+ return os.getenv('CONCURRENCY', default)
+
+def get_fuzzing_targets():
+ return '--enable-fuzz-targets' if os.getenv('FUZZING_TARGETS') == 'yes' else ''
+
+def is_compiler_clang():
+ compiler = os.getenv('COMPILER', 'clang')
+ return compiler == 'clang'
+
+def get_c_compiler():
+ return f'clang-{clang_version}' if is_compiler_clang() else 'gcc'
+
+def get_cxx_compiler():
+ return f'clang++-{clang_version}' if is_compiler_clang() else 'g++'
+
+def get_optimizations():
+ optimizations = os.getenv('OPTIMIZATIONS', 'yes')
+ return '-O1' if optimizations == 'yes' else '-O0'
def get_cflags():
return " ".join([
- "-O1",
+ get_optimizations(),
"-Werror=vla",
"-Werror=shadow",
"-Wformat=2",
"-Werror=format-security",
- "-Werror=string-plus-int",
+ "-fstack-clash-protection",
+ "-fstack-protector-strong",
+ "-fcf-protection=full",
+ "-Werror=string-plus-int" if is_compiler_clang() else '',
])
])
-def get_base_configure_cmd():
+def get_base_configure_cmd(additional_c_flags='', additional_cxx_flags='', enable_systemd=True, enable_sodium=True):
+ cflags = " ".join([get_cflags(), additional_c_flags])
+ cxxflags = " ".join([get_cxxflags(), additional_cxx_flags])
return " ".join([
- f'CFLAGS="{get_cflags()}"',
- f'CXXFLAGS="{get_cxxflags()}"',
+ f'CFLAGS="{cflags}"',
+ f'CXXFLAGS="{cxxflags}"',
'./configure',
- "CC='clang-12'",
- "CXX='clang++-12'",
+ f"CC='{get_c_compiler()}'",
+ f"CXX='{get_cxx_compiler()}'",
"--enable-option-checking=fatal",
- "--enable-systemd",
- "--with-libsodium",
+ "--enable-systemd" if enable_systemd else '',
+ "--with-libsodium" if enable_sodium else '',
"--enable-fortify-source=auto",
"--enable-auto-var-init=pattern",
+ get_coverage(),
+ get_sanitizers()
])
@task
def ci_auth_configure(c):
- sanitizers = get_sanitizers()
-
- unittests = os.getenv('UNIT_TESTS')
- if unittests == 'yes':
- unittests = '--enable-unit-tests --enable-backend-unit-tests'
- else:
- unittests = ''
-
- fuzz_targets = os.getenv('FUZZING_TARGETS')
- fuzz_targets = '--enable-fuzz-targets' if fuzz_targets == 'yes' else ''
-
+ unittests = get_unit_tests(True)
+ fuzz_targets = get_fuzzing_targets()
modules = " ".join([
"bind",
"geoip",
"LDFLAGS='-L/usr/local/lib -Wl,-rpath,/usr/local/lib'",
f"--with-modules='{modules}'",
"--enable-tools",
+ "--enable-dns-over-tls",
"--enable-experimental-pkcs11",
"--enable-experimental-gss-tsig",
"--enable-remotebackend-zeromq",
+ "--enable-verbose-logging",
"--with-lmdb=/usr",
- "--with-libdecaf",
+ "--with-libdecaf" if os.getenv('DECAF_SUPPORT', 'no') == 'yes' else '',
"--prefix=/opt/pdns-auth",
"--enable-ixfrdist",
- sanitizers,
unittests,
- fuzz_targets,
+ fuzz_targets
])
res = c.run(configure_cmd, warn=True)
if res.exited != 0:
@task
-def ci_rec_configure(c):
- sanitizers = get_sanitizers()
-
- unittests = os.getenv('UNIT_TESTS')
- unittests = '--enable-unit-tests' if unittests == 'yes' else ''
+def ci_rec_configure(c, features):
+ unittests = get_unit_tests()
- configure_cmd = " ".join([
- get_base_configure_cmd(),
- "--enable-nod",
- "--prefix=/opt/pdns-recursor",
- "--with-lua=luajit",
- "--with-libcap",
- "--with-net-snmp",
- "--enable-dns-over-tls",
- sanitizers,
- unittests,
- ])
+ if features == 'full':
+ configure_cmd = " ".join([
+ get_base_configure_cmd(),
+ "--prefix=/opt/pdns-recursor",
+ "--enable-option-checking",
+ "--enable-verbose-logging",
+ "--enable-dns-over-tls",
+ "--enable-nod",
+ "--with-libcap",
+ "--with-lua=luajit",
+ "--with-net-snmp",
+ unittests,
+ ])
+ else:
+ configure_cmd = " ".join([
+ get_base_configure_cmd(),
+ "--prefix=/opt/pdns-recursor",
+ "--enable-option-checking",
+ "--enable-verbose-logging",
+ "--disable-dns-over-tls",
+ "--disable-dnstap",
+ "--disable-nod",
+ "--disable-systemd",
+ "--with-lua=luajit",
+ "--without-libcap",
+ "--without-libcurl",
+ "--without-libdecaf",
+ "--without-libsodium",
+ "--without-net-snmp",
+ unittests,
+ ])
res = c.run(configure_cmd, warn=True)
if res.exited != 0:
c.run('cat config.log')
--enable-dnscrypt \
--enable-dns-over-tls \
--enable-dns-over-https \
+ --enable-dns-over-quic \
+ --enable-dns-over-http3 \
--enable-systemd \
--prefix=/opt/dnsdist \
--with-gnutls \
+ --with-h2o \
--with-libsodium \
--with-lua=luajit \
--with-libcap \
+ --with-net-snmp \
--with-nghttp2 \
- --with-re2 '
+ --with-re2'
else:
features_set = '--disable-dnstap \
--disable-dnscrypt \
--without-cdb \
--without-ebpf \
--without-gnutls \
+ --without-h2o \
--without-libedit \
--without-libsodium \
--without-lmdb \
--without-net-snmp \
--without-nghttp2 \
- --without-re2 '
+ --without-re2'
additional_flags = '-DDISABLE_COMPLETION \
-DDISABLE_DELAY_PIPE \
-DDISABLE_DYNBLOCKS \
-DDISABLE_HASHED_CREDENTIALS \
-DDISABLE_FALSE_SHARING_PADDING \
-DDISABLE_NPN'
- unittests = ' --enable-unit-tests' if os.getenv('UNIT_TESTS') == 'yes' else ''
- 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" \
- CXXFLAGS="%s" \
- AR=llvm-ar-12 \
- RANLIB=llvm-ranlib-12 \
- ./configure \
- CC='clang-12' \
- CXX='clang++-12' \
- --enable-option-checking=fatal \
- --enable-fortify-source=auto \
- --enable-auto-var-init=pattern \
- --enable-lto=thin \
- --prefix=/opt/dnsdist %s %s %s''' % (cflags, cxxflags, features_set, sanitizers, unittests), warn=True)
+ unittests = get_unit_tests()
+ fuzztargets = get_fuzzing_targets()
+ tools = f'''AR=llvm-ar-{clang_version} RANLIB=llvm-ranlib-{clang_version}''' if is_compiler_clang() else ''
+ configure_cmd = " ".join([
+ tools,
+ get_base_configure_cmd(additional_c_flags='', additional_cxx_flags=additional_flags, enable_systemd=False, enable_sodium=False),
+ features_set,
+ unittests,
+ fuzztargets,
+ '--enable-lto=thin',
+ '--prefix=/opt/dnsdist'
+ ])
+
+ res = c.run(configure_cmd, warn=True)
if res.exited != 0:
c.run('cat config.log')
raise UnexpectedExit(res)
@task
def ci_auth_make(c):
- c.run('make -j8 -k V=1')
+ c.run(f'make -j{get_build_concurrency()} -k V=1')
+
+@task
+def ci_auth_make_bear(c):
+ c.run(f'bear --append -- make -j{get_build_concurrency()} -k V=1')
@task
def ci_rec_make(c):
- c.run('make -j8 -k V=1')
+ c.run(f'make -j{get_build_concurrency()} -k V=1')
+
+@task
+def ci_rec_make_bear(c):
+ # Assumed to be running under ./pdns/recursordist/
+ c.run(f'bear --append -- make -j{get_build_concurrency()} -k V=1')
@task
def ci_dnsdist_make(c):
- c.run('make -j4 -k V=1')
+ c.run(f'make -j{get_build_concurrency(4)} -k V=1')
+
+@task
+def ci_dnsdist_make_bear(c):
+ # Assumed to be running under ./pdns/dnsdistdist/
+ c.run(f'bear --append -- make -j{get_build_concurrency(4)} -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):
c.run('cat test-suite.log')
raise UnexpectedExit(res)
+@task
+def ci_make_distdir(c):
+ res = c.run('make distdir')
+
@task
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")
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')
'bind-dnssec-nsec3-both',
'bind-dnssec-nsec3-optout-both',
'bind-dnssec-nsec3-narrow',
- # FIXME 'bind-dnssec-pkcs11'
+ 'bind-dnssec-pkcs11'
],
geoip = [
'geoip',
geoip_mmdb = ['geoip'],
)
-godbc_mssql_credentials = {"username": "sa", "password": "SAsa12%%"}
+godbc_mssql_credentials = {"username": "sa", "password": "SAsa12%%-not-a-secret-password"}
-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
[pdns-mssql-docker-nodb]
Driver=FreeTDS
Trace=No
-Server=127.0.0.1
+Server={auth_backend_ip_addr}
Port=1433
TDS_Version=7.1
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\'')
+
+def setup_softhsm(c):
+ # Modify the location of the softhsm tokens and configuration directory.
+ # Enables token generation by non-root users (runner)
+ c.run('mkdir -p /opt/pdns-auth/softhsm/tokens')
+ c.run('echo "directories.tokendir = /opt/pdns-auth/softhsm/tokens" > /opt/pdns-auth/softhsm/softhsm2.conf')
@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
+ if backend == 'bind':
+ setup_softhsm(c)
+ with c.cd('regression-tests'):
+ for variant in backend_regress_tests[backend]:
+ c.run(f'{pdns_auth_env_vars} SOFTHSM2_CONF=/opt/pdns-auth/softhsm/softhsm2.conf ./start-test-stop 5300 {variant}')
+ return
+
if backend == 'godbc_sqlite3':
setup_godbc_sqlite3(c)
with c.cd('regression-tests'):
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'{pdns_auth_env_vars} ./runtests')
c.run('/opt/pdns-auth/bin/pdnsutil test-algorithms')
c.run('ls -ald /var /var/agentx /var/agentx/master')
c.run('ls -al /var/agentx/master')
with c.cd('regression-tests.dnsdist'):
- c.run('DNSDISTBIN=/opt/dnsdist/bin/dnsdist ./runtests')
+ c.run('DNSDISTBIN=/opt/dnsdist/bin/dnsdist LD_LIBRARY_PATH=/opt/dnsdist/lib/ ENABLE_SUDO_TESTS=1 ./runtests')
@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):
c.run('curl -LO http://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip')
c.run('unzip top-1m.csv.zip -d .')
c.run('chmod +x /opt/pdns-recursor/bin/* /opt/pdns-recursor/sbin/*')
- c.run(f'DNSBULKTEST=/usr/bin/dnsbulktest RECURSOR=/opt/pdns-recursor/sbin/pdns_recursor RECCONTROL=/opt/pdns-recursor/bin/rec_control THRESHOLD=95 TRACE=no ./timestamp ./recursor-test 5300 100 {threads} {mthreads} {shards}')
+ c.run(f'DNSBULKTEST=/usr/bin/dnsbulktest RECURSOR=/opt/pdns-recursor/sbin/pdns_recursor RECCONTROL=/opt/pdns-recursor/bin/rec_control THRESHOLD=95 TRACE=no ./recursor-test 5300 100 {threads} {mthreads} {shards}')
@task
def install_swagger_tools(c):
@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):
--form description="master build" \
https://scan.coverity.com/builds?project={project}', hide=True)
+@task
+def ci_build_and_install_quiche(c):
+ # we have to pass -L because GitHub will do a redirect, sadly
+ c.run(f'curl -L -o quiche-{quiche_version}.tar.gz https://github.com/cloudflare/quiche/archive/{quiche_version}.tar.gz')
+ # Line below should echo two spaces between digest and name
+ c.run(f'echo {quiche_hash}" "quiche-{quiche_version}.tar.gz | sha256sum -c -')
+ c.run(f'tar xf quiche-{quiche_version}.tar.gz')
+ with c.cd(f'quiche-{quiche_version}'):
+ c.run('cargo build --release --no-default-features --features ffi,boringssl-boring-crate --package quiche')
+ # cannot use c.sudo() inside a cd() context, see https://github.com/pyinvoke/invoke/issues/687
+ c.run('sudo install -Dm644 quiche/include/quiche.h /usr/include')
+ c.run('sudo install -Dm644 target/release/libquiche.so /usr/lib')
+ c.run('install -D target/release/libquiche.so /opt/dnsdist/lib/libquiche.so')
+ c.run(f"""sudo install -Dm644 /dev/stdin /usr/lib/pkgconfig/quiche.pc <<PC
+# quiche
+Name: quiche
+Description: quiche library
+URL: https://github.com/cloudflare/quiche
+Version: {quiche_version}
+Libs: -lquiche
+PC""")
+
# this is run always
def setup():
if '/usr/lib/ccache' not in os.environ['PATH']: